From dc846f60e6d7d3ab1d3b6a1a973b2c482ed20a1a Mon Sep 17 00:00:00 2001 From: H-Chris233 Date: Sun, 31 May 2026 23:14:48 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20MediaPlayPause=20H?= =?UTF-8?q?otkeyTrigger=20=E6=94=AF=E6=8C=81=E6=9C=89=E7=BA=BF=E8=80=B3?= =?UTF-8?q?=E6=9C=BA=E7=BA=BF=E6=8E=A7=E5=BD=95=E9=9F=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 枚举新增 MediaPlayPause 变体,与 RightControl 等并列 - Windows WH_KEYBOARD_LL 钩子捕获 VK_MEDIA_PLAY_PAUSE (0xB3) - macOS/Linux 标记为 unreachable (Windows-only) - 前端类型、i18n 翻译支持 - 配置迁移保护:Linux fcitx 函数添加 MediaPlayPause 守卫 Closes #477 --- openless-all/app/src-tauri/src/coordinator.rs | 2 ++ openless-all/app/src-tauri/src/hotkey.rs | 4 ++++ openless-all/app/src-tauri/src/linux_fcitx.rs | 12 +++++++++++- openless-all/app/src-tauri/src/shortcut_binding.rs | 2 ++ openless-all/app/src-tauri/src/types.rs | 4 ++++ openless-all/app/src/i18n/en.ts | 1 + openless-all/app/src/i18n/ja.ts | 1 + openless-all/app/src/i18n/ko.ts | 1 + openless-all/app/src/i18n/zh-CN.ts | 1 + openless-all/app/src/i18n/zh-TW.ts | 1 + openless-all/app/src/lib/hotkey.ts | 3 +++ openless-all/app/src/lib/types.ts | 1 + 12 files changed, 32 insertions(+), 1 deletion(-) diff --git a/openless-all/app/src-tauri/src/coordinator.rs b/openless-all/app/src-tauri/src/coordinator.rs index a8c96683..12ce3cf0 100644 --- a/openless-all/app/src-tauri/src/coordinator.rs +++ b/openless-all/app/src-tauri/src/coordinator.rs @@ -1963,6 +1963,8 @@ fn window_key_matches_trigger(trigger: crate::types::HotkeyTrigger, key: &str, c HotkeyTrigger::LeftOption => (key == "Alt" || key == "AltGraph") && code == "AltLeft", HotkeyTrigger::RightCommand => key == "Meta" && code == "MetaRight", HotkeyTrigger::Fn => key == "Control" && code == "ControlRight", + // MediaPlayPause 走 WH_KEYBOARD_LL,不走 window hotkey fallback + HotkeyTrigger::MediaPlayPause => false, // Custom 走 global-hotkey crate,不走 window hotkey fallback HotkeyTrigger::Custom => false, } diff --git a/openless-all/app/src-tauri/src/hotkey.rs b/openless-all/app/src-tauri/src/hotkey.rs index 845f15d5..180d72c7 100644 --- a/openless-all/app/src-tauri/src/hotkey.rs +++ b/openless-all/app/src-tauri/src/hotkey.rs @@ -621,6 +621,7 @@ mod platform { HotkeyTrigger::RightOption | HotkeyTrigger::RightAlt => 61, HotkeyTrigger::RightCommand => 54, HotkeyTrigger::Fn => 63, + HotkeyTrigger::MediaPlayPause => 0, HotkeyTrigger::Custom => unreachable!("custom combo hotkeys use ComboHotkeyMonitor"), } } @@ -633,6 +634,7 @@ mod platform { FLAG_MASK_ALTERNATE } HotkeyTrigger::Fn => FLAG_MASK_SECONDARY_FN, + HotkeyTrigger::MediaPlayPause => 0, HotkeyTrigger::Custom => unreachable!("custom combo hotkeys use ComboHotkeyMonitor"), } } @@ -766,6 +768,7 @@ mod platform { const VK_LMENU: u32 = 0xA4; const VK_RMENU: u32 = 0xA5; const VK_RWIN: u32 = 0x5C; + const VK_MEDIA_PLAY_PAUSE: u32 = 0xB3; const LLKHF_INJECTED: u32 = 0x0000_0010; const ACCEPT_INJECTED_ENV: &str = "OPENLESS_ACCEPT_SYNTHETIC_HOTKEY_EVENTS"; @@ -1024,6 +1027,7 @@ mod platform { HotkeyTrigger::RightCommand => VK_RWIN, HotkeyTrigger::LeftOption => VK_LMENU, HotkeyTrigger::Fn => VK_RCONTROL, + HotkeyTrigger::MediaPlayPause => VK_MEDIA_PLAY_PAUSE, HotkeyTrigger::Custom => unreachable!("custom combo hotkeys use ComboHotkeyMonitor"), } } diff --git a/openless-all/app/src-tauri/src/linux_fcitx.rs b/openless-all/app/src-tauri/src/linux_fcitx.rs index 92164a5f..2b9b3b9e 100644 --- a/openless-all/app/src-tauri/src/linux_fcitx.rs +++ b/openless-all/app/src-tauri/src/linux_fcitx.rs @@ -100,6 +100,7 @@ fn trigger_to_keysym(trigger: crate::types::HotkeyTrigger) -> u32 { crate::types::HotkeyTrigger::LeftOption => KEYSYM_ALT_L, crate::types::HotkeyTrigger::RightCommand => KEYSYM_SUPER_R, crate::types::HotkeyTrigger::Fn => KEYSYM_CONTROL_R, + crate::types::HotkeyTrigger::MediaPlayPause => unreachable!("Windows-only"), crate::types::HotkeyTrigger::Custom => unreachable!(), } } @@ -112,13 +113,16 @@ fn trigger_name(trigger: crate::types::HotkeyTrigger) -> &'static str { crate::types::HotkeyTrigger::LeftOption => "Alt_L", crate::types::HotkeyTrigger::RightCommand => "Super_R", crate::types::HotkeyTrigger::Fn => "Control_R", + crate::types::HotkeyTrigger::MediaPlayPause => unreachable!("Windows-only"), crate::types::HotkeyTrigger::Custom => unreachable!(), } } /// 将 OpenLess 的主听写热键绑定同步到 fcitx5 插件。 pub fn sync_binding_to_plugin(binding: &crate::types::HotkeyBinding) { - if binding.trigger == crate::types::HotkeyTrigger::Custom { + if binding.trigger == crate::types::HotkeyTrigger::Custom + || binding.trigger == crate::types::HotkeyTrigger::MediaPlayPause + { return; } let sym = trigger_to_keysym(binding.trigger); @@ -185,6 +189,9 @@ pub fn sync_qa_binding(trigger: Option) { let _ = set_qa_hotkey_raw(0, 0); return; }; + if trigger == crate::types::HotkeyTrigger::MediaPlayPause { + return; + } let sym = trigger_to_keysym(trigger); let name = trigger_name(trigger); match set_qa_hotkey_raw(sym, 0) { @@ -199,6 +206,9 @@ pub fn sync_translation_binding(trigger: Option) { let _ = set_translation_hotkey_raw(0, 0); return; }; + if trigger == crate::types::HotkeyTrigger::MediaPlayPause { + return; + } let sym = trigger_to_keysym(trigger); let name = trigger_name(trigger); match set_translation_hotkey_raw(sym, 0) { diff --git a/openless-all/app/src-tauri/src/shortcut_binding.rs b/openless-all/app/src-tauri/src/shortcut_binding.rs index bc2bd85b..6b9ad733 100644 --- a/openless-all/app/src-tauri/src/shortcut_binding.rs +++ b/openless-all/app/src-tauri/src/shortcut_binding.rs @@ -54,6 +54,7 @@ pub fn legacy_modifier_trigger(binding: &ShortcutBinding) -> Option Some(HotkeyTrigger::Fn), + "mediaplaypause" | "mediaplay" | "playpause" => Some(HotkeyTrigger::MediaPlayPause), _ => None, } } @@ -66,6 +67,7 @@ pub fn binding_from_legacy_trigger(trigger: HotkeyTrigger) -> ShortcutBinding { HotkeyTrigger::LeftControl => "LeftControl", HotkeyTrigger::RightCommand => "RightCommand", HotkeyTrigger::Fn => "Fn", + HotkeyTrigger::MediaPlayPause => "MediaPlayPause", HotkeyTrigger::Custom => "RightOption", }; ShortcutBinding { diff --git a/openless-all/app/src-tauri/src/types.rs b/openless-all/app/src-tauri/src/types.rs index da154b27..f64f054e 100644 --- a/openless-all/app/src-tauri/src/types.rs +++ b/openless-all/app/src-tauri/src/types.rs @@ -1847,6 +1847,7 @@ pub enum HotkeyTrigger { RightCommand, Fn, RightAlt, // Windows synonym for RightOption + MediaPlayPause, Custom, } @@ -1860,6 +1861,7 @@ impl HotkeyTrigger { HotkeyTrigger::RightCommand => "右 Command", HotkeyTrigger::Fn => "Fn (地球键)", HotkeyTrigger::RightAlt => "右 Alt", + HotkeyTrigger::MediaPlayPause => "⏯ Media 播放/暂停", HotkeyTrigger::Custom => "自定义组合键", } } @@ -1951,6 +1953,7 @@ fn legacy_trigger_code(trigger: HotkeyTrigger) -> &'static str { HotkeyTrigger::Fn => "ControlRight", #[cfg(not(target_os = "windows"))] HotkeyTrigger::Fn => "Fn", + HotkeyTrigger::MediaPlayPause => "MediaPlayPause", HotkeyTrigger::Custom => "", } } @@ -2071,6 +2074,7 @@ impl HotkeyCapability { HotkeyTrigger::RightAlt, HotkeyTrigger::LeftControl, HotkeyTrigger::RightCommand, + HotkeyTrigger::MediaPlayPause, HotkeyTrigger::Custom, ], requires_accessibility_permission: false, diff --git a/openless-all/app/src/i18n/en.ts b/openless-all/app/src/i18n/en.ts index a40853af..2957b4db 100644 --- a/openless-all/app/src/i18n/en.ts +++ b/openless-all/app/src/i18n/en.ts @@ -883,6 +883,7 @@ export const en: typeof zhCN = { rightCommand: 'Right Command', fn: 'Fn (Globe key)', rightAlt: 'Right Alt', + mediaPlayPause: '⏯ Media Play/Pause', custom: 'Custom combination\u2026', }, fallback: 'Global hotkey', diff --git a/openless-all/app/src/i18n/ja.ts b/openless-all/app/src/i18n/ja.ts index 44daaf6a..4ca0a390 100644 --- a/openless-all/app/src/i18n/ja.ts +++ b/openless-all/app/src/i18n/ja.ts @@ -885,6 +885,7 @@ export const ja: typeof zhCN = { rightCommand: '右 Command', fn: 'Fn (地球キー)', rightAlt: '右 Alt', + mediaPlayPause: '⏯ メディア再生/一時停止', custom: 'カスタム組み合わせ…', }, fallback: 'グローバルショートカット', diff --git a/openless-all/app/src/i18n/ko.ts b/openless-all/app/src/i18n/ko.ts index 5fd5bb8b..0b849849 100644 --- a/openless-all/app/src/i18n/ko.ts +++ b/openless-all/app/src/i18n/ko.ts @@ -885,6 +885,7 @@ export const ko: typeof zhCN = { rightCommand: '오른쪽 Command', fn: 'Fn (지구본 키)', rightAlt: '오른쪽 Alt', + mediaPlayPause: '⏯ 미디어 재생/일시정지', custom: '사용자 지정 조합…', }, fallback: '전역 단축키', diff --git a/openless-all/app/src/i18n/zh-CN.ts b/openless-all/app/src/i18n/zh-CN.ts index ad39fa4d..ca387876 100644 --- a/openless-all/app/src/i18n/zh-CN.ts +++ b/openless-all/app/src/i18n/zh-CN.ts @@ -881,6 +881,7 @@ export const zhCN = { rightCommand: '右 Command', fn: 'Fn (地球键)', rightAlt: '右 Alt', + mediaPlayPause: '⏯ 媒体播放/暂停', custom: '自定义组合键…', }, fallback: '全局快捷键', diff --git a/openless-all/app/src/i18n/zh-TW.ts b/openless-all/app/src/i18n/zh-TW.ts index 401671b5..043e71b9 100644 --- a/openless-all/app/src/i18n/zh-TW.ts +++ b/openless-all/app/src/i18n/zh-TW.ts @@ -883,6 +883,7 @@ export const zhTW: typeof zhCN = { rightCommand: '右 Command', fn: 'Fn (地球鍵)', rightAlt: '右 Alt', + mediaPlayPause: '⏯ 媒體播放/暫停', custom: '自訂組合…', }, fallback: '全局快捷鍵', diff --git a/openless-all/app/src/lib/hotkey.ts b/openless-all/app/src/lib/hotkey.ts index 2f28ac99..208c0fa9 100644 --- a/openless-all/app/src/lib/hotkey.ts +++ b/openless-all/app/src/lib/hotkey.ts @@ -169,6 +169,8 @@ function legacyTriggerCode(trigger: HotkeyTrigger | null | undefined): string | return 'MetaRight'; case 'fn': return 'Fn'; + case 'mediaPlayPause': + return 'MediaPlayPause'; default: return null; } @@ -259,6 +261,7 @@ function formatPrimary(primary: string): string { case 'leftcontrol': return isMac ? 'Left ⌃' : 'Left Ctrl'; case 'rightcommand': return isMac ? 'Right ⌘' : (currentPlatform().isWindows ? 'Right Win' : 'Right Super'); case 'fn': return 'Fn'; + case 'mediaplaypause': return '⏯ Media'; case 'shift': return isMac ? '⇧' : 'Shift'; } return trimmed; diff --git a/openless-all/app/src/lib/types.ts b/openless-all/app/src/lib/types.ts index 9b466cdf..4b8d08f1 100644 --- a/openless-all/app/src/lib/types.ts +++ b/openless-all/app/src/lib/types.ts @@ -60,6 +60,7 @@ export type HotkeyTrigger = | 'rightCommand' | 'fn' | 'rightAlt' + | 'mediaPlayPause' | 'custom'; export type HotkeyMode = 'toggle' | 'hold' | 'doubleClick';