Added suspend/resume handling for shortcut processing

This commit is contained in:
Thorsten Sommer 2026-01-23 08:38:29 +01:00
parent 55bf598912
commit e41be099dd
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
4 changed files with 136 additions and 17 deletions

View File

@ -1,4 +1,5 @@
using AIStudio.Dialogs; using AIStudio.Dialogs;
using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using DialogOptions = AIStudio.Dialogs.DialogOptions; using DialogOptions = AIStudio.Dialogs.DialogOptions;
@ -13,6 +14,9 @@ public partial class ConfigurationShortcut : ConfigurationBaseCore
[Inject] [Inject]
private IDialogService DialogService { get; init; } = null!; private IDialogService DialogService { get; init; } = null!;
[Inject]
private RustService RustService { get; init; } = null!;
/// <summary> /// <summary>
/// The current shortcut value. /// The current shortcut value.
/// </summary> /// </summary>
@ -59,7 +63,7 @@ public partial class ConfigurationShortcut : ConfigurationBaseCore
if (string.IsNullOrWhiteSpace(shortcut)) if (string.IsNullOrWhiteSpace(shortcut))
return string.Empty; return string.Empty;
// Convert internal format to display format // Convert internal format to display format:
return shortcut return shortcut
.Replace("CmdOrControl", OperatingSystem.IsMacOS() ? "Cmd" : "Ctrl") .Replace("CmdOrControl", OperatingSystem.IsMacOS() ? "Cmd" : "Ctrl")
.Replace("CommandOrControl", OperatingSystem.IsMacOS() ? "Cmd" : "Ctrl"); .Replace("CommandOrControl", OperatingSystem.IsMacOS() ? "Cmd" : "Ctrl");
@ -67,10 +71,15 @@ public partial class ConfigurationShortcut : ConfigurationBaseCore
private async Task OpenDialog() private async Task OpenDialog()
{ {
var currentShortcut = this.Shortcut(); // 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
{
var dialogParameters = new DialogParameters<ShortcutDialog> var dialogParameters = new DialogParameters<ShortcutDialog>
{ {
{ x => x.InitialShortcut, currentShortcut }, { x => x.InitialShortcut, this.Shortcut() },
{ x => x.ShortcutName, this.ShortcutName }, { x => x.ShortcutName, this.ShortcutName },
}; };
@ -90,4 +99,10 @@ public partial class ConfigurationShortcut : ConfigurationBaseCore
await this.InformAboutChange(); await this.InformAboutChange();
} }
} }
finally
{
// Resume the shortcut processing when the dialog is closed:
await this.RustService.ResumeShortcutProcessing();
}
}
} }

View File

@ -70,6 +70,72 @@ public sealed partial class RustService
} }
} }
/// <summary>
/// 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.
/// </summary>
/// <returns>True if successful, false otherwise.</returns>
public async Task<bool> 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<ShortcutResponse>(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;
}
}
/// <summary>
/// Resumes the shortcut processing after it was suspended.
/// </summary>
/// <returns>True if successful, false otherwise.</returns>
public async Task<bool> 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<ShortcutResponse>(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 RegisterShortcutRequest(string Name, string Shortcut);
private sealed record ShortcutResponse(bool Success, string ErrorMessage); private sealed record ShortcutResponse(bool Success, string ErrorMessage);

View File

@ -31,6 +31,9 @@ static EVENT_BROADCAST: Lazy<Mutex<Option<broadcast::Sender<Event>>>> = Lazy::ne
/// Stores the currently registered global shortcuts (name -> shortcut string). /// Stores the currently registered global shortcuts (name -> shortcut string).
static REGISTERED_SHORTCUTS: Lazy<Mutex<HashMap<String, String>>> = Lazy::new(|| Mutex::new(HashMap::new())); static REGISTERED_SHORTCUTS: Lazy<Mutex<HashMap<String, String>>> = Lazy::new(|| Mutex::new(HashMap::new()));
/// Flag to temporarily suspend shortcut processing (shortcuts remain registered but events are not sent).
static SHORTCUTS_SUSPENDED: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(false));
/// Starts the Tauri app. /// Starts the Tauri app.
pub fn start_tauri() { pub fn start_tauri() {
info!("Starting Tauri app..."); info!("Starting Tauri app...");
@ -770,6 +773,13 @@ pub fn register_shortcut(_token: APIToken, payload: Json<RegisterShortcutRequest
// Register the new shortcut: // Register the new shortcut:
let shortcut_name = name.clone(); let shortcut_name = name.clone();
match shortcut_manager.register(new_shortcut.as_str(), move || { match shortcut_manager.register(new_shortcut.as_str(), move || {
// Check if shortcuts are suspended:
let is_suspended = *SHORTCUTS_SUSPENDED.lock().unwrap();
if is_suspended {
debug!(Source = "Tauri"; "Global shortcut '{shortcut_name}' triggered but processing is suspended.");
return;
}
info!(Source = "Tauri"; "Global shortcut triggered for '{shortcut_name}'."); info!(Source = "Tauri"; "Global shortcut triggered for '{shortcut_name}'.");
let event = Event::new(TauriEventType::GlobalShortcutPressed, vec![shortcut_name.clone()]); let event = Event::new(TauriEventType::GlobalShortcutPressed, vec![shortcut_name.clone()]);
let sender = event_sender.clone(); let sender = event_sender.clone();
@ -871,6 +881,32 @@ pub fn validate_shortcut(_token: APIToken, payload: Json<ValidateShortcutRequest
} }
} }
/// Suspends shortcut processing. 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.
#[post("/shortcuts/suspend")]
pub fn suspend_shortcuts(_token: APIToken) -> Json<ShortcutResponse> {
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<ShortcutResponse> {
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. /// Validates the syntax of a shortcut string.
fn validate_shortcut_syntax(shortcut: &str) -> bool { fn validate_shortcut_syntax(shortcut: &str) -> bool {
let parts: Vec<&str> = shortcut.split('+').collect(); let parts: Vec<&str> = shortcut.split('+').collect();

View File

@ -89,6 +89,8 @@ pub fn start_runtime_api() {
crate::log::log_event, crate::log::log_event,
crate::app_window::register_shortcut, crate::app_window::register_shortcut,
crate::app_window::validate_shortcut, crate::app_window::validate_shortcut,
crate::app_window::suspend_shortcuts,
crate::app_window::resume_shortcuts,
]) ])
.ignite().await.unwrap() .ignite().await.unwrap()
.launch().await.unwrap(); .launch().await.unwrap();