Made the shortcuts type-safe

This commit is contained in:
Thorsten Sommer 2026-01-23 15:31:02 +01:00
parent 9f3ade916d
commit b2882e0d93
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
9 changed files with 141 additions and 61 deletions

View File

@ -1,4 +1,5 @@
using AIStudio.Dialogs; using AIStudio.Dialogs;
using AIStudio.Tools.Rust;
using AIStudio.Tools.Services; using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
@ -33,7 +34,7 @@ public partial class ConfigurationShortcut : ConfigurationBaseCore
/// The name/identifier of the shortcut (used for conflict detection and registration). /// The name/identifier of the shortcut (used for conflict detection and registration).
/// </summary> /// </summary>
[Parameter] [Parameter]
public string ShortcutName { get; set; } = string.Empty; public Shortcut ShortcutId { get; init; }
/// <summary> /// <summary>
/// The icon to display. /// The icon to display.
@ -80,7 +81,7 @@ public partial class ConfigurationShortcut : ConfigurationBaseCore
var dialogParameters = new DialogParameters<ShortcutDialog> var dialogParameters = new DialogParameters<ShortcutDialog>
{ {
{ x => x.InitialShortcut, this.Shortcut() }, { x => x.InitialShortcut, this.Shortcut() },
{ x => x.ShortcutName, this.ShortcutName }, { x => x.ShortcutId, this.ShortcutId },
}; };
var dialogReference = await this.DialogService.ShowAsync<ShortcutDialog>( var dialogReference = await this.DialogService.ShowAsync<ShortcutDialog>(

View File

@ -1,5 +1,6 @@
@using AIStudio.Settings @using AIStudio.Settings
@using AIStudio.Settings.DataModel @using AIStudio.Settings.DataModel
@using AIStudio.Tools.Rust
@inherits SettingsPanelBase @inherits SettingsPanelBase
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Apps" HeaderText="@T("App Options")"> <ExpansionPanel HeaderIcon="@Icons.Material.Filled.Apps" HeaderText="@T("App Options")">
@ -33,6 +34,6 @@
@if (PreviewFeatures.PRE_SPEECH_TO_TEXT_2026.IsEnabled(this.SettingsManager)) @if (PreviewFeatures.PRE_SPEECH_TO_TEXT_2026.IsEnabled(this.SettingsManager))
{ {
<ConfigurationSelect OptionDescription="@T("Select a transcription provider")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider)" Data="@this.GetFilteredTranscriptionProviders()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider = selectedValue)" OptionHelp="@T("Select a transcription provider for transcribing your voice. Without a selected provider, dictation and transcription features will be disabled.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.UseTranscriptionProvider, out var meta) && meta.IsLocked"/> <ConfigurationSelect OptionDescription="@T("Select a transcription provider")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider)" Data="@this.GetFilteredTranscriptionProviders()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider = selectedValue)" OptionHelp="@T("Select a transcription provider for transcribing your voice. Without a selected provider, dictation and transcription features will be disabled.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.UseTranscriptionProvider, out var meta) && meta.IsLocked"/>
<ConfigurationShortcut OptionDescription="@T("Voice recording shortcut")" Shortcut="@(() => this.SettingsManager.ConfigurationData.App.ShortcutVoiceRecording)" ShortcutUpdate="@(shortcut => this.SettingsManager.ConfigurationData.App.ShortcutVoiceRecording = shortcut)" ShortcutName="voice_recording_toggle" OptionHelp="@T("The global keyboard shortcut for toggling voice recording. This shortcut works system-wide, even when the app is not focused.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.ShortcutVoiceRecording, out var meta) && meta.IsLocked"/> <ConfigurationShortcut ShortcutId="Shortcut.VOICE_RECORDING_TOGGLE" OptionDescription="@T("Voice recording shortcut")" Shortcut="@(() => this.SettingsManager.ConfigurationData.App.ShortcutVoiceRecording)" ShortcutUpdate="@(shortcut => this.SettingsManager.ConfigurationData.App.ShortcutVoiceRecording = shortcut)" OptionHelp="@T("The global keyboard shortcut for toggling voice recording. This shortcut works system-wide, even when the app is not focused.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.ShortcutVoiceRecording, out var meta) && meta.IsLocked"/>
} }
</ExpansionPanel> </ExpansionPanel>

View File

@ -47,11 +47,12 @@ public partial class VoiceRecorder : MSGComponentBase
{ {
case Event.TAURI_EVENT_RECEIVED when data is TauriEvent { EventType: TauriEventType.GLOBAL_SHORTCUT_PRESSED } tauriEvent: case Event.TAURI_EVENT_RECEIVED when data is TauriEvent { EventType: TauriEventType.GLOBAL_SHORTCUT_PRESSED } tauriEvent:
// Check if this is the voice recording toggle shortcut: // Check if this is the voice recording toggle shortcut:
if (tauriEvent.Payload.Count > 0 && tauriEvent.Payload[0] == "voice_recording_toggle") if (tauriEvent.TryGetShortcut(out var shortcutId) && shortcutId == Shortcut.VOICE_RECORDING_TOGGLE)
{ {
this.Logger.LogInformation("Global shortcut triggered for voice recording toggle."); this.Logger.LogInformation("Global shortcut triggered for voice recording toggle.");
await this.ToggleRecordingFromShortcut(); await this.ToggleRecordingFromShortcut();
} }
break; break;
} }
} }

View File

@ -1,4 +1,5 @@
using AIStudio.Components; using AIStudio.Components;
using AIStudio.Tools.Rust;
using AIStudio.Tools.Services; using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
@ -24,10 +25,10 @@ public partial class ShortcutDialog : MSGComponentBase
public string InitialShortcut { get; set; } = string.Empty; public string InitialShortcut { get; set; } = string.Empty;
/// <summary> /// <summary>
/// The name/identifier of the shortcut for conflict detection. /// The identifier of the shortcut for conflict detection.
/// </summary> /// </summary>
[Parameter] [Parameter]
public string ShortcutName { get; set; } = string.Empty; public Shortcut ShortcutId { get; set; }
private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new(); private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();

View File

@ -0,0 +1,17 @@
namespace AIStudio.Tools.Rust;
/// <summary>
/// Identifies a global keyboard shortcut.
/// </summary>
public enum Shortcut
{
/// <summary>
/// Null pattern - no shortcut assigned or unknown shortcut.
/// </summary>
NONE = 0,
/// <summary>
/// Toggles voice recording on/off.
/// </summary>
VOICE_RECORDING_TOGGLE,
}

View File

@ -5,4 +5,38 @@ namespace AIStudio.Tools.Rust;
/// </summary> /// </summary>
/// <param name="EventType">The type of the Tauri event.</param> /// <param name="EventType">The type of the Tauri event.</param>
/// <param name="Payload">The payload of the Tauri event.</param> /// <param name="Payload">The payload of the Tauri event.</param>
public readonly record struct TauriEvent(TauriEventType EventType, List<string> Payload); public readonly record struct TauriEvent(TauriEventType EventType, List<string> Payload)
{
/// <summary>
/// Attempts to parse the first payload element as a shortcut.
/// </summary>
/// <param name="shortcut">The parsed shortcut name if successful.</param>
/// <returns>True if parsing was successful, false otherwise.</returns>
public bool TryGetShortcut(out Shortcut shortcut)
{
shortcut = default;
if (this.Payload.Count == 0)
return false;
// Try standard enum parsing (handles PascalCase and numeric values):
if (Enum.TryParse(this.Payload[0], ignoreCase: true, out shortcut))
return true;
// Try parsing snake_case format (e.g., "voice_recording_toggle"):
return TryParseSnakeCase(this.Payload[0], out shortcut);
}
/// <summary>
/// Tries to parse a snake_case string into a ShortcutName enum value.
/// </summary>
private static bool TryParseSnakeCase(string value, out Shortcut shortcut)
{
shortcut = default;
// Convert snake_case to UPPER_SNAKE_CASE for enum matching:
var upperSnakeCase = value.ToUpperInvariant();
// Try to match against enum names (which are in UPPER_SNAKE_CASE):
return Enum.TryParse(upperSnakeCase, ignoreCase: false, out shortcut);
}
};

View File

@ -1,5 +1,6 @@
using AIStudio.Settings; using AIStudio.Settings;
using AIStudio.Settings.DataModel; using AIStudio.Settings.DataModel;
using AIStudio.Tools.Rust;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
@ -57,44 +58,51 @@ public sealed class GlobalShortcutService : BackgroundService, IMessageBusReceiv
} }
} }
public Task<TResult?> ProcessMessageWithResult<TPayload, TResult>( public Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) => Task.FromResult<TResult?>(default);
ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
=> Task.FromResult<TResult?>(default);
#endregion #endregion
private async Task RegisterAllShortcuts() private async Task RegisterAllShortcuts()
{ {
this.logger.LogInformation("Registering global shortcuts."); this.logger.LogInformation("Registering global shortcuts.");
foreach (var shortcutId in Enum.GetValues<Shortcut>())
{
var shortcut = this.GetShortcutValue(shortcutId);
var isEnabled = this.IsShortcutAllowed(shortcutId);
// if (isEnabled && !string.IsNullOrWhiteSpace(shortcut))
// Voice recording shortcut (preview feature)
//
if (PreviewFeatures.PRE_SPEECH_TO_TEXT_2026.IsEnabled(this.settingsManager))
{ {
var shortcut = this.settingsManager.ConfigurationData.App.ShortcutVoiceRecording; var success = await this.rustService.UpdateGlobalShortcut(shortcutId, shortcut);
if (!string.IsNullOrWhiteSpace(shortcut))
{
var success = await this.rustService.UpdateGlobalShortcut("voice_recording_toggle", shortcut);
if (success) if (success)
this.logger.LogInformation("Global shortcut 'voice_recording_toggle' ({Shortcut}) registered.", shortcut); this.logger.LogInformation("Global shortcut '{ShortcutId}' ({Shortcut}) registered.", shortcutId, shortcut);
else else
this.logger.LogWarning("Failed to register global shortcut 'voice_recording_toggle' ({Shortcut}).", shortcut); this.logger.LogWarning("Failed to register global shortcut '{ShortcutId}' ({Shortcut}).", shortcutId, shortcut);
} }
else else
{ {
// Disable shortcut when empty // Disable the shortcut when empty or feature is disabled:
await this.rustService.UpdateGlobalShortcut("voice_recording_toggle", string.Empty); await this.rustService.UpdateGlobalShortcut(shortcutId, string.Empty);
} }
} }
else
{
// Disable the shortcut when the preview feature is disabled:
await this.rustService.UpdateGlobalShortcut("voice_recording_toggle", string.Empty);
}
this.logger.LogInformation("Global shortcuts registration completed."); this.logger.LogInformation("Global shortcuts registration completed.");
} }
private string GetShortcutValue(Shortcut name) => name switch
{
Shortcut.VOICE_RECORDING_TOGGLE => this.settingsManager.ConfigurationData.App.ShortcutVoiceRecording,
_ => string.Empty,
};
private bool IsShortcutAllowed(Shortcut name) => name switch
{
// Voice recording is a preview feature:
Shortcut.VOICE_RECORDING_TOGGLE => PreviewFeatures.PRE_SPEECH_TO_TEXT_2026.IsEnabled(this.settingsManager),
// Other shortcuts are always allowed:
_ => true,
};
public static void Initialize() => IS_INITIALIZED = true; public static void Initialize() => IS_INITIALIZED = true;
} }

View File

@ -1,4 +1,6 @@
// ReSharper disable NotAccessedPositionalProperty.Local // ReSharper disable NotAccessedPositionalProperty.Local
using AIStudio.Tools.Rust;
namespace AIStudio.Tools.Services; namespace AIStudio.Tools.Services;
public sealed partial class RustService public sealed partial class RustService
@ -6,35 +8,35 @@ public sealed partial class RustService
/// <summary> /// <summary>
/// Registers or updates a global keyboard shortcut. /// Registers or updates a global keyboard shortcut.
/// </summary> /// </summary>
/// <param name="name">The name/identifier for the shortcut (e.g., "voice_recording_toggle").</param> /// <param name="shortcutId">The identifier for the shortcut.</param>
/// <param name="shortcut">The shortcut string in Tauri format (e.g., "CmdOrControl+1"). Use empty string to disable.</param> /// <param name="shortcut">The shortcut string in Tauri format (e.g., "CmdOrControl+1"). Use empty string to disable.</param>
/// <returns>True if the shortcut was registered successfully, false otherwise.</returns> /// <returns>True if the shortcut was registered successfully, false otherwise.</returns>
public async Task<bool> UpdateGlobalShortcut(string name, string shortcut) public async Task<bool> UpdateGlobalShortcut(Shortcut shortcutId, string shortcut)
{ {
try try
{ {
var request = new RegisterShortcutRequest(name, shortcut); var request = new RegisterShortcutRequest(shortcutId, shortcut);
var response = await this.http.PostAsJsonAsync("/shortcuts/register", request, this.jsonRustSerializerOptions); var response = await this.http.PostAsJsonAsync("/shortcuts/register", request, this.jsonRustSerializerOptions);
if (!response.IsSuccessStatusCode) if (!response.IsSuccessStatusCode)
{ {
this.logger?.LogError("Failed to register global shortcut '{Name}' due to network error: {StatusCode}", name, response.StatusCode); this.logger?.LogError("Failed to register global shortcut '{ShortcutId}' due to network error: {StatusCode}", shortcutId, response.StatusCode);
return false; return false;
} }
var result = await response.Content.ReadFromJsonAsync<ShortcutResponse>(this.jsonRustSerializerOptions); var result = await response.Content.ReadFromJsonAsync<ShortcutResponse>(this.jsonRustSerializerOptions);
if (result is null || !result.Success) if (result is null || !result.Success)
{ {
this.logger?.LogError("Failed to register global shortcut '{Name}': {Error}", name, result?.ErrorMessage ?? "Unknown error"); this.logger?.LogError("Failed to register global shortcut '{ShortcutId}': {Error}", shortcutId, result?.ErrorMessage ?? "Unknown error");
return false; return false;
} }
this.logger?.LogInformation("Global shortcut '{Name}' registered successfully with key '{Shortcut}'.", name, shortcut); this.logger?.LogInformation("Global shortcut '{ShortcutId}' registered successfully with key '{Shortcut}'.", shortcutId, shortcut);
return true; return true;
} }
catch (Exception ex) catch (Exception ex)
{ {
this.logger?.LogError(ex, "Exception while registering global shortcut '{Name}'.", name); this.logger?.LogError(ex, "Exception while registering global shortcut '{ShortcutId}'.", shortcutId);
return false; return false;
} }
} }
@ -136,7 +138,7 @@ public sealed partial class RustService
} }
} }
private sealed record RegisterShortcutRequest(string Name, string Shortcut); private sealed record RegisterShortcutRequest(Shortcut ShortcutId, string Shortcut);
private sealed record ShortcutResponse(bool Success, string ErrorMessage); private sealed record ShortcutResponse(bool Success, string ErrorMessage);
@ -151,5 +153,5 @@ public sealed partial class RustService
/// <param name="IsValid">Whether the shortcut syntax is valid.</param> /// <param name="IsValid">Whether the shortcut syntax is valid.</param>
/// <param name="ErrorMessage">Error message if not valid.</param> /// <param name="ErrorMessage">Error message if not valid.</param>
/// <param name="HasConflict">Whether the shortcut conflicts with another registered shortcut.</param> /// <param name="HasConflict">Whether the shortcut conflicts with another registered shortcut.</param>
/// <param name="ConflictDescription">Description of the conflict if any.</param> /// <param name="ConflictDescription">Description of the conflict, if any.</param>
public sealed record ShortcutValidationResult(bool IsValid, string ErrorMessage, bool HasConflict, string ConflictDescription); public sealed record ShortcutValidationResult(bool IsValid, string ErrorMessage, bool HasConflict, string ConflictDescription);

View File

@ -29,7 +29,24 @@ static CHECK_UPDATE_RESPONSE: Lazy<Mutex<Option<UpdateResponse<tauri::Wry>>>> =
static EVENT_BROADCAST: Lazy<Mutex<Option<broadcast::Sender<Event>>>> = Lazy::new(|| Mutex::new(None)); static EVENT_BROADCAST: Lazy<Mutex<Option<broadcast::Sender<Event>>>> = Lazy::new(|| Mutex::new(None));
/// 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<Shortcut, String>>> = Lazy::new(|| Mutex::new(HashMap::new()));
/// Enum identifying global keyboard shortcuts.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Shortcut {
None = 0,
VoiceRecordingToggle,
}
impl Shortcut {
/// Returns the display name for logging.
pub fn display_name(&self) -> &'static str {
match self {
Shortcut::None => "none",
Shortcut::VoiceRecordingToggle => "voice_recording_toggle",
}
}
}
/// Starts the Tauri app. /// Starts the Tauri app.
pub fn start_tauri() { pub fn start_tauri() {
@ -686,8 +703,8 @@ pub struct FileSaveResponse {
/// Request payload for registering a global shortcut. /// Request payload for registering a global shortcut.
#[derive(Clone, Deserialize)] #[derive(Clone, Deserialize)]
pub struct RegisterShortcutRequest { pub struct RegisterShortcutRequest {
/// The name/identifier for the shortcut (e.g., "voice_recording_toggle"). /// The shortcut ID to use.
name: String, id: Shortcut,
/// The shortcut string in Tauri format (e.g., "CmdOrControl+1"). /// The shortcut string in Tauri format (e.g., "CmdOrControl+1").
/// Use empty string to unregister the shortcut. /// Use empty string to unregister the shortcut.
@ -707,17 +724,15 @@ pub struct ShortcutResponse {
fn register_shortcut_with_callback( fn register_shortcut_with_callback(
shortcut_manager: &mut impl tauri::GlobalShortcutManager, shortcut_manager: &mut impl tauri::GlobalShortcutManager,
shortcut: &str, shortcut: &str,
name: &str, shortcut_id: Shortcut,
event_sender: broadcast::Sender<Event>, event_sender: broadcast::Sender<Event>,
) -> Result<(), tauri::Error> { ) -> Result<(), tauri::Error> {
let shortcut_name = name.to_string();
// //
// Match the shortcut registration to transform the Tauri result into the Rust result: // Match the shortcut registration to transform the Tauri result into the Rust result:
// //
match shortcut_manager.register(shortcut, move || { match shortcut_manager.register(shortcut, move || {
info!(Source = "Tauri"; "Global shortcut triggered for '{shortcut_name}'."); info!(Source = "Tauri"; "Global shortcut triggered for '{}'.", shortcut_id.display_name());
let event = Event::new(TauriEventType::GlobalShortcutPressed, vec![shortcut_name.clone()]); let event = Event::new(TauriEventType::GlobalShortcutPressed, vec![shortcut_id.to_string()]);
let sender = event_sender.clone(); let sender = event_sender.clone();
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
match sender.send(event) { match sender.send(event) {
@ -735,10 +750,10 @@ fn register_shortcut_with_callback(
/// the existing shortcut for that name will be unregistered. /// the existing shortcut for that name will be unregistered.
#[post("/shortcuts/register", data = "<payload>")] #[post("/shortcuts/register", data = "<payload>")]
pub fn register_shortcut(_token: APIToken, payload: Json<RegisterShortcutRequest>) -> Json<ShortcutResponse> { pub fn register_shortcut(_token: APIToken, payload: Json<RegisterShortcutRequest>) -> Json<ShortcutResponse> {
let name = payload.name.clone(); let id = payload.id;
let new_shortcut = payload.shortcut.clone(); let new_shortcut = payload.shortcut.clone();
info!(Source = "Tauri"; "Registering global shortcut '{name}' with key '{new_shortcut}'."); info!(Source = "Tauri"; "Registering global shortcut '{}' with key '{new_shortcut}'.", id.display_name());
// Get the main window to access the global shortcut manager: // Get the main window to access the global shortcut manager:
let main_window_lock = MAIN_WINDOW.lock().unwrap(); let main_window_lock = MAIN_WINDOW.lock().unwrap();
@ -757,10 +772,10 @@ pub fn register_shortcut(_token: APIToken, payload: Json<RegisterShortcutRequest
let mut registered_shortcuts = REGISTERED_SHORTCUTS.lock().unwrap(); let mut registered_shortcuts = REGISTERED_SHORTCUTS.lock().unwrap();
// Unregister the old shortcut if one exists for this name: // Unregister the old shortcut if one exists for this name:
if let Some(old_shortcut) = registered_shortcuts.get(&name) { if let Some(old_shortcut) = registered_shortcuts.get(&id) {
if !old_shortcut.is_empty() { if !old_shortcut.is_empty() {
match shortcut_manager.unregister(old_shortcut.as_str()) { match shortcut_manager.unregister(old_shortcut.as_str()) {
Ok(_) => info!(Source = "Tauri"; "Unregistered old shortcut '{old_shortcut}' for '{name}'."), Ok(_) => info!(Source = "Tauri"; "Unregistered old shortcut '{old_shortcut}' for '{}'.", id.display_name()),
Err(error) => warn!(Source = "Tauri"; "Failed to unregister old shortcut '{old_shortcut}': {error}"), Err(error) => warn!(Source = "Tauri"; "Failed to unregister old shortcut '{old_shortcut}': {error}"),
} }
} }
@ -768,8 +783,8 @@ pub fn register_shortcut(_token: APIToken, payload: Json<RegisterShortcutRequest
// When the new shortcut is empty, we're done (just unregistering): // When the new shortcut is empty, we're done (just unregistering):
if new_shortcut.is_empty() { if new_shortcut.is_empty() {
registered_shortcuts.remove(&name); registered_shortcuts.remove(&id);
info!(Source = "Tauri"; "Shortcut '{name}' has been disabled."); info!(Source = "Tauri"; "Shortcut '{}' has been disabled.", id.display_name());
return Json(ShortcutResponse { return Json(ShortcutResponse {
success: true, success: true,
error_message: String::new(), error_message: String::new(),
@ -792,10 +807,10 @@ pub fn register_shortcut(_token: APIToken, payload: Json<RegisterShortcutRequest
drop(event_broadcast_lock); drop(event_broadcast_lock);
// Register the new shortcut: // Register the new shortcut:
match register_shortcut_with_callback(&mut shortcut_manager, &new_shortcut, &name, event_sender) { match register_shortcut_with_callback(&mut shortcut_manager, &new_shortcut, id, event_sender) {
Ok(_) => { Ok(_) => {
info!(Source = "Tauri"; "Global shortcut '{new_shortcut}' registered successfully for '{name}'."); info!(Source = "Tauri"; "Global shortcut '{new_shortcut}' registered successfully for '{}'.", id.display_name());
registered_shortcuts.insert(name, new_shortcut); registered_shortcuts.insert(id, new_shortcut);
Json(ShortcutResponse { Json(ShortcutResponse {
success: true, success: true,
error_message: String::new(), error_message: String::new(),
@ -854,7 +869,7 @@ pub fn validate_shortcut(_token: APIToken, payload: Json<ValidateShortcutRequest
is_valid: true, is_valid: true,
error_message: String::new(), error_message: String::new(),
has_conflict: true, has_conflict: true,
conflict_description: format!("Already used by: {}", name), conflict_description: format!("Already used by: {}", name.display_name()),
}); });
} }
} }
@ -909,8 +924,8 @@ pub fn suspend_shortcuts(_token: APIToken) -> Json<ShortcutResponse> {
for (name, shortcut) in registered_shortcuts.iter() { for (name, shortcut) in registered_shortcuts.iter() {
if !shortcut.is_empty() { if !shortcut.is_empty() {
match shortcut_manager.unregister(shortcut.as_str()) { match shortcut_manager.unregister(shortcut.as_str()) {
Ok(_) => info!(Source = "Tauri"; "Temporarily unregistered shortcut '{shortcut}' for '{name}'."), Ok(_) => info!(Source = "Tauri"; "Temporarily unregistered shortcut '{shortcut}' for '{}'.", name.display_name()),
Err(error) => warn!(Source = "Tauri"; "Failed to unregister shortcut '{shortcut}' for '{name}': {error}"), Err(error) => warn!(Source = "Tauri"; "Failed to unregister shortcut '{shortcut}' for '{}': {error}", name.display_name()),
} }
} }
} }
@ -958,18 +973,18 @@ pub fn resume_shortcuts(_token: APIToken) -> Json<ShortcutResponse> {
// Re-register all shortcuts with the OS: // Re-register all shortcuts with the OS:
let mut success_count = 0; let mut success_count = 0;
for (name, shortcut) in registered_shortcuts.iter() { for (shortcut_id, shortcut) in registered_shortcuts.iter() {
if shortcut.is_empty() { if shortcut.is_empty() {
continue; continue;
} }
match register_shortcut_with_callback(&mut shortcut_manager, shortcut, name, event_sender.clone()) { match register_shortcut_with_callback(&mut shortcut_manager, shortcut, *shortcut_id, event_sender.clone()) {
Ok(_) => { Ok(_) => {
info!(Source = "Tauri"; "Re-registered shortcut '{shortcut}' for '{name}'."); info!(Source = "Tauri"; "Re-registered shortcut '{shortcut}' for '{}'.", shortcut_id.display_name());
success_count += 1; success_count += 1;
}, },
Err(error) => warn!(Source = "Tauri"; "Failed to re-register shortcut '{shortcut}' for '{name}': {error}"), Err(error) => warn!(Source = "Tauri"; "Failed to re-register shortcut '{shortcut}' for '{}': {error}", shortcut_id.display_name()),
} }
} }