diff --git a/src-tauri/src/hotkey.rs b/src-tauri/src/hotkey.rs index d42a02bbd8..9d82f15427 100644 --- a/src-tauri/src/hotkey.rs +++ b/src-tauri/src/hotkey.rs @@ -4,24 +4,37 @@ use crate::APP; use log::{info, warn}; use tauri::{AppHandle, GlobalShortcutManager}; +fn get_hotkey_from_store(name: &str) -> String { + match get(name) { + Some(v) => v.as_str().unwrap_or_default().to_string(), + None => { + set(name, ""); + String::new() + } + } +} + fn register(app_handle: &AppHandle, name: &str, handler: F, key: &str) -> Result<(), String> where F: Fn() + Send + 'static, { - let hotkey = { - if key.is_empty() { - match get(name) { - Some(v) => v.as_str().unwrap().to_string(), - None => { - set(name, ""); - String::new() - } - } - } else { - key.to_string() - } + let hotkey = if key.is_empty() { + get_hotkey_from_store(name) + } else { + key.to_string() }; + let old_hotkey = get_hotkey_from_store(name); + if !old_hotkey.is_empty() { + // Always try to unregister old key first to avoid depending on frontend order. + if let Err(e) = app_handle.global_shortcut_manager().unregister(&old_hotkey) { + warn!( + "Failed to unregister old shortcut: {} for {} {:?}", + old_hotkey, name, e + ); + } + } + if !hotkey.is_empty() { match app_handle .global_shortcut_manager() @@ -35,7 +48,12 @@ where return Err(e.to_string()); } }; + } else { + info!("Cleared global shortcut for {}", name); } + + // Persist only when backend registration flow succeeds. + set(name, hotkey); Ok(()) } @@ -43,27 +61,42 @@ where pub fn register_shortcut(shortcut: &str) -> Result<(), String> { let app_handle = APP.get().unwrap(); match shortcut { - "hotkey_selection_translate" => register( - app_handle, - "hotkey_selection_translate", - selection_translate, - "", - )?, + "hotkey_selection_translate" => { + register(app_handle, "hotkey_selection_translate", selection_translate, "")? + } "hotkey_input_translate" => { register(app_handle, "hotkey_input_translate", input_translate, "")? } - "hotkey_ocr_recognize" => register(app_handle, "hotkey_ocr_recognize", ocr_recognize, "")?, - "hotkey_ocr_translate" => register(app_handle, "hotkey_ocr_translate", ocr_translate, "")?, + "hotkey_ocr_recognize" => { + register(app_handle, "hotkey_ocr_recognize", ocr_recognize, "")? + } + "hotkey_ocr_translate" => { + register(app_handle, "hotkey_ocr_translate", ocr_translate, "")? + } "all" => { - register( + let mut errors = vec![]; + + if let Err(e) = register( app_handle, "hotkey_selection_translate", selection_translate, "", - )?; - register(app_handle, "hotkey_input_translate", input_translate, "")?; - register(app_handle, "hotkey_ocr_recognize", ocr_recognize, "")?; - register(app_handle, "hotkey_ocr_translate", ocr_translate, "")?; + ) { + errors.push(format!("hotkey_selection_translate: {}", e)); + } + if let Err(e) = register(app_handle, "hotkey_input_translate", input_translate, "") { + errors.push(format!("hotkey_input_translate: {}", e)); + } + if let Err(e) = register(app_handle, "hotkey_ocr_recognize", ocr_recognize, "") { + errors.push(format!("hotkey_ocr_recognize: {}", e)); + } + if let Err(e) = register(app_handle, "hotkey_ocr_translate", ocr_translate, "") { + errors.push(format!("hotkey_ocr_translate: {}", e)); + } + + if !errors.is_empty() { + return Err(errors.join("\n")); + } } _ => {} } @@ -92,7 +125,7 @@ pub fn register_shortcut_by_frontend(name: &str, shortcut: &str) -> Result<(), S "hotkey_ocr_translate" => { register(app_handle, "hotkey_ocr_translate", ocr_translate, shortcut)? } - _ => {} + _ => return Err(format!("Unknown hotkey name: {}", name)), } Ok(()) } diff --git a/src/window/Config/pages/Hotkey/index.jsx b/src/window/Config/pages/Hotkey/index.jsx index a917cedcf4..ede6a7e74c 100644 --- a/src/window/Config/pages/Hotkey/index.jsx +++ b/src/window/Config/pages/Hotkey/index.jsx @@ -1,4 +1,3 @@ -import { unregister, isRegistered } from '@tauri-apps/api/globalShortcut'; import toast, { Toaster } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; import { CardBody } from '@nextui-org/react'; @@ -46,10 +45,10 @@ const keyMap = { }; export default function Hotkey() { - const [selectionTranslate, setSelectionTranslate] = useConfig('hotkey_selection_translate', ''); - const [inputTranslate, setInputTranslate] = useConfig('hotkey_input_translate', ''); - const [ocrRecognize, setOcrRecognize] = useConfig('hotkey_ocr_recognize', ''); - const [ocrTranslate, setOcrTranslate] = useConfig('hotkey_ocr_translate', ''); + const [selectionTranslate, setSelectionTranslate] = useConfig('hotkey_selection_translate', '', { sync: false }); + const [inputTranslate, setInputTranslate] = useConfig('hotkey_input_translate', '', { sync: false }); + const [ocrRecognize, setOcrRecognize] = useConfig('hotkey_ocr_recognize', '', { sync: false }); + const [ocrTranslate, setOcrTranslate] = useConfig('hotkey_ocr_translate', '', { sync: false }); const { t } = useTranslation(); const toastStyle = useToastStyle(); @@ -93,24 +92,19 @@ export default function Hotkey() { } } - function registerHandler(name, key) { - isRegistered(key).then((res) => { - if (res) { - toast.error(t('config.hotkey.is_register'), { style: toastStyle }); - } else { - invoke('register_shortcut_by_frontend', { - name: name, - shortcut: key, - }).then( - () => { - toast.success(t('config.hotkey.success'), { style: toastStyle }); - }, - (e) => { - toast.error(e, { style: toastStyle }); - } - ); + function registerHandler(name, key, setKey) { + invoke('register_shortcut_by_frontend', { + name: name, + shortcut: key, + }).then( + () => { + setKey(key, true); + toast.success(t('config.hotkey.success'), { style: toastStyle }); + }, + (e) => { + toast.error(e, { style: toastStyle }); } - }); + ); } return ( @@ -129,17 +123,16 @@ export default function Hotkey() { onKeyDown={(e) => { keyDown(e, setSelectionTranslate); }} - onFocus={() => { - unregister(selectionTranslate); - setSelectionTranslate(''); - }} endContent={