mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-13 11:13:48 +00:00
Added focused-window shortcut fallback on Linux (#882)
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
This commit is contained in:
1 parent
db508edc28
commit
1e5f07cb01
11 files changed
+350
-17
No files matched your search
@@ -8,4 +8,5 @@ public enum ShortcutBackend
|
||||
NONE,
|
||||
PORTAL,
|
||||
TAURI,
|
||||
LOCAL,
|
||||
}
|
||||
@@ -20,13 +20,19 @@ public sealed class GlobalShortcutService : BackgroundService, IMessageBusReceiv
|
||||
}
|
||||
|
||||
private readonly SemaphoreSlim registrationSemaphore = new(1, 1);
|
||||
private readonly object runtimeStateLock = new();
|
||||
private readonly Dictionary<Shortcut, ShortcutState> lastSentStates = [];
|
||||
private readonly Dictionary<Shortcut, string> lastNonEmptyShortcuts = [];
|
||||
private readonly Dictionary<Shortcut, ShortcutRuntimeBinding> runtimeBindings = [];
|
||||
private readonly ILogger<GlobalShortcutService> logger;
|
||||
private readonly SettingsManager settingsManager;
|
||||
private readonly MessageBus messageBus;
|
||||
private readonly RustService rustService;
|
||||
private readonly VoiceRecordingAvailabilityService voiceRecordingAvailabilityService;
|
||||
private bool isProcessingSuspended;
|
||||
private bool localFallbackWarningShown;
|
||||
|
||||
public event Func<GlobalShortcutRuntimeState, Task>? RuntimeStateChanged;
|
||||
|
||||
public GlobalShortcutService(
|
||||
ILogger<GlobalShortcutService> logger,
|
||||
@@ -58,6 +64,45 @@ public sealed class GlobalShortcutService : BackgroundService, IMessageBusReceiv
|
||||
await base.StopAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the active backend and processing state for a shortcut.
|
||||
/// </summary>
|
||||
public GlobalShortcutRuntimeState GetRuntimeState(Shortcut shortcutId)
|
||||
{
|
||||
lock (this.runtimeStateLock)
|
||||
{
|
||||
if (this.runtimeBindings.TryGetValue(shortcutId, out var binding))
|
||||
return new(shortcutId, binding.Shortcut, binding.Backend, this.isProcessingSuspended);
|
||||
|
||||
return new(shortcutId, string.Empty, ShortcutBackend.NONE, this.isProcessingSuspended);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pauses native and focused-window shortcut processing.
|
||||
/// </summary>
|
||||
public async Task<bool> SuspendShortcutProcessing()
|
||||
{
|
||||
lock (this.runtimeStateLock)
|
||||
this.isProcessingSuspended = true;
|
||||
|
||||
await this.PublishAllRuntimeStates();
|
||||
return await this.rustService.SuspendShortcutProcessing();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resumes native and focused-window shortcut processing.
|
||||
/// </summary>
|
||||
public async Task<bool> ResumeShortcutProcessing()
|
||||
{
|
||||
var result = await this.rustService.ResumeShortcutProcessing();
|
||||
lock (this.runtimeStateLock)
|
||||
this.isProcessingSuspended = false;
|
||||
|
||||
await this.PublishAllRuntimeStates();
|
||||
return result;
|
||||
}
|
||||
|
||||
#region IMessageBusReceiver
|
||||
|
||||
public async Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
|
||||
@@ -155,6 +200,9 @@ public sealed class GlobalShortcutService : BackgroundService, IMessageBusReceiv
|
||||
if (!string.IsNullOrWhiteSpace(requestedState.Shortcut))
|
||||
this.lastNonEmptyShortcuts[shortcutId] = requestedState.Shortcut;
|
||||
|
||||
lock (this.runtimeStateLock)
|
||||
this.runtimeBindings[shortcutId] = new(requestedState.Shortcut, result.Backend);
|
||||
|
||||
this.logger.LogInformation(
|
||||
"Global shortcut '{ShortcutId}' ({Shortcut}) synchronized through {Backend}.",
|
||||
shortcutId,
|
||||
@@ -163,6 +211,15 @@ public sealed class GlobalShortcutService : BackgroundService, IMessageBusReceiv
|
||||
|
||||
if (result.Backend is ShortcutBackend.PORTAL)
|
||||
await this.UpdateEffectiveDisplayName(shortcutId, result.EffectiveDisplayName);
|
||||
|
||||
await this.PublishRuntimeState(shortcutId);
|
||||
if (result.Backend is ShortcutBackend.LOCAL && !this.localFallbackWarningShown)
|
||||
{
|
||||
this.localFallbackWarningShown = true;
|
||||
await this.messageBus.SendWarning(new(
|
||||
Icons.Material.Filled.Keyboard,
|
||||
TB("The voice recording shortcut currently works only while AI Studio is focused.")));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -236,6 +293,31 @@ public sealed class GlobalShortcutService : BackgroundService, IMessageBusReceiv
|
||||
await this.messageBus.SendMessage<bool>(null, Event.GLOBAL_SHORTCUT_CHANGED);
|
||||
}
|
||||
|
||||
private async Task PublishAllRuntimeStates()
|
||||
{
|
||||
Shortcut[] shortcutIds;
|
||||
lock (this.runtimeStateLock)
|
||||
shortcutIds = this.runtimeBindings.Keys.ToArray();
|
||||
|
||||
foreach (var shortcutId in shortcutIds)
|
||||
await this.PublishRuntimeState(shortcutId);
|
||||
}
|
||||
|
||||
private async Task PublishRuntimeState(Shortcut shortcutId)
|
||||
{
|
||||
var subscribers = this.RuntimeStateChanged;
|
||||
if (subscribers is null)
|
||||
return;
|
||||
|
||||
var handlers = subscribers.GetInvocationList()
|
||||
.Cast<Func<GlobalShortcutRuntimeState, Task>>()
|
||||
.ToArray();
|
||||
var runtimeState = this.GetRuntimeState(shortcutId);
|
||||
|
||||
foreach (var handler in handlers)
|
||||
await handler(runtimeState);
|
||||
}
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(GlobalShortcutService).Namespace, nameof(GlobalShortcutService));
|
||||
|
||||
private async Task<ShortcutState> GetShortcutState(Shortcut shortcutId, ShortcutSyncSource source)
|
||||
@@ -262,4 +344,12 @@ public sealed class GlobalShortcutService : BackgroundService, IMessageBusReceiv
|
||||
}
|
||||
|
||||
private readonly record struct ShortcutState(string Shortcut, bool IsEnabled, bool UsesPersistedFallback);
|
||||
}
|
||||
|
||||
private readonly record struct ShortcutRuntimeBinding(string Shortcut, ShortcutBackend Backend);
|
||||
}
|
||||
|
||||
public sealed record GlobalShortcutRuntimeState(
|
||||
Shortcut ShortcutId,
|
||||
string Shortcut,
|
||||
ShortcutBackend Backend,
|
||||
bool IsSuspended);
|
||||
Reference in new issue
Block a user