diff --git a/app/MindWork AI Studio/Components/ConfigurationShortcut.razor.cs b/app/MindWork AI Studio/Components/ConfigurationShortcut.razor.cs index 6d312caa..1bf605d2 100644 --- a/app/MindWork AI Studio/Components/ConfigurationShortcut.razor.cs +++ b/app/MindWork AI Studio/Components/ConfigurationShortcut.razor.cs @@ -1,4 +1,5 @@ using AIStudio.Dialogs; +using AIStudio.Tools.Services; using Microsoft.AspNetCore.Components; using DialogOptions = AIStudio.Dialogs.DialogOptions; @@ -13,6 +14,9 @@ public partial class ConfigurationShortcut : ConfigurationBaseCore [Inject] private IDialogService DialogService { get; init; } = null!; + [Inject] + private RustService RustService { get; init; } = null!; + /// /// The current shortcut value. /// @@ -59,7 +63,7 @@ public partial class ConfigurationShortcut : ConfigurationBaseCore if (string.IsNullOrWhiteSpace(shortcut)) return string.Empty; - // Convert internal format to display format + // Convert internal format to display format: return shortcut .Replace("CmdOrControl", OperatingSystem.IsMacOS() ? "Cmd" : "Ctrl") .Replace("CommandOrControl", OperatingSystem.IsMacOS() ? "Cmd" : "Ctrl"); @@ -67,27 +71,38 @@ public partial class ConfigurationShortcut : ConfigurationBaseCore private async Task OpenDialog() { - var currentShortcut = this.Shortcut(); - var dialogParameters = new DialogParameters + // Suspend shortcut processing while the dialog is open, so the user can + // press the current shortcut to re-enter it without triggering the action: + await this.RustService.SuspendShortcutProcessing(); + + try { - { x => x.InitialShortcut, currentShortcut }, - { x => x.ShortcutName, this.ShortcutName }, - }; + var dialogParameters = new DialogParameters + { + { x => x.InitialShortcut, this.Shortcut() }, + { x => x.ShortcutName, this.ShortcutName }, + }; - var dialogReference = await this.DialogService.ShowAsync( - this.T("Configure Keyboard Shortcut"), - dialogParameters, - DialogOptions.FULLSCREEN); + var dialogReference = await this.DialogService.ShowAsync( + this.T("Configure Keyboard Shortcut"), + dialogParameters, + DialogOptions.FULLSCREEN); - var dialogResult = await dialogReference.Result; - if (dialogResult is null || dialogResult.Canceled) - return; + var dialogResult = await dialogReference.Result; + if (dialogResult is null || dialogResult.Canceled) + return; - if (dialogResult.Data is string newShortcut) + if (dialogResult.Data is string newShortcut) + { + await this.ShortcutUpdate(newShortcut); + await this.SettingsManager.StoreSettings(); + await this.InformAboutChange(); + } + } + finally { - await this.ShortcutUpdate(newShortcut); - await this.SettingsManager.StoreSettings(); - await this.InformAboutChange(); + // Resume the shortcut processing when the dialog is closed: + await this.RustService.ResumeShortcutProcessing(); } } } diff --git a/app/MindWork AI Studio/Tools/Services/RustService.Shortcuts.cs b/app/MindWork AI Studio/Tools/Services/RustService.Shortcuts.cs index 62ea6ded..5da8d6ac 100644 --- a/app/MindWork AI Studio/Tools/Services/RustService.Shortcuts.cs +++ b/app/MindWork AI Studio/Tools/Services/RustService.Shortcuts.cs @@ -70,6 +70,72 @@ public sealed partial class RustService } } + /// + /// Suspends shortcut processing. The shortcuts remain registered, but events are not sent. + /// This is useful when opening a dialog to configure shortcuts, so the user can + /// press the current shortcut to re-enter it without triggering the action. + /// + /// True if successful, false otherwise. + public async Task SuspendShortcutProcessing() + { + try + { + var response = await this.http.PostAsync("/shortcuts/suspend", null); + if (!response.IsSuccessStatusCode) + { + this.logger?.LogError("Failed to suspend the shortcut processing due to network error: {StatusCode}.", response.StatusCode); + return false; + } + + var result = await response.Content.ReadFromJsonAsync(this.jsonRustSerializerOptions); + if (result is null || !result.Success) + { + this.logger?.LogError("Failed to suspend shortcut processing: {Error}", result?.ErrorMessage ?? "Unknown error"); + return false; + } + + this.logger?.LogDebug("Shortcut processing suspended."); + return true; + } + catch (Exception ex) + { + this.logger?.LogError(ex, "Exception while suspending shortcut processing."); + return false; + } + } + + /// + /// Resumes the shortcut processing after it was suspended. + /// + /// True if successful, false otherwise. + public async Task ResumeShortcutProcessing() + { + try + { + var response = await this.http.PostAsync("/shortcuts/resume", null); + if (!response.IsSuccessStatusCode) + { + this.logger?.LogError("Failed to resume shortcut processing due to network error: {StatusCode}.", response.StatusCode); + return false; + } + + var result = await response.Content.ReadFromJsonAsync(this.jsonRustSerializerOptions); + if (result is null || !result.Success) + { + this.logger?.LogError("Failed to resume shortcut processing: {Error}", result?.ErrorMessage ?? "Unknown error"); + return false; + } + + this.logger?.LogDebug("Shortcut processing resumed."); + return true; + } + catch (Exception ex) + { + this.logger?.LogError(ex, "Exception while resuming shortcut processing."); + return false; + } + } + private sealed record RegisterShortcutRequest(string Name, string Shortcut); private sealed record ShortcutResponse(bool Success, string ErrorMessage); diff --git a/runtime/src/app_window.rs b/runtime/src/app_window.rs index d42f76ca..a08a718d 100644 --- a/runtime/src/app_window.rs +++ b/runtime/src/app_window.rs @@ -31,6 +31,9 @@ static EVENT_BROADCAST: Lazy>>> = Lazy::ne /// Stores the currently registered global shortcuts (name -> shortcut string). static REGISTERED_SHORTCUTS: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); +/// Flag to temporarily suspend shortcut processing (shortcuts remain registered but events are not sent). +static SHORTCUTS_SUSPENDED: Lazy> = Lazy::new(|| Mutex::new(false)); + /// Starts the Tauri app. pub fn start_tauri() { info!("Starting Tauri app..."); @@ -770,6 +773,13 @@ pub fn register_shortcut(_token: APIToken, payload: Json Json { + let mut suspended = SHORTCUTS_SUSPENDED.lock().unwrap(); + *suspended = true; + info!(Source = "Tauri"; "Shortcut processing has been suspended."); + Json(ShortcutResponse { + success: true, + error_message: String::new(), + }) +} + +/// Resumes shortcut processing after it was suspended. +#[post("/shortcuts/resume")] +pub fn resume_shortcuts(_token: APIToken) -> Json { + let mut suspended = SHORTCUTS_SUSPENDED.lock().unwrap(); + *suspended = false; + info!(Source = "Tauri"; "Shortcut processing has been resumed."); + Json(ShortcutResponse { + success: true, + error_message: String::new(), + }) +} + /// Validates the syntax of a shortcut string. fn validate_shortcut_syntax(shortcut: &str) -> bool { let parts: Vec<&str> = shortcut.split('+').collect(); diff --git a/runtime/src/runtime_api.rs b/runtime/src/runtime_api.rs index a4ba4782..de81cc18 100644 --- a/runtime/src/runtime_api.rs +++ b/runtime/src/runtime_api.rs @@ -89,6 +89,8 @@ pub fn start_runtime_api() { crate::log::log_event, crate::app_window::register_shortcut, crate::app_window::validate_shortcut, + crate::app_window::suspend_shortcuts, + crate::app_window::resume_shortcuts, ]) .ignite().await.unwrap() .launch().await.unwrap();