fix: address hotkey review feedback
This commit is contained in:
1 parent
2d155912b1
commit
e7625c5eb5
2 files changed
+289
-77
No files matched your search
@@ -5,6 +5,7 @@
|
||||
//! insertion, persists history, emits `capsule:state` events to the capsule
|
||||
//! window.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc;
|
||||
use std::sync::Arc;
|
||||
@@ -19,7 +20,7 @@ use crate::asr::{
|
||||
DictionaryHotword, RawTranscript, VolcengineCredentials, VolcengineStreamingASR,
|
||||
WhisperBatchASR,
|
||||
};
|
||||
use crate::hotkey::{HotkeyEvent, HotkeyMonitor};
|
||||
use crate::hotkey::{binding_matches_pressed_codes, HotkeyEvent, HotkeyMonitor};
|
||||
use crate::insertion::TextInserter;
|
||||
use crate::persistence::{
|
||||
CredentialAccount, CredentialsVault, DictionaryStore, HistoryStore, PreferencesStore,
|
||||
@@ -130,6 +131,7 @@ struct Inner {
|
||||
hotkey_status: Mutex<HotkeyStatus>,
|
||||
hotkey_trigger_held: AtomicBool,
|
||||
hotkey_last_click_at: Mutex<Option<Instant>>,
|
||||
window_hotkey_pressed_codes: Mutex<BTreeSet<String>>,
|
||||
/// 翻译模式触发标志。每次 begin_session 重置为 false;hotkey 监听器在
|
||||
/// Listening / Starting 阶段看到 Shift down 边沿时 set true。
|
||||
/// end_session 在调 polish/translate 前读这个 flag + translation_target_language
|
||||
@@ -226,6 +228,7 @@ impl Coordinator {
|
||||
hotkey_status: Mutex::new(HotkeyStatus::default()),
|
||||
hotkey_trigger_held: AtomicBool::new(false),
|
||||
hotkey_last_click_at: Mutex::new(None),
|
||||
window_hotkey_pressed_codes: Mutex::new(BTreeSet::new()),
|
||||
translation_modifier_seen: AtomicBool::new(false),
|
||||
qa_hotkey: Mutex::new(None),
|
||||
qa_state: Mutex::new(QaSessionState::default()),
|
||||
@@ -374,6 +377,7 @@ impl Coordinator {
|
||||
}
|
||||
|
||||
pub fn update_hotkey_binding(&self) {
|
||||
self.inner.window_hotkey_pressed_codes.lock().clear();
|
||||
if let Some(monitor) = self.inner.hotkey.lock().as_ref() {
|
||||
monitor.update_binding(self.inner.prefs.get().hotkey);
|
||||
}
|
||||
@@ -700,11 +704,14 @@ fn hotkey_bridge_loop(inner: Arc<Inner>, rx: mpsc::Receiver<HotkeyEvent>) {
|
||||
}
|
||||
|
||||
async fn handle_pressed_edge(inner: &Arc<Inner>) {
|
||||
let was_held = inner.hotkey_trigger_held.swap(true, Ordering::SeqCst);
|
||||
let was_held = inner.hotkey_trigger_held.load(Ordering::SeqCst);
|
||||
if !was_held {
|
||||
if !should_accept_pressed_edge(inner) {
|
||||
return;
|
||||
}
|
||||
if inner.hotkey_trigger_held.swap(true, Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
// 路由:QA 浮窗可见时,rightOption 边沿走 QA;否则走主听写。详见 issue #118 v2。
|
||||
let panel_visible = inner.qa_state.lock().panel_visible;
|
||||
if panel_visible {
|
||||
@@ -908,22 +915,41 @@ async fn handle_window_hotkey_event(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let binding = inner.prefs.get().hotkey;
|
||||
if !window_key_matches_binding(&binding, &key, &code) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match event_type.as_str() {
|
||||
"keydown" => {
|
||||
if repeat {
|
||||
return Ok(());
|
||||
}
|
||||
log::info!("[window-hotkey] pressed code={code} repeat={repeat}");
|
||||
handle_pressed_edge(inner).await;
|
||||
let binding = inner.prefs.get().hotkey;
|
||||
let Some((was_active, is_active)) = update_window_hotkey_pressed_codes(
|
||||
&mut inner.window_hotkey_pressed_codes.lock(),
|
||||
&binding,
|
||||
&key,
|
||||
&code,
|
||||
true,
|
||||
) else {
|
||||
return Ok(());
|
||||
};
|
||||
if is_active && !was_active {
|
||||
log::info!("[window-hotkey] pressed code={code} repeat={repeat}");
|
||||
handle_pressed_edge(inner).await;
|
||||
}
|
||||
}
|
||||
"keyup" => {
|
||||
log::info!("[window-hotkey] released code={code}");
|
||||
handle_released_edge(inner).await;
|
||||
let binding = inner.prefs.get().hotkey;
|
||||
let Some((was_active, is_active)) = update_window_hotkey_pressed_codes(
|
||||
&mut inner.window_hotkey_pressed_codes.lock(),
|
||||
&binding,
|
||||
&key,
|
||||
&code,
|
||||
false,
|
||||
) else {
|
||||
return Ok(());
|
||||
};
|
||||
if was_active && !is_active {
|
||||
log::info!("[window-hotkey] released code={code}");
|
||||
handle_released_edge(inner).await;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -935,6 +961,31 @@ fn window_hotkey_fallback_enabled() -> bool {
|
||||
crate::types::HotkeyCapability::current().explicit_fallback_available
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "windows", test))]
|
||||
fn update_window_hotkey_pressed_codes(
|
||||
pressed_codes: &mut BTreeSet<String>,
|
||||
binding: &HotkeyBinding,
|
||||
key: &str,
|
||||
code: &str,
|
||||
pressed: bool,
|
||||
) -> Option<(bool, bool)> {
|
||||
if !window_key_matches_binding(binding, key, code) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let normalized = normalize_window_hotkey_code(key, code);
|
||||
let was_active = binding_matches_pressed_codes(binding, pressed_codes);
|
||||
if pressed {
|
||||
pressed_codes.insert(normalized);
|
||||
} else {
|
||||
pressed_codes.remove(&normalized);
|
||||
}
|
||||
Some((
|
||||
was_active,
|
||||
binding_matches_pressed_codes(binding, pressed_codes),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "windows", test))]
|
||||
fn window_key_matches_binding(binding: &HotkeyBinding, key: &str, code: &str) -> bool {
|
||||
let normalized = normalize_window_hotkey_code(key, code);
|
||||
@@ -2607,16 +2658,85 @@ mod tests {
|
||||
trigger: HotkeyTrigger::RightControl,
|
||||
..Default::default()
|
||||
};
|
||||
assert!(window_key_matches_binding(&legacy, "Control", "ControlRight"));
|
||||
assert!(!window_key_matches_binding(&legacy, "Control", "ControlLeft"));
|
||||
assert!(window_key_matches_binding(
|
||||
&legacy,
|
||||
"Control",
|
||||
"ControlRight"
|
||||
));
|
||||
assert!(!window_key_matches_binding(
|
||||
&legacy,
|
||||
"Control",
|
||||
"ControlLeft"
|
||||
));
|
||||
|
||||
let caps_lock = HotkeyBinding {
|
||||
trigger: HotkeyTrigger::RightControl,
|
||||
keys: Some(vec![HotkeyKey::new("CapsLock")]),
|
||||
..Default::default()
|
||||
};
|
||||
assert!(window_key_matches_binding(&caps_lock, "CapsLock", "CapsLock"));
|
||||
assert!(!window_key_matches_binding(&caps_lock, "Control", "ControlRight"));
|
||||
assert!(window_key_matches_binding(
|
||||
&caps_lock, "CapsLock", "CapsLock"
|
||||
));
|
||||
assert!(!window_key_matches_binding(
|
||||
&caps_lock,
|
||||
"Control",
|
||||
"ControlRight"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_hotkey_fallback_requires_full_combo_before_activating() {
|
||||
let binding = HotkeyBinding {
|
||||
trigger: HotkeyTrigger::RightControl,
|
||||
keys: Some(vec![
|
||||
HotkeyKey::new("ControlLeft"),
|
||||
HotkeyKey::new("AltLeft"),
|
||||
HotkeyKey::new("Mouse4"),
|
||||
]),
|
||||
..Default::default()
|
||||
};
|
||||
let mut pressed_codes = std::collections::BTreeSet::new();
|
||||
|
||||
assert_eq!(
|
||||
update_window_hotkey_pressed_codes(
|
||||
&mut pressed_codes,
|
||||
&binding,
|
||||
"Control",
|
||||
"ControlLeft",
|
||||
true,
|
||||
),
|
||||
Some((false, false))
|
||||
);
|
||||
assert_eq!(
|
||||
update_window_hotkey_pressed_codes(
|
||||
&mut pressed_codes,
|
||||
&binding,
|
||||
"Alt",
|
||||
"AltLeft",
|
||||
true
|
||||
),
|
||||
Some((false, false))
|
||||
);
|
||||
assert_eq!(
|
||||
update_window_hotkey_pressed_codes(
|
||||
&mut pressed_codes,
|
||||
&binding,
|
||||
"Mouse4",
|
||||
"Mouse4",
|
||||
true,
|
||||
),
|
||||
Some((false, true))
|
||||
);
|
||||
assert_eq!(
|
||||
update_window_hotkey_pressed_codes(
|
||||
&mut pressed_codes,
|
||||
&binding,
|
||||
"Alt",
|
||||
"AltLeft",
|
||||
false
|
||||
),
|
||||
Some((true, false))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2732,6 +2852,29 @@ mod tests {
|
||||
assert_eq!(state.session_id, 41);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn rejected_double_click_press_does_not_mark_trigger_held() {
|
||||
let coordinator = Coordinator::new();
|
||||
coordinator
|
||||
.inner
|
||||
.prefs
|
||||
.set(crate::types::UserPreferences {
|
||||
hotkey: crate::types::HotkeyBinding {
|
||||
trigger: HotkeyTrigger::RightControl,
|
||||
mode: HotkeyMode::DoubleClick,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
handle_pressed_edge(&coordinator.inner).await;
|
||||
|
||||
assert!(!coordinator.inner.hotkey_trigger_held.load(Ordering::SeqCst));
|
||||
handle_released_edge(&coordinator.inner).await;
|
||||
assert!(!coordinator.inner.hotkey_trigger_held.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double_click_mode_requires_second_press_within_window() {
|
||||
let coordinator = Coordinator::new();
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
//! 仅产出"边沿"事件,toggle vs hold 由 Coordinator 解释。
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc::{self, Sender};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -166,7 +166,48 @@ fn dispatch_hotkey_code(shared: &Shared, tx: &Sender<HotkeyEvent>, code: &str, p
|
||||
}
|
||||
}
|
||||
|
||||
fn binding_matches_pressed_codes(binding: &HotkeyBinding, pressed_codes: &BTreeSet<String>) -> bool {
|
||||
fn dispatch_translation_modifier_code(
|
||||
shared: &Shared,
|
||||
tx: &Sender<HotkeyEvent>,
|
||||
code: &str,
|
||||
pressed: bool,
|
||||
) {
|
||||
if !is_shift_hotkey_code(code) {
|
||||
return;
|
||||
}
|
||||
|
||||
let shift_is_hotkey = shared
|
||||
.binding
|
||||
.read()
|
||||
.effective_codes()
|
||||
.iter()
|
||||
.any(|candidate| candidate == code);
|
||||
if shift_is_hotkey {
|
||||
return;
|
||||
}
|
||||
|
||||
if pressed {
|
||||
let was_held = shared
|
||||
.translation_modifier_held
|
||||
.swap(true, Ordering::SeqCst);
|
||||
if !was_held {
|
||||
send_or_log(tx, HotkeyEvent::TranslationModifierPressed);
|
||||
}
|
||||
} else {
|
||||
shared
|
||||
.translation_modifier_held
|
||||
.store(false, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_shift_hotkey_code(code: &str) -> bool {
|
||||
matches!(code, "ShiftLeft" | "ShiftRight")
|
||||
}
|
||||
|
||||
pub(crate) fn binding_matches_pressed_codes(
|
||||
binding: &HotkeyBinding,
|
||||
pressed_codes: &BTreeSet<String>,
|
||||
) -> bool {
|
||||
let codes = binding.effective_codes();
|
||||
!codes.is_empty()
|
||||
&& codes
|
||||
@@ -179,13 +220,13 @@ fn binding_matches_pressed_codes(binding: &HotkeyBinding, pressed_codes: &BTreeS
|
||||
#[cfg(target_os = "macos")]
|
||||
mod platform {
|
||||
use std::ffi::c_void;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{
|
||||
dispatch_hotkey_code, install_error, send_or_log, start_listener_thread,
|
||||
update_shared_binding, HotkeyAdapter, HotkeyEvent, Shared, StartupTx,
|
||||
dispatch_hotkey_code, dispatch_translation_modifier_code, install_error,
|
||||
is_shift_hotkey_code, send_or_log, start_listener_thread, update_shared_binding,
|
||||
HotkeyAdapter, HotkeyEvent, Shared, StartupTx,
|
||||
};
|
||||
use crate::types::{HotkeyAdapterKind, HotkeyBinding, HotkeyInstallError};
|
||||
|
||||
@@ -392,23 +433,17 @@ mod platform {
|
||||
|
||||
fn handle_flags_changed(ctx: &CallbackContext, event: CgEventRef) {
|
||||
let flags = unsafe { CGEventGetFlags(event) };
|
||||
|
||||
// Shift 是翻译模式修饰键 — 与触发键的 keycode 检查独立,任何时刻按 Shift 都生效。
|
||||
let shift_active = (flags & FLAG_MASK_SHIFT) != 0;
|
||||
let shift_was_held = ctx.shared.translation_modifier_held.load(Ordering::SeqCst);
|
||||
if shift_active && !shift_was_held {
|
||||
ctx.shared
|
||||
.translation_modifier_held
|
||||
.store(true, Ordering::SeqCst);
|
||||
send_or_log(&ctx.tx, HotkeyEvent::TranslationModifierPressed);
|
||||
} else if !shift_active && shift_was_held {
|
||||
ctx.shared
|
||||
.translation_modifier_held
|
||||
.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
let keycode = unsafe { CGEventGetIntegerValueField(event, KEYBOARD_EVENT_KEYCODE) };
|
||||
if let Some(code) = mac_keycode_to_hotkey_code(keycode) {
|
||||
if is_shift_hotkey_code(code) {
|
||||
// Shift 作为录音热键成员时只参与热键匹配,不再额外切到翻译模式。
|
||||
dispatch_translation_modifier_code(
|
||||
&ctx.shared,
|
||||
&ctx.tx,
|
||||
code,
|
||||
(flags & FLAG_MASK_SHIFT) != 0,
|
||||
);
|
||||
}
|
||||
if let Some(mask) = mac_keycode_flag_mask(keycode) {
|
||||
let family_active = (flags & mask) != 0;
|
||||
let code_was_pressed = ctx.shared.pressed_codes.read().contains(code);
|
||||
@@ -578,11 +613,67 @@ mod platform {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::types::{HotkeyKey, HotkeyMode, HotkeyTrigger};
|
||||
|
||||
fn shared_with_binding(binding: HotkeyBinding) -> Shared {
|
||||
Shared {
|
||||
binding: RwLock::new(binding),
|
||||
pressed_codes: RwLock::new(BTreeSet::new()),
|
||||
trigger_held: AtomicBool::new(false),
|
||||
translation_modifier_held: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shift_hotkey_press_does_not_emit_translation_modifier() {
|
||||
let shared = shared_with_binding(HotkeyBinding {
|
||||
trigger: HotkeyTrigger::RightControl,
|
||||
mode: HotkeyMode::Toggle,
|
||||
keys: Some(vec![HotkeyKey::new("ShiftLeft")]),
|
||||
});
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
dispatch_translation_modifier_code(&shared, &tx, "ShiftLeft", true);
|
||||
assert!(rx.try_recv().is_err());
|
||||
|
||||
dispatch_hotkey_code(&shared, &tx, "ShiftLeft", true);
|
||||
assert!(matches!(rx.try_recv(), Ok(HotkeyEvent::Pressed)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbound_shift_press_still_emits_translation_modifier_once_per_hold() {
|
||||
let shared = shared_with_binding(HotkeyBinding {
|
||||
trigger: HotkeyTrigger::RightControl,
|
||||
mode: HotkeyMode::Toggle,
|
||||
keys: Some(vec![HotkeyKey::new("ControlRight")]),
|
||||
});
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
dispatch_translation_modifier_code(&shared, &tx, "ShiftLeft", true);
|
||||
assert!(matches!(
|
||||
rx.try_recv(),
|
||||
Ok(HotkeyEvent::TranslationModifierPressed)
|
||||
));
|
||||
|
||||
dispatch_translation_modifier_code(&shared, &tx, "ShiftLeft", true);
|
||||
assert!(rx.try_recv().is_err());
|
||||
|
||||
dispatch_translation_modifier_code(&shared, &tx, "ShiftLeft", false);
|
||||
dispatch_translation_modifier_code(&shared, &tx, "ShiftLeft", true);
|
||||
assert!(matches!(
|
||||
rx.try_recv(),
|
||||
Ok(HotkeyEvent::TranslationModifierPressed)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────── Windows implementation ───────────────────────────
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
mod platform {
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::atomic::{AtomicPtr, Ordering as AtomicOrdering};
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::sync::Arc;
|
||||
@@ -591,13 +682,14 @@ mod platform {
|
||||
use windows::Win32::System::Threading::GetCurrentThreadId;
|
||||
use windows::Win32::UI::WindowsAndMessaging::{
|
||||
CallNextHookEx, DispatchMessageW, GetMessageW, PostThreadMessageW, SetWindowsHookExW,
|
||||
TranslateMessage, UnhookWindowsHookEx, HC_ACTION, HHOOK, KBDLLHOOKSTRUCT,
|
||||
MSLLHOOKSTRUCT, MSG, WH_KEYBOARD_LL, WH_MOUSE_LL, WM_QUIT,
|
||||
TranslateMessage, UnhookWindowsHookEx, HC_ACTION, HHOOK, KBDLLHOOKSTRUCT, MSG,
|
||||
MSLLHOOKSTRUCT, WH_KEYBOARD_LL, WH_MOUSE_LL, WM_QUIT,
|
||||
};
|
||||
|
||||
use super::{
|
||||
dispatch_hotkey_code, install_error, send_or_log, start_listener_thread,
|
||||
update_shared_binding, HotkeyAdapter, HotkeyEvent, Shared, StartupTx,
|
||||
dispatch_hotkey_code, dispatch_translation_modifier_code, install_error, send_or_log,
|
||||
start_listener_thread, update_shared_binding, HotkeyAdapter, HotkeyEvent, Shared,
|
||||
StartupTx,
|
||||
};
|
||||
use crate::types::{HotkeyAdapterKind, HotkeyBinding, HotkeyInstallError};
|
||||
|
||||
@@ -830,28 +922,11 @@ mod platform {
|
||||
return;
|
||||
}
|
||||
|
||||
// Shift(任一侧)= 翻译模式修饰键。在录音过程中任意时刻按下都生效。详见 issue #4。
|
||||
if matches!(vk_code, VK_SHIFT | VK_LSHIFT | VK_RSHIFT) {
|
||||
match message {
|
||||
WM_KEYDOWN | WM_SYSKEYDOWN => {
|
||||
let was_held = ctx
|
||||
.shared
|
||||
.translation_modifier_held
|
||||
.swap(true, Ordering::SeqCst);
|
||||
if !was_held {
|
||||
send_or_log(&ctx.tx, HotkeyEvent::TranslationModifierPressed);
|
||||
}
|
||||
}
|
||||
WM_KEYUP | WM_SYSKEYUP => {
|
||||
ctx.shared
|
||||
.translation_modifier_held
|
||||
.store(false, Ordering::SeqCst);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(code) = vk_to_hotkey_code(vk_code) {
|
||||
if matches!(vk_code, VK_SHIFT | VK_LSHIFT | VK_RSHIFT) {
|
||||
// Shift 作为录音热键成员时只参与热键匹配,不再额外切到翻译模式。
|
||||
dispatch_translation_modifier_code(&ctx.shared, &ctx.tx, code, is_down);
|
||||
}
|
||||
if is_down {
|
||||
dispatch_hotkey_code(&ctx.shared, &ctx.tx, code, true);
|
||||
} else if is_up {
|
||||
@@ -1008,8 +1083,9 @@ mod platform {
|
||||
use rdev::{listen, Button, Event, EventType, Key};
|
||||
|
||||
use super::{
|
||||
dispatch_hotkey_code, install_error, send_or_log, start_listener_thread,
|
||||
update_shared_binding, HotkeyAdapter, HotkeyEvent, Shared, StartupTx,
|
||||
dispatch_hotkey_code, dispatch_translation_modifier_code, install_error, send_or_log,
|
||||
start_listener_thread, update_shared_binding, HotkeyAdapter, HotkeyEvent, Shared,
|
||||
StartupTx,
|
||||
};
|
||||
use crate::types::{HotkeyAdapterKind, HotkeyBinding, HotkeyInstallError};
|
||||
|
||||
@@ -1082,26 +1158,19 @@ mod platform {
|
||||
send_or_log(tx, HotkeyEvent::Cancelled);
|
||||
return;
|
||||
}
|
||||
// Shift(任一侧)= 翻译模式修饰键。详见 issue #4。
|
||||
if matches!(key, Key::ShiftLeft | Key::ShiftRight) {
|
||||
let was_held = shared
|
||||
.translation_modifier_held
|
||||
.swap(true, Ordering::SeqCst);
|
||||
if !was_held {
|
||||
send_or_log(tx, HotkeyEvent::TranslationModifierPressed);
|
||||
}
|
||||
}
|
||||
if let Some(code) = rdev_key_to_hotkey_code(key) {
|
||||
if matches!(key, Key::ShiftLeft | Key::ShiftRight) {
|
||||
// Shift 作为录音热键成员时只参与热键匹配,不再额外切到翻译模式。
|
||||
dispatch_translation_modifier_code(shared, tx, code, true);
|
||||
}
|
||||
dispatch_hotkey_code(shared, tx, code, true);
|
||||
}
|
||||
}
|
||||
EventType::KeyRelease(key) => {
|
||||
if matches!(key, Key::ShiftLeft | Key::ShiftRight) {
|
||||
shared
|
||||
.translation_modifier_held
|
||||
.store(false, Ordering::SeqCst);
|
||||
}
|
||||
if let Some(code) = rdev_key_to_hotkey_code(key) {
|
||||
if matches!(key, Key::ShiftLeft | Key::ShiftRight) {
|
||||
dispatch_translation_modifier_code(shared, tx, code, false);
|
||||
}
|
||||
dispatch_hotkey_code(shared, tx, code, false);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user