refactor: improve keybind saving logic and error handling

This commit is contained in:
PandaDEV 2025-01-02 17:05:02 +10:00
parent 6656af8ab1
commit 0c28a5b0db
No known key found for this signature in database
GPG key ID: 13EFF9BAF70EE75C
8 changed files with 478 additions and 154 deletions

View file

@ -1,48 +1,61 @@
use tauri_plugin_aptabase::EventTracker;
use crate::utils::commands::center_window_on_current_monitor;
use crate::utils::keys::KeyCode;
use global_hotkey::{
hotkey::{Code, HotKey, Modifiers},
GlobalHotKeyEvent, GlobalHotKeyManager, HotKeyState,
};
use std::cell::RefCell;
use lazy_static::lazy_static;
use std::str::FromStr;
use std::sync::Mutex;
use tauri::{AppHandle, Listener, Manager};
use tauri_plugin_aptabase::EventTracker;
thread_local! {
static HOTKEY_MANAGER: RefCell<Option<GlobalHotKeyManager>> = RefCell::new(None);
lazy_static! {
static ref HOTKEY_MANAGER: Mutex<Option<GlobalHotKeyManager>> = Mutex::new(None);
static ref REGISTERED_HOTKEYS: Mutex<Vec<HotKey>> = Mutex::new(Vec::new());
}
pub fn setup(app_handle: tauri::AppHandle) {
let app_handle_clone = app_handle.clone();
let manager = GlobalHotKeyManager::new().expect("Failed to initialize hotkey manager");
HOTKEY_MANAGER.with(|m| *m.borrow_mut() = Some(manager));
let manager = match GlobalHotKeyManager::new() {
Ok(manager) => manager,
Err(err) => {
eprintln!("Failed to initialize hotkey manager: {:?}", err);
return;
}
};
{
let mut manager_guard = HOTKEY_MANAGER.lock().unwrap();
*manager_guard = Some(manager);
}
let rt = app_handle.state::<tokio::runtime::Runtime>();
let initial_keybind = rt
.block_on(crate::db::settings::get_keybind(app_handle_clone.clone()))
.expect("Failed to get initial keybind");
let initial_shortcut = initial_keybind.join("+");
let initial_shortcut_for_update = initial_shortcut.clone();
let initial_shortcut_for_save = initial_shortcut.clone();
let initial_shortcut_for_update = initial_keybind.clone();
let initial_shortcut_for_save = initial_keybind.clone();
if let Err(e) = register_shortcut(&initial_shortcut) {
if let Err(e) = register_shortcut(&initial_keybind) {
eprintln!("Error registering initial shortcut: {:?}", e);
}
app_handle.listen("update-shortcut", move |event| {
let payload_str = event.payload().to_string();
let payload_str = event.payload();
if let Ok(old_hotkey) = parse_hotkey(&initial_shortcut_for_update) {
HOTKEY_MANAGER.with(|manager| {
if let Some(manager) = manager.borrow().as_ref() {
let _ = manager.unregister(old_hotkey);
}
});
let manager_guard = HOTKEY_MANAGER.lock().unwrap();
if let Some(manager) = manager_guard.as_ref() {
let _ = manager.unregister(old_hotkey);
}
}
if let Err(e) = register_shortcut(&payload_str) {
let payload: Vec<String> = serde_json::from_str(payload_str).unwrap_or_default();
if let Err(e) = register_shortcut(&payload) {
eprintln!("Error re-registering shortcut: {:?}", e);
}
});
@ -51,14 +64,14 @@ pub fn setup(app_handle: tauri::AppHandle) {
let payload_str = event.payload().to_string();
if let Ok(old_hotkey) = parse_hotkey(&initial_shortcut_for_save) {
HOTKEY_MANAGER.with(|manager| {
if let Some(manager) = manager.borrow().as_ref() {
let _ = manager.unregister(old_hotkey);
}
});
let manager_guard = HOTKEY_MANAGER.lock().unwrap();
if let Some(manager) = manager_guard.as_ref() {
let _ = manager.unregister(old_hotkey);
}
}
if let Err(e) = register_shortcut(&payload_str) {
let payload: Vec<String> = serde_json::from_str(&payload_str).unwrap_or_default();
if let Err(e) = register_shortcut(&payload) {
eprintln!("Error registering saved shortcut: {:?}", e);
}
});
@ -81,48 +94,36 @@ pub fn setup(app_handle: tauri::AppHandle) {
});
}
fn register_shortcut(shortcut: &str) -> Result<(), Box<dyn std::error::Error>> {
fn register_shortcut(shortcut: &[String]) -> Result<(), Box<dyn std::error::Error>> {
let hotkey = parse_hotkey(shortcut)?;
HOTKEY_MANAGER.with(|manager| {
if let Some(manager) = manager.borrow().as_ref() {
manager.register(hotkey)?;
}
let manager_guard = HOTKEY_MANAGER.lock().unwrap();
if let Some(manager) = manager_guard.as_ref() {
manager.register(hotkey.clone())?;
REGISTERED_HOTKEYS.lock().unwrap().push(hotkey);
Ok(())
})
} else {
Err("Hotkey manager not initialized".into())
}
}
fn parse_hotkey(shortcut: &str) -> Result<HotKey, Box<dyn std::error::Error>> {
fn parse_hotkey(shortcut: &[String]) -> Result<HotKey, Box<dyn std::error::Error>> {
let mut modifiers = Modifiers::empty();
let mut code = None;
let shortcut = shortcut.replace("\"", "");
for part in shortcut.split('+') {
let part = part.trim().to_lowercase();
for part in shortcut {
match part.as_str() {
"ctrl" | "control" | "controlleft" => modifiers |= Modifiers::CONTROL,
"alt" | "altleft" | "optionleft" => modifiers |= Modifiers::ALT,
"shift" | "shiftleft" => modifiers |= Modifiers::SHIFT,
"super" | "meta" | "cmd" | "metaleft" => modifiers |= Modifiers::META,
"ControlLeft" => modifiers |= Modifiers::CONTROL,
"AltLeft" => modifiers |= Modifiers::ALT,
"ShiftLeft" => modifiers |= Modifiers::SHIFT,
"MetaLeft" => modifiers |= Modifiers::META,
key => {
let key_code = if key.starts_with("key") {
"Key".to_string() + &key[3..].to_uppercase()
} else if key.len() == 1 && key.chars().next().unwrap().is_alphabetic() {
"Key".to_string() + &key.to_uppercase()
} else {
key.to_string()
};
code = Some(
Code::from_str(&key_code)
.map_err(|_| format!("Invalid key code: {}", key_code))?,
);
code = Some(Code::from(KeyCode::from_str(key)?));
}
}
}
let key_code =
code.ok_or_else(|| format!("No valid key code found in shortcut: {}", shortcut))?;
let key_code = code.ok_or_else(|| "No valid key code found".to_string())?;
Ok(HotKey::new(Some(modifiers), key_code))
}
@ -144,7 +145,10 @@ fn handle_hotkey_event(app_handle: &AppHandle) {
center_window_on_current_monitor(&window);
}
let _ = app_handle.track_event("hotkey_triggered", Some(serde_json::json!({
"action": if window.is_visible().unwrap() { "hide" } else { "show" }
})));
let _ = app_handle.track_event(
"hotkey_triggered",
Some(serde_json::json!({
"action": if window.is_visible().unwrap() { "hide" } else { "show" }
})),
);
}

View file

@ -30,11 +30,8 @@ pub async fn save_keybind(
pool: tauri::State<'_, SqlitePool>,
keybind: Vec<String>,
) -> Result<(), String> {
let keybind_str = keybind.join("+");
let keybind_clone = keybind_str.clone();
app_handle
.emit("update-shortcut", &keybind_str)
.emit("update-shortcut", &keybind)
.map_err(|e| e.to_string())?;
let json = serde_json::to_string(&keybind).map_err(|e| e.to_string())?;
@ -46,7 +43,7 @@ pub async fn save_keybind(
.map_err(|e| e.to_string())?;
let _ = app_handle.track_event("keybind_saved", Some(serde_json::json!({
"keybind": keybind_clone
"keybind": keybind
})));
Ok(())
@ -97,7 +94,7 @@ pub async fn get_keybind(app_handle: tauri::AppHandle) -> Result<Vec<String>, St
.map_err(|e| e.to_string())?;
let json = row.map(|r| r.get::<String, _>("value")).unwrap_or_else(|| {
serde_json::to_string(&vec!["Meta".to_string(), "V".to_string()])
serde_json::to_string(&vec!["MetaLeft".to_string(), "KeyV".to_string()])
.expect("Failed to serialize default keybind")
});

118
src-tauri/src/utils/keys.rs Normal file
View file

@ -0,0 +1,118 @@
use global_hotkey::hotkey::Code;
use std::str::FromStr;
pub struct KeyCode(Code);
impl FromStr for KeyCode {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let code = match s {
"Backquote" => Code::Backquote,
"Backslash" => Code::Backslash,
"BracketLeft" => Code::BracketLeft,
"BracketRight" => Code::BracketRight,
"Comma" => Code::Comma,
"Digit0" => Code::Digit0,
"Digit1" => Code::Digit1,
"Digit2" => Code::Digit2,
"Digit3" => Code::Digit3,
"Digit4" => Code::Digit4,
"Digit5" => Code::Digit5,
"Digit6" => Code::Digit6,
"Digit7" => Code::Digit7,
"Digit8" => Code::Digit8,
"Digit9" => Code::Digit9,
"Equal" => Code::Equal,
"KeyA" => Code::KeyA,
"KeyB" => Code::KeyB,
"KeyC" => Code::KeyC,
"KeyD" => Code::KeyD,
"KeyE" => Code::KeyE,
"KeyF" => Code::KeyF,
"KeyG" => Code::KeyG,
"KeyH" => Code::KeyH,
"KeyI" => Code::KeyI,
"KeyJ" => Code::KeyJ,
"KeyK" => Code::KeyK,
"KeyL" => Code::KeyL,
"KeyM" => Code::KeyM,
"KeyN" => Code::KeyN,
"KeyO" => Code::KeyO,
"KeyP" => Code::KeyP,
"KeyQ" => Code::KeyQ,
"KeyR" => Code::KeyR,
"KeyS" => Code::KeyS,
"KeyT" => Code::KeyT,
"KeyU" => Code::KeyU,
"KeyV" => Code::KeyV,
"KeyW" => Code::KeyW,
"KeyX" => Code::KeyX,
"KeyY" => Code::KeyY,
"KeyZ" => Code::KeyZ,
"Minus" => Code::Minus,
"Period" => Code::Period,
"Quote" => Code::Quote,
"Semicolon" => Code::Semicolon,
"Slash" => Code::Slash,
"Backspace" => Code::Backspace,
"CapsLock" => Code::CapsLock,
"Delete" => Code::Delete,
"Enter" => Code::Enter,
"Space" => Code::Space,
"Tab" => Code::Tab,
"End" => Code::End,
"Home" => Code::Home,
"Insert" => Code::Insert,
"PageDown" => Code::PageDown,
"PageUp" => Code::PageUp,
"ArrowDown" => Code::ArrowDown,
"ArrowLeft" => Code::ArrowLeft,
"ArrowRight" => Code::ArrowRight,
"ArrowUp" => Code::ArrowUp,
"NumLock" => Code::NumLock,
"Numpad0" => Code::Numpad0,
"Numpad1" => Code::Numpad1,
"Numpad2" => Code::Numpad2,
"Numpad3" => Code::Numpad3,
"Numpad4" => Code::Numpad4,
"Numpad5" => Code::Numpad5,
"Numpad6" => Code::Numpad6,
"Numpad7" => Code::Numpad7,
"Numpad8" => Code::Numpad8,
"Numpad9" => Code::Numpad9,
"NumpadAdd" => Code::NumpadAdd,
"NumpadDecimal" => Code::NumpadDecimal,
"NumpadDivide" => Code::NumpadDivide,
"NumpadMultiply" => Code::NumpadMultiply,
"NumpadSubtract" => Code::NumpadSubtract,
"Escape" => Code::Escape,
"PrintScreen" => Code::PrintScreen,
"ScrollLock" => Code::ScrollLock,
"Pause" => Code::Pause,
"AudioVolumeDown" => Code::AudioVolumeDown,
"AudioVolumeMute" => Code::AudioVolumeMute,
"AudioVolumeUp" => Code::AudioVolumeUp,
"F1" => Code::F1,
"F2" => Code::F2,
"F3" => Code::F3,
"F4" => Code::F4,
"F5" => Code::F5,
"F6" => Code::F6,
"F7" => Code::F7,
"F8" => Code::F8,
"F9" => Code::F9,
"F10" => Code::F10,
"F11" => Code::F11,
"F12" => Code::F12,
_ => return Err(format!("Unknown key code: {}", s)),
};
Ok(KeyCode(code))
}
}
impl From<KeyCode> for Code {
fn from(key_code: KeyCode) -> Self {
key_code.0
}
}

View file

@ -2,3 +2,4 @@ pub mod commands;
pub mod favicon;
pub mod types;
pub mod logger;
pub mod keys;