198 lines
6.7 KiB
Rust
198 lines
6.7 KiB
Rust
//! Tauri global-shortcut implementation
|
|
//!
|
|
//! This module provides shortcut functionality using Tauri's built-in
|
|
//! global-shortcut plugin.
|
|
|
|
use log::{error, warn};
|
|
use tauri::AppHandle;
|
|
use tauri_plugin_global_shortcut::{GlobalShortcutExt, Shortcut, ShortcutState};
|
|
|
|
#[cfg(not(target_os = "linux"))]
|
|
use crate::settings::get_settings;
|
|
use crate::settings::{self, ShortcutBinding};
|
|
|
|
use super::handler::handle_shortcut_event;
|
|
|
|
/// Initialize shortcuts using Tauri's global-shortcut plugin
|
|
pub fn init_shortcuts(app: &AppHandle) {
|
|
let default_bindings = settings::get_default_settings().bindings;
|
|
let user_settings = settings::load_or_create_app_settings(app);
|
|
|
|
// Register all default shortcuts, applying user customizations
|
|
for (id, default_binding) in default_bindings {
|
|
if id == "cancel" {
|
|
continue; // Skip cancel shortcut, it will be registered dynamically
|
|
}
|
|
// Skip post-processing shortcut when the feature is disabled
|
|
if id == "transcribe_with_post_process" && !user_settings.post_process_enabled {
|
|
continue;
|
|
}
|
|
let binding = user_settings
|
|
.bindings
|
|
.get(&id)
|
|
.cloned()
|
|
.unwrap_or(default_binding);
|
|
|
|
if let Err(e) = register_shortcut(app, binding) {
|
|
error!("Failed to register shortcut {} during init: {}", id, e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Validate a shortcut string for the Tauri global-shortcut implementation.
|
|
/// Tauri requires at least one non-modifier key and doesn't support the fn key.
|
|
pub fn validate_shortcut(raw: &str) -> Result<(), String> {
|
|
if raw.trim().is_empty() {
|
|
return Err("Shortcut cannot be empty".into());
|
|
}
|
|
|
|
let modifiers = [
|
|
"ctrl", "control", "shift", "alt", "option", "meta", "command", "cmd", "super", "win",
|
|
"windows",
|
|
];
|
|
|
|
// Check for fn key which Tauri doesn't support
|
|
let parts: Vec<String> = raw.split('+').map(|p| p.trim().to_lowercase()).collect();
|
|
for part in &parts {
|
|
if part == "fn" || part == "function" {
|
|
return Err("The 'fn' key is not supported by Tauri global shortcuts".into());
|
|
}
|
|
}
|
|
|
|
// Check for at least one non-modifier key
|
|
let has_non_modifier = parts.iter().any(|part| !modifiers.contains(&part.as_str()));
|
|
|
|
if has_non_modifier {
|
|
Ok(())
|
|
} else {
|
|
Err("Tauri shortcuts must include a main key (letter, number, F-key, etc.) in addition to modifiers".into())
|
|
}
|
|
}
|
|
|
|
/// Register a shortcut using Tauri's global-shortcut plugin
|
|
pub fn register_shortcut(app: &AppHandle, binding: ShortcutBinding) -> Result<(), String> {
|
|
// Validate for Tauri requirements
|
|
if let Err(e) = validate_shortcut(&binding.current_binding) {
|
|
warn!(
|
|
"register_tauri_shortcut validation error for binding '{}': {}",
|
|
binding.current_binding, e
|
|
);
|
|
return Err(e);
|
|
}
|
|
|
|
// Parse shortcut and return error if it fails
|
|
let shortcut = match binding.current_binding.parse::<Shortcut>() {
|
|
Ok(s) => s,
|
|
Err(e) => {
|
|
let error_msg = format!(
|
|
"Failed to parse shortcut '{}': {}",
|
|
binding.current_binding, e
|
|
);
|
|
error!("register_tauri_shortcut parse error: {}", error_msg);
|
|
return Err(error_msg);
|
|
}
|
|
};
|
|
|
|
// Prevent duplicate registrations that would silently shadow one another
|
|
if app.global_shortcut().is_registered(shortcut) {
|
|
let error_msg = format!("Shortcut '{}' is already in use", binding.current_binding);
|
|
warn!("register_tauri_shortcut duplicate error: {}", error_msg);
|
|
return Err(error_msg);
|
|
}
|
|
|
|
// Clone binding.id for use in the closure
|
|
let binding_id_for_closure = binding.id.clone();
|
|
|
|
app.global_shortcut()
|
|
.on_shortcut(shortcut, move |app_handle, scut, event| {
|
|
if scut == &shortcut {
|
|
let shortcut_string = scut.into_string();
|
|
let is_pressed = event.state == ShortcutState::Pressed;
|
|
handle_shortcut_event(
|
|
app_handle,
|
|
&binding_id_for_closure,
|
|
&shortcut_string,
|
|
is_pressed,
|
|
);
|
|
}
|
|
})
|
|
.map_err(|e| {
|
|
let error_msg = format!(
|
|
"Couldn't register shortcut '{}': {}",
|
|
binding.current_binding, e
|
|
);
|
|
error!("register_tauri_shortcut registration error: {}", error_msg);
|
|
error_msg
|
|
})?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Unregister a shortcut from Tauri's global-shortcut plugin
|
|
pub fn unregister_shortcut(app: &AppHandle, binding: ShortcutBinding) -> Result<(), String> {
|
|
let shortcut = match binding.current_binding.parse::<Shortcut>() {
|
|
Ok(s) => s,
|
|
Err(e) => {
|
|
let error_msg = format!(
|
|
"Failed to parse shortcut '{}' for unregistration: {}",
|
|
binding.current_binding, e
|
|
);
|
|
error!("unregister_tauri_shortcut parse error: {}", error_msg);
|
|
return Err(error_msg);
|
|
}
|
|
};
|
|
|
|
app.global_shortcut().unregister(shortcut).map_err(|e| {
|
|
let error_msg = format!(
|
|
"Failed to unregister shortcut '{}': {}",
|
|
binding.current_binding, e
|
|
);
|
|
error!("unregister_tauri_shortcut error: {}", error_msg);
|
|
error_msg
|
|
})?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Register the cancel shortcut (called when recording starts)
|
|
pub fn register_cancel_shortcut(app: &AppHandle) {
|
|
// Cancel shortcut is disabled on Linux due to instability with dynamic shortcut registration
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
let _ = app;
|
|
return;
|
|
}
|
|
|
|
#[cfg(not(target_os = "linux"))]
|
|
{
|
|
let app_clone = app.clone();
|
|
tauri::async_runtime::spawn(async move {
|
|
if let Some(cancel_binding) = get_settings(&app_clone).bindings.get("cancel").cloned() {
|
|
if let Err(e) = register_shortcut(&app_clone, cancel_binding) {
|
|
error!("Failed to register cancel shortcut: {}", e);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Unregister the cancel shortcut (called when recording stops)
|
|
pub fn unregister_cancel_shortcut(app: &AppHandle) {
|
|
// Cancel shortcut is disabled on Linux due to instability with dynamic shortcut registration
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
let _ = app;
|
|
return;
|
|
}
|
|
|
|
#[cfg(not(target_os = "linux"))]
|
|
{
|
|
let app_clone = app.clone();
|
|
tauri::async_runtime::spawn(async move {
|
|
if let Some(cancel_binding) = get_settings(&app_clone).bindings.get("cancel").cloned() {
|
|
// We ignore errors here as it might already be unregistered
|
|
let _ = unregister_shortcut(&app_clone, cancel_binding);
|
|
}
|
|
});
|
|
}
|
|
}
|