From 0f6d438025eeab104555fd11073e58476130cbc3 Mon Sep 17 00:00:00 2001 From: rszyma Date: Sun, 22 Oct 2023 06:07:01 +0200 Subject: [PATCH 01/15] feat(linux): allow remapping mouse scroll --- parser/src/keys/mod.rs | 20 +++++++++++ src/kanata/linux.rs | 54 ++++++++++++++++++++++++----- src/kanata/mod.rs | 78 ++++++++++++++++++++++++++---------------- src/oskbd/linux.rs | 56 ++++++++++++++++++++++++++++++ src/oskbd/mod.rs | 40 +++++++++++++++++++++- 5 files changed, 210 insertions(+), 38 deletions(-) diff --git a/parser/src/keys/mod.rs b/parser/src/keys/mod.rs index c831a6ebb..80313d61c 100644 --- a/parser/src/keys/mod.rs +++ b/parser/src/keys/mod.rs @@ -242,6 +242,16 @@ pub fn str_to_oscode(s: &str) -> Option { "mfwd" | "mouseforward" => OsCode::BTN_EXTRA, "mbck" | "mousebackward" => OsCode::BTN_SIDE, + // NOTE: these are linux-only right now due to missing implementation for windows + #[cfg(any(target_os = "linux", target_os = "unknown"))] + "mwu" | "mousewheelup" => OsCode::MouseWheelUp, + #[cfg(any(target_os = "linux", target_os = "unknown"))] + "mwd" | "mousewheeldown" => OsCode::MouseWheelDown, + #[cfg(any(target_os = "linux", target_os = "unknown"))] + "mwl" | "mousewheelleft" => OsCode::MouseWheelLeft, + #[cfg(any(target_os = "linux", target_os = "unknown"))] + "mwr" | "mousewheelright" => OsCode::MouseWheelRight, + "hmpg" | "homepage" => OsCode::KEY_HOMEPAGE, "mdia" | "media" => OsCode::KEY_MEDIA, "mail" => OsCode::KEY_MAIL, @@ -1018,6 +1028,16 @@ pub enum OsCode { BTN_TRIGGER_HAPPY39 = 742, BTN_TRIGGER_HAPPY40 = 743, BTN_MAX = 744, + + // Mouse wheel events are not a part of EV_KEY, so they technically + // shouldn't be there, but they're still there, because this way + // it's easier to implement allowing to add them to defsrc without + // making tons of changes all over the codebase. + MouseWheelUp = 745, + MouseWheelDown = 746, + MouseWheelLeft = 747, + MouseWheelRight = 748, + KEY_MAX = 767, } diff --git a/src/kanata/linux.rs b/src/kanata/linux.rs index 97ff87f2e..12da21cd5 100644 --- a/src/kanata/linux.rs +++ b/src/kanata/linux.rs @@ -10,7 +10,7 @@ use super::*; impl Kanata { /// Enter an infinite loop that listens for OS key events and sends them to the processing /// thread. - pub fn event_loop(kanata: Arc>, tx: Sender) -> Result<()> { + pub fn event_loop(kanata: Arc>, tx: Sender) -> Result<()> { info!("entering the event loop"); let k = kanata.lock(); @@ -36,10 +36,10 @@ impl Kanata { log::trace!("{events:?}"); for in_event in events.into_iter() { - let key_event = match KeyEvent::try_from(in_event) { + let supported_in_event = match SupportedInputEvent::try_from(in_event) { Ok(ev) => ev, _ => { - // Pass-through non-key events + // Pass-through non-key and non-scroll events let mut kanata = kanata.lock(); kanata .kbd_out @@ -49,21 +49,59 @@ impl Kanata { } }; - check_for_exit(&key_event); + // TODO: Perhaps this should be exposed as config option? + // Hardcoding this would disable hi-res scrolls whenever kanata is running, + // regardless of whether user remaps mouse wheel. + // But allowing passthrough when scroll is mapped might + // show some side effects, such as scroll still working while remapped. + // Another idea is to disable corresponding hi-res scroll + // events for the scroll events that are listed in defsrc. + let allow_hi_res_scroll_events_passthrough = false; + + let osc: OsCode = match supported_in_event { + SupportedInputEvent::KeyEvent(kev) => kev.code, + SupportedInputEvent::ScrollEvent( + sev @ ScrollEvent { + kind: ScrollEventKind::Standard, + .. + }, + ) => sev + .try_into() + .expect("standard scroll should have OsCode mapping"), + SupportedInputEvent::ScrollEvent(ScrollEvent { + kind: ScrollEventKind::HiRes, + .. + }) => { + if allow_hi_res_scroll_events_passthrough { + let mut kanata = kanata.lock(); + kanata + .kbd_out + .write_raw(in_event) + .map_err(|e| anyhow!("failed write: {}", e))?; + } + continue; + } + }; // Check if this keycode is mapped in the configuration. If it hasn't been mapped, send // it immediately. - if !MAPPED_KEYS.lock().contains(&key_event.code) { + let is_mapped = MAPPED_KEYS.lock().contains(&osc); + + if let SupportedInputEvent::KeyEvent(key_event) = supported_in_event { + check_for_exit(&key_event); + } + + if !is_mapped { let mut kanata = kanata.lock(); kanata .kbd_out - .write_key(key_event.code, key_event.value) - .map_err(|e| anyhow!("failed write key: {}", e))?; + .write_raw(in_event) + .map_err(|e| anyhow!("failed write: {}", e))?; continue; } // Send key events to the processing loop - if let Err(e) = tx.send(key_event) { + if let Err(e) = tx.send(supported_in_event) { bail!("failed to send on channel: {}", e) } } diff --git a/src/kanata/mod.rs b/src/kanata/mod.rs index de2d6173a..a00479a8f 100644 --- a/src/kanata/mod.rs +++ b/src/kanata/mod.rs @@ -404,30 +404,46 @@ impl Kanata { } /// Update keyberon layout state for press/release, handle repeat separately - fn handle_key_event(&mut self, event: &KeyEvent) -> Result<()> { + fn handle_input_event(&mut self, event: &SupportedInputEvent) -> Result<()> { log::debug!("process recv ev {event:?}"); - let evc: u16 = event.code.into(); self.ticks_since_idle = 0; - let kbrn_ev = match event.value { - KeyValue::Press => { - if let Some(state) = &mut self.dynamic_macro_record_state { - state.macro_items.push(DynamicMacroItem::Press(event.code)); - } - Event::Press(0, evc) - } - KeyValue::Release => { - if let Some(state) = &mut self.dynamic_macro_record_state { - state - .macro_items - .push(DynamicMacroItem::Release(event.code)); - } - Event::Release(0, evc) + + let kbrn_ev; + + match event { + SupportedInputEvent::KeyEvent(kev) => { + let evc: u16 = kev.code.into(); + kbrn_ev = match kev.value { + KeyValue::Press => { + if let Some(state) = &mut self.dynamic_macro_record_state { + state.macro_items.push(DynamicMacroItem::Press(kev.code)); + } + Event::Press(0, evc) + } + KeyValue::Release => { + if let Some(state) = &mut self.dynamic_macro_record_state { + state.macro_items.push(DynamicMacroItem::Release(kev.code)); + } + Event::Release(0, evc) + } + KeyValue::Repeat => { + let ret = self.handle_repeat(kev); + return ret; + } + }; } - KeyValue::Repeat => { - let ret = self.handle_repeat(event); - return ret; + SupportedInputEvent::ScrollEvent(sev) => { + let osc: OsCode = match (*sev).try_into() { + Ok(code) => code, + Err(_) => return Ok(()), + }; + let evc: u16 = osc.into(); + self.layout.bm().event(Event::Press(0, evc)); + self.layout.bm().event(Event::Release(0, evc)); + return Ok(()); } - }; + } + self.layout.bm().event(kbrn_ev); Ok(()) } @@ -1461,7 +1477,7 @@ impl Kanata { /// Starts a new thread that processes OS key events and advances the keyberon layout's state. pub fn start_processing_loop( kanata: Arc>, - rx: Receiver, + rx: Receiver, tx: Option>, nodelay: bool, ) { @@ -1470,11 +1486,15 @@ impl Kanata { if !nodelay { info!("Init: catching only releases and sending immediately"); for _ in 0..500 { - if let Ok(kev) = rx.try_recv() { - if kev.value == KeyValue::Release { + if let Ok(SupportedInputEvent::KeyEvent(KeyEvent { + code, + value: KeyValue::Release, + })) = rx.try_recv() + { + { let mut k = kanata.lock(); - info!("Init: releasing {:?}", kev.code); - k.kbd_out.release_key(kev.code).expect("key released"); + info!("Init: releasing {:?}", code); + k.kbd_out.release_key(code).expect("key released"); } } std::thread::sleep(time::Duration::from_millis(1)); @@ -1504,7 +1524,7 @@ impl Kanata { if can_block { log::trace!("blocking on channel"); match rx.recv() { - Ok(kev) => { + Ok(ev) => { let mut k = kanata.lock(); let now = time::Instant::now() .checked_sub(time::Duration::from_millis(1)) @@ -1557,7 +1577,7 @@ impl Kanata { #[cfg(feature = "perf_logging")] let start = std::time::Instant::now(); - if let Err(e) = k.handle_key_event(&kev) { + if let Err(e) = k.handle_input_event(&ev) { break e; } @@ -1588,11 +1608,11 @@ impl Kanata { } else { let mut k = kanata.lock(); match rx.try_recv() { - Ok(kev) => { + Ok(ev) => { #[cfg(feature = "perf_logging")] let start = std::time::Instant::now(); - if let Err(e) = k.handle_key_event(&kev) { + if let Err(e) = k.handle_input_event(&ev) { break e; } diff --git a/src/oskbd/linux.rs b/src/oskbd/linux.rs index 68c57ebfa..c0232f0c5 100644 --- a/src/oskbd/linux.rs +++ b/src/oskbd/linux.rs @@ -272,6 +272,62 @@ impl TryFrom for KeyEvent { } } +impl TryFrom for SupportedInputEvent { + type Error = (); + fn try_from(item: InputEvent) -> Result { + match item.kind() { + evdev::InputEventKind::Key(k) => Ok(Self::KeyEvent(KeyEvent { + code: OsCode::from_u16(k.0).ok_or(())?, + value: KeyValue::from(item.value()), + })), + evdev::InputEventKind::RelAxis(axis_type) => { + let (direction, kind) = match (axis_type, item.value()) { + (RelativeAxisType::REL_WHEEL, dist) => ( + if dist > 0 { + MoveDirection::Up + } else { + MoveDirection::Down + }, + ScrollEventKind::Standard, + ), + (RelativeAxisType::REL_HWHEEL, dist) => ( + if dist > 0 { + MoveDirection::Left + } else { + MoveDirection::Right + }, + ScrollEventKind::Standard, + ), + (RelativeAxisType::REL_WHEEL_HI_RES, dist) => ( + if dist > 0 { + MoveDirection::Up + } else { + MoveDirection::Down + }, + ScrollEventKind::HiRes, + ), + (RelativeAxisType::REL_HWHEEL_HI_RES, dist) => ( + if dist > 0 { + MoveDirection::Left + } else { + MoveDirection::Right + }, + ScrollEventKind::HiRes, + ), + _ => return Err(()), + }; + + Ok(Self::ScrollEvent(ScrollEvent { + kind, + direction, + distance: item.value().unsigned_abs(), + })) + } + _ => Err(()), + } + } +} + impl From for InputEvent { fn from(item: KeyEvent) -> Self { InputEvent::new(EventType::KEY, item.code as u16, item.value as i32) diff --git a/src/oskbd/mod.rs b/src/oskbd/mod.rs index 32a9a3761..e15374748 100644 --- a/src/oskbd/mod.rs +++ b/src/oskbd/mod.rs @@ -45,7 +45,7 @@ impl From for bool { } } -use kanata_parser::keys::OsCode; +use kanata_parser::{custom_action::MoveDirection, keys::OsCode}; #[derive(Debug, Clone, Copy)] pub struct KeyEvent { @@ -59,3 +59,41 @@ impl KeyEvent { Self { code, value } } } + +#[derive(Debug, Clone, Copy)] +pub enum ScrollEventKind { + Standard, + HiRes, +} + +#[derive(Debug, Clone, Copy)] +pub struct ScrollEvent { + pub kind: ScrollEventKind, + pub direction: MoveDirection, + /// Unit: scroll notches if ScrollEventKind::Standard or + /// scroll notches * 120 if ScrollEventKind::HiRes + pub distance: u32, +} + +impl TryFrom for OsCode { + type Error = (); + fn try_from(value: ScrollEvent) -> Result { + match value.kind { + ScrollEventKind::Standard => { + return Ok(match value.direction { + MoveDirection::Up => OsCode::MouseWheelUp, + MoveDirection::Down => OsCode::MouseWheelDown, + MoveDirection::Left => OsCode::MouseWheelLeft, + MoveDirection::Right => OsCode::MouseWheelRight, + }) + } + ScrollEventKind::HiRes => return Err(()), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum SupportedInputEvent { + KeyEvent(KeyEvent), + ScrollEvent(ScrollEvent), +} From 57d88e733dfaa01ce89d3388815a9e90cefc24fe Mon Sep 17 00:00:00 2001 From: rszyma Date: Sun, 22 Oct 2023 06:26:21 +0200 Subject: [PATCH 02/15] shut up clippy --- src/kanata/mod.rs | 10 ++++------ src/oskbd/mod.rs | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/kanata/mod.rs b/src/kanata/mod.rs index b8bd1ef51..fa675ac52 100644 --- a/src/kanata/mod.rs +++ b/src/kanata/mod.rs @@ -447,12 +447,10 @@ impl Kanata { log::debug!("process recv ev {event:?}"); self.ticks_since_idle = 0; - let kbrn_ev; - - match event { + let kbrn_ev = match event { SupportedInputEvent::KeyEvent(kev) => { let evc: u16 = kev.code.into(); - kbrn_ev = match kev.value { + match kev.value { KeyValue::Press => { if let Some(state) = &mut self.dynamic_macro_record_state { state.macro_items.push(DynamicMacroItem::Press(kev.code)); @@ -469,7 +467,7 @@ impl Kanata { let ret = self.handle_repeat(kev); return ret; } - }; + } } SupportedInputEvent::ScrollEvent(sev) => { let osc: OsCode = match (*sev).try_into() { @@ -481,7 +479,7 @@ impl Kanata { self.layout.bm().event(Event::Release(0, evc)); return Ok(()); } - } + }; self.layout.bm().event(kbrn_ev); Ok(()) diff --git a/src/oskbd/mod.rs b/src/oskbd/mod.rs index e15374748..8beb122df 100644 --- a/src/oskbd/mod.rs +++ b/src/oskbd/mod.rs @@ -80,14 +80,14 @@ impl TryFrom for OsCode { fn try_from(value: ScrollEvent) -> Result { match value.kind { ScrollEventKind::Standard => { - return Ok(match value.direction { + Ok(match value.direction { MoveDirection::Up => OsCode::MouseWheelUp, MoveDirection::Down => OsCode::MouseWheelDown, MoveDirection::Left => OsCode::MouseWheelLeft, MoveDirection::Right => OsCode::MouseWheelRight, }) } - ScrollEventKind::HiRes => return Err(()), + ScrollEventKind::HiRes => Err(()), } } } From 3ee298345bc580948d6c0ae3cc112473f026b7b1 Mon Sep 17 00:00:00 2001 From: rszyma Date: Sun, 22 Oct 2023 06:28:08 +0200 Subject: [PATCH 03/15] cargo fmt --- src/oskbd/mod.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/oskbd/mod.rs b/src/oskbd/mod.rs index 8beb122df..2b59f7cd6 100644 --- a/src/oskbd/mod.rs +++ b/src/oskbd/mod.rs @@ -79,14 +79,12 @@ impl TryFrom for OsCode { type Error = (); fn try_from(value: ScrollEvent) -> Result { match value.kind { - ScrollEventKind::Standard => { - Ok(match value.direction { - MoveDirection::Up => OsCode::MouseWheelUp, - MoveDirection::Down => OsCode::MouseWheelDown, - MoveDirection::Left => OsCode::MouseWheelLeft, - MoveDirection::Right => OsCode::MouseWheelRight, - }) - } + ScrollEventKind::Standard => Ok(match value.direction { + MoveDirection::Up => OsCode::MouseWheelUp, + MoveDirection::Down => OsCode::MouseWheelDown, + MoveDirection::Left => OsCode::MouseWheelLeft, + MoveDirection::Right => OsCode::MouseWheelRight, + }), ScrollEventKind::HiRes => Err(()), } } From a0b9af6724382e9d1906c8d14ec92636f0630816 Mon Sep 17 00:00:00 2001 From: rszyma Date: Wed, 25 Oct 2023 15:37:43 +0200 Subject: [PATCH 04/15] fix mapping mw* -> _ --- parser/src/cfg/mod.rs | 28 ++++++++++ parser/src/custom_action.rs | 3 + src/kanata/linux.rs | 106 ++++++++++++++++++++++++------------ src/kanata/mod.rs | 4 ++ src/oskbd/linux.rs | 16 +++--- src/oskbd/mod.rs | 12 ++-- 6 files changed, 119 insertions(+), 50 deletions(-) diff --git a/parser/src/cfg/mod.rs b/parser/src/cfg/mod.rs index 4feeb332d..a413caf84 100644 --- a/parser/src/cfg/mod.rs +++ b/parser/src/cfg/mod.rs @@ -1209,6 +1209,34 @@ fn parse_action_atom(ac_span: &Spanned, s: &ParsedState) -> Result<&'sta s.a.sref(s.a.sref_slice(CustomAction::MouseTap(Btn::Backward))), ))) } + "mwu" | "mousewheelup" => { + return Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice( + CustomAction::MWheelNotch { + direction: MWheelDirection::Up, + }, + ))))) + } + "mwd" | "mousewheeldown" => { + return Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice( + CustomAction::MWheelNotch { + direction: MWheelDirection::Down, + }, + ))))) + } + "mwl" | "mousewheelleft" => { + return Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice( + CustomAction::MWheelNotch { + direction: MWheelDirection::Left, + }, + ))))) + } + "mwr" | "mousewheelright" => { + return Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice( + CustomAction::MWheelNotch { + direction: MWheelDirection::Right, + }, + ))))) + } "rpt" | "repeat" | "rpt-key" => { return Ok(s.a.sref(Action::Custom( s.a.sref(s.a.sref_slice(CustomAction::Repeat)), diff --git a/parser/src/custom_action.rs b/parser/src/custom_action.rs index 3d94049c2..28143c5d9 100644 --- a/parser/src/custom_action.rs +++ b/parser/src/custom_action.rs @@ -29,6 +29,9 @@ pub enum CustomAction { interval: u16, distance: u16, }, + MWheelNotch { + direction: MWheelDirection, + }, MoveMouse { direction: MoveDirection, interval: u16, diff --git a/src/kanata/linux.rs b/src/kanata/linux.rs index c0bd203c1..d6a8a35b8 100644 --- a/src/kanata/linux.rs +++ b/src/kanata/linux.rs @@ -31,6 +31,12 @@ impl Kanata { Kanata::set_repeat_rate(&k.defcfg_items)?; drop(k); + let scroll_wheel_mapped = false + || MAPPED_KEYS.lock().contains(&OsCode::MouseWheelUp) + || MAPPED_KEYS.lock().contains(&OsCode::MouseWheelDown) + || MAPPED_KEYS.lock().contains(&OsCode::MouseWheelLeft) + || MAPPED_KEYS.lock().contains(&OsCode::MouseWheelRight); + loop { let events = kbd_in.read().map_err(|e| anyhow!("failed read: {}", e))?; log::trace!("{events:?}"); @@ -49,57 +55,85 @@ impl Kanata { } }; - // TODO: Perhaps this should be exposed as config option? - // Hardcoding this would disable hi-res scrolls whenever kanata is running, - // regardless of whether user remaps mouse wheel. - // But allowing passthrough when scroll is mapped might - // show some side effects, such as scroll still working while remapped. - // Another idea is to disable corresponding hi-res scroll - // events for the scroll events that are listed in defsrc. - let allow_hi_res_scroll_events_passthrough = false; + if let SupportedInputEvent::KeyEvent(key_event) = supported_in_event { + check_for_exit(&key_event); + } - let osc: OsCode = match supported_in_event { - SupportedInputEvent::KeyEvent(kev) => kev.code, + match supported_in_event { + SupportedInputEvent::KeyEvent(kev) => { + // Check if this keycode is mapped in the configuration. + // If it hasn't been mapped, send it immediately. + if !MAPPED_KEYS.lock().contains(&kev.code) { + let mut kanata = kanata.lock(); + kanata + .kbd_out + .write_raw(in_event) + .map_err(|e| anyhow!("failed write: {}", e))?; + continue; + }; + } SupportedInputEvent::ScrollEvent( sev @ ScrollEvent { kind: ScrollEventKind::Standard, - .. + direction, + distance, }, - ) => sev - .try_into() - .expect("standard scroll should have OsCode mapping"), - SupportedInputEvent::ScrollEvent(ScrollEvent { - kind: ScrollEventKind::HiRes, - .. - }) => { - if allow_hi_res_scroll_events_passthrough { + ) => { + let osc: OsCode = sev + .try_into() + .expect("standard scroll should have OsCode mapping"); + if scroll_wheel_mapped { + if MAPPED_KEYS.lock().contains(&osc) { + // Send this event to processing loop. + } else { + // We can't simply passthough with `write_raw`, because hi-res events will not + // be passed-through when scroll_wheel_mapped==true. If we just used + // `write_raw` here, some of the scrolls issued by kanata would be + // REL_WHEEL_HI_RES + REL_HWHEEL and some just REL_HWHEEL and an issue + // like this one would happen: https://github.com/jtroo/kanata/issues/395 + // + // So to fix this case, we need to use `scroll` + // which will also send hi-res scrolls along normal scrolls. + let mut kanata = kanata.lock(); + kanata + .kbd_out + .scroll( + direction, + distance as u16 * HI_RES_SCROLL_UNITS_IN_LO_RES, + ) + .map_err(|e| anyhow!("failed write: {}", e))?; + continue; + } + } else { + // Passthrough if none of the scroll wheel events are mapped + // in the configuration. let mut kanata = kanata.lock(); kanata .kbd_out .write_raw(in_event) .map_err(|e| anyhow!("failed write: {}", e))?; + continue; + } + } + SupportedInputEvent::ScrollEvent(ScrollEvent { + kind: ScrollEventKind::HiRes, + .. + }) => { + // Don't passthrough hi-res mouse wheel events when scroll wheel is remapped, + if scroll_wheel_mapped { + continue; } + // Passthrough if none of the scroll wheel events are mapped + // in the configuration. + let mut kanata = kanata.lock(); + kanata + .kbd_out + .write_raw(in_event) + .map_err(|e| anyhow!("failed write: {}", e))?; continue; } }; - // Check if this keycode is mapped in the configuration. If it hasn't been mapped, send - // it immediately. - let is_mapped = MAPPED_KEYS.lock().contains(&osc); - - if let SupportedInputEvent::KeyEvent(key_event) = supported_in_event { - check_for_exit(&key_event); - } - - if !is_mapped { - let mut kanata = kanata.lock(); - kanata - .kbd_out - .write_raw(in_event) - .map_err(|e| anyhow!("failed write: {}", e))?; - continue; - } - // Send key events to the processing loop if let Err(e) = tx.try_send(supported_in_event) { bail!("failed to send on channel: {}", e) diff --git a/src/kanata/mod.rs b/src/kanata/mod.rs index fa675ac52..a10d800e0 100644 --- a/src/kanata/mod.rs +++ b/src/kanata/mod.rs @@ -1004,6 +1004,10 @@ impl Kanata { }) } }, + CustomAction::MWheelNotch { direction } => { + self.kbd_out + .scroll(*direction, HI_RES_SCROLL_UNITS_IN_LO_RES)?; + } CustomAction::MoveMouse { direction, interval, diff --git a/src/oskbd/linux.rs b/src/oskbd/linux.rs index 5dffc5698..8fca869b8 100644 --- a/src/oskbd/linux.rs +++ b/src/oskbd/linux.rs @@ -284,33 +284,33 @@ impl TryFrom for SupportedInputEvent { let (direction, kind) = match (axis_type, item.value()) { (RelativeAxisType::REL_WHEEL, dist) => ( if dist > 0 { - MoveDirection::Up + MWheelDirection::Up } else { - MoveDirection::Down + MWheelDirection::Down }, ScrollEventKind::Standard, ), (RelativeAxisType::REL_HWHEEL, dist) => ( if dist > 0 { - MoveDirection::Left + MWheelDirection::Left } else { - MoveDirection::Right + MWheelDirection::Right }, ScrollEventKind::Standard, ), (RelativeAxisType::REL_WHEEL_HI_RES, dist) => ( if dist > 0 { - MoveDirection::Up + MWheelDirection::Up } else { - MoveDirection::Down + MWheelDirection::Down }, ScrollEventKind::HiRes, ), (RelativeAxisType::REL_HWHEEL_HI_RES, dist) => ( if dist > 0 { - MoveDirection::Left + MWheelDirection::Left } else { - MoveDirection::Right + MWheelDirection::Right }, ScrollEventKind::HiRes, ), diff --git a/src/oskbd/mod.rs b/src/oskbd/mod.rs index 2b59f7cd6..8083c77de 100644 --- a/src/oskbd/mod.rs +++ b/src/oskbd/mod.rs @@ -45,7 +45,7 @@ impl From for bool { } } -use kanata_parser::{custom_action::MoveDirection, keys::OsCode}; +use kanata_parser::{custom_action::MWheelDirection, keys::OsCode}; #[derive(Debug, Clone, Copy)] pub struct KeyEvent { @@ -69,7 +69,7 @@ pub enum ScrollEventKind { #[derive(Debug, Clone, Copy)] pub struct ScrollEvent { pub kind: ScrollEventKind, - pub direction: MoveDirection, + pub direction: MWheelDirection, /// Unit: scroll notches if ScrollEventKind::Standard or /// scroll notches * 120 if ScrollEventKind::HiRes pub distance: u32, @@ -80,10 +80,10 @@ impl TryFrom for OsCode { fn try_from(value: ScrollEvent) -> Result { match value.kind { ScrollEventKind::Standard => Ok(match value.direction { - MoveDirection::Up => OsCode::MouseWheelUp, - MoveDirection::Down => OsCode::MouseWheelDown, - MoveDirection::Left => OsCode::MouseWheelLeft, - MoveDirection::Right => OsCode::MouseWheelRight, + MWheelDirection::Up => OsCode::MouseWheelUp, + MWheelDirection::Down => OsCode::MouseWheelDown, + MWheelDirection::Left => OsCode::MouseWheelLeft, + MWheelDirection::Right => OsCode::MouseWheelRight, }), ScrollEventKind::HiRes => Err(()), } From f9e1c95b45c0180cc3642549bee19a8605daa72a Mon Sep 17 00:00:00 2001 From: rszyma Date: Wed, 25 Oct 2023 15:51:40 +0200 Subject: [PATCH 05/15] fix: swap directions of horizontal scroll --- src/oskbd/linux.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/oskbd/linux.rs b/src/oskbd/linux.rs index 8fca869b8..3119cda26 100644 --- a/src/oskbd/linux.rs +++ b/src/oskbd/linux.rs @@ -292,9 +292,9 @@ impl TryFrom for SupportedInputEvent { ), (RelativeAxisType::REL_HWHEEL, dist) => ( if dist > 0 { - MWheelDirection::Left - } else { MWheelDirection::Right + } else { + MWheelDirection::Left }, ScrollEventKind::Standard, ), @@ -308,9 +308,9 @@ impl TryFrom for SupportedInputEvent { ), (RelativeAxisType::REL_HWHEEL_HI_RES, dist) => ( if dist > 0 { - MWheelDirection::Left - } else { MWheelDirection::Right + } else { + MWheelDirection::Left }, ScrollEventKind::HiRes, ), From f8a41d8c445eca46d0b7ce3ffc57a2a529d400c8 Mon Sep 17 00:00:00 2001 From: rszyma Date: Wed, 25 Oct 2023 16:38:35 +0200 Subject: [PATCH 06/15] fmt: clippy complaining about stuff --- src/kanata/linux.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/kanata/linux.rs b/src/kanata/linux.rs index d6a8a35b8..e040eb881 100644 --- a/src/kanata/linux.rs +++ b/src/kanata/linux.rs @@ -31,8 +31,7 @@ impl Kanata { Kanata::set_repeat_rate(&k.defcfg_items)?; drop(k); - let scroll_wheel_mapped = false - || MAPPED_KEYS.lock().contains(&OsCode::MouseWheelUp) + let scroll_wheel_mapped = MAPPED_KEYS.lock().contains(&OsCode::MouseWheelUp) || MAPPED_KEYS.lock().contains(&OsCode::MouseWheelDown) || MAPPED_KEYS.lock().contains(&OsCode::MouseWheelLeft) || MAPPED_KEYS.lock().contains(&OsCode::MouseWheelRight); From 8fa8ec2ba5a0465a95fe816e47f5d7fc69079b72 Mon Sep 17 00:00:00 2001 From: rszyma Date: Thu, 26 Oct 2023 23:22:39 +0200 Subject: [PATCH 07/15] refactor: revert SupportedInputEvent to KeyEvent in processing loop --- src/kanata/linux.rs | 7 +++-- src/kanata/mod.rs | 75 +++++++++++++++++++-------------------------- src/oskbd/mod.rs | 14 +++++++++ 3 files changed, 50 insertions(+), 46 deletions(-) diff --git a/src/kanata/linux.rs b/src/kanata/linux.rs index e040eb881..340302daa 100644 --- a/src/kanata/linux.rs +++ b/src/kanata/linux.rs @@ -10,7 +10,7 @@ use super::*; impl Kanata { /// Enter an infinite loop that listens for OS key events and sends them to the processing /// thread. - pub fn event_loop(kanata: Arc>, tx: Sender) -> Result<()> { + pub fn event_loop(kanata: Arc>, tx: Sender) -> Result<()> { info!("entering the event loop"); let k = kanata.lock(); @@ -134,7 +134,10 @@ impl Kanata { }; // Send key events to the processing loop - if let Err(e) = tx.try_send(supported_in_event) { + if let Err(e) = tx.try_send(supported_in_event.try_into().expect( + "only hi-res scroll can fail this conversion, + but this should be unreachable for it", + )) { bail!("failed to send on channel: {}", e) } } diff --git a/src/kanata/mod.rs b/src/kanata/mod.rs index a10d800e0..624d12634 100644 --- a/src/kanata/mod.rs +++ b/src/kanata/mod.rs @@ -443,44 +443,35 @@ impl Kanata { } /// Update keyberon layout state for press/release, handle repeat separately - fn handle_input_event(&mut self, event: &SupportedInputEvent) -> Result<()> { + fn handle_input_event(&mut self, event: &KeyEvent) -> Result<()> { log::debug!("process recv ev {event:?}"); + let evc: u16 = event.code.into(); self.ticks_since_idle = 0; - - let kbrn_ev = match event { - SupportedInputEvent::KeyEvent(kev) => { - let evc: u16 = kev.code.into(); - match kev.value { - KeyValue::Press => { - if let Some(state) = &mut self.dynamic_macro_record_state { - state.macro_items.push(DynamicMacroItem::Press(kev.code)); - } - Event::Press(0, evc) - } - KeyValue::Release => { - if let Some(state) = &mut self.dynamic_macro_record_state { - state.macro_items.push(DynamicMacroItem::Release(kev.code)); - } - Event::Release(0, evc) - } - KeyValue::Repeat => { - let ret = self.handle_repeat(kev); - return ret; - } + let kbrn_ev = match event.value { + KeyValue::Press => { + if let Some(state) = &mut self.dynamic_macro_record_state { + state.macro_items.push(DynamicMacroItem::Press(event.code)); } + Event::Press(0, evc) } - SupportedInputEvent::ScrollEvent(sev) => { - let osc: OsCode = match (*sev).try_into() { - Ok(code) => code, - Err(_) => return Ok(()), - }; - let evc: u16 = osc.into(); + KeyValue::Release => { + if let Some(state) = &mut self.dynamic_macro_record_state { + state + .macro_items + .push(DynamicMacroItem::Release(event.code)); + } + Event::Release(0, evc) + } + KeyValue::Repeat => { + let ret = self.handle_repeat(event); + return ret; + } + KeyValue::Tap => { self.layout.bm().event(Event::Press(0, evc)); self.layout.bm().event(Event::Release(0, evc)); return Ok(()); } }; - self.layout.bm().event(kbrn_ev); Ok(()) } @@ -1549,7 +1540,7 @@ impl Kanata { /// Starts a new thread that processes OS key events and advances the keyberon layout's state. pub fn start_processing_loop( kanata: Arc>, - rx: Receiver, + rx: Receiver, tx: Option>, nodelay: bool, ) { @@ -1558,15 +1549,11 @@ impl Kanata { if !nodelay { info!("Init: catching only releases and sending immediately"); for _ in 0..500 { - if let Ok(SupportedInputEvent::KeyEvent(KeyEvent { - code, - value: KeyValue::Release, - })) = rx.try_recv() - { - { + if let Ok(kev) = rx.try_recv() { + if kev.value == KeyValue::Release { let mut k = kanata.lock(); - info!("Init: releasing {:?}", code); - k.kbd_out.release_key(code).expect("key released"); + info!("Init: releasing {:?}", kev.code); + k.kbd_out.release_key(kev.code).expect("key released"); } } std::thread::sleep(time::Duration::from_millis(1)); @@ -1596,7 +1583,7 @@ impl Kanata { if can_block { log::trace!("blocking on channel"); match rx.recv() { - Ok(ev) => { + Ok(kev) => { let mut k = kanata.lock(); let now = time::Instant::now() .checked_sub(time::Duration::from_millis(1)) @@ -1622,8 +1609,8 @@ impl Kanata { // are not cleared. if (now - k.last_tick) > time::Duration::from_secs(60) { log::debug!( - "clearing keyberon normal key states due to blocking for a while" - ); + "clearing keyberon normal key states due to blocking for a while" + ); k.layout.bm().states.retain(|s| { !matches!( s, @@ -1649,7 +1636,7 @@ impl Kanata { #[cfg(feature = "perf_logging")] let start = std::time::Instant::now(); - if let Err(e) = k.handle_input_event(&ev) { + if let Err(e) = k.handle_input_event(&kev) { break e; } @@ -1680,11 +1667,11 @@ impl Kanata { } else { let mut k = kanata.lock(); match rx.try_recv() { - Ok(ev) => { + Ok(kev) => { #[cfg(feature = "perf_logging")] let start = std::time::Instant::now(); - if let Err(e) = k.handle_input_event(&ev) { + if let Err(e) = k.handle_input_event(&kev) { break e; } diff --git a/src/oskbd/mod.rs b/src/oskbd/mod.rs index 8083c77de..c8be937a4 100644 --- a/src/oskbd/mod.rs +++ b/src/oskbd/mod.rs @@ -17,6 +17,7 @@ pub enum KeyValue { Release = 0, Press = 1, Repeat = 2, + Tap, } impl From for KeyValue { @@ -95,3 +96,16 @@ pub enum SupportedInputEvent { KeyEvent(KeyEvent), ScrollEvent(ScrollEvent), } + +impl TryFrom for KeyEvent { + type Error = (); + fn try_from(value: SupportedInputEvent) -> Result { + Ok(match value { + SupportedInputEvent::KeyEvent(kev) => kev, + SupportedInputEvent::ScrollEvent(sev) => KeyEvent { + code: sev.try_into()?, + value: KeyValue::Tap, + }, + }) + } +} From 7e646fb853cb4934627cda731a4c71791afb81d6 Mon Sep 17 00:00:00 2001 From: jtroo Date: Sat, 28 Oct 2023 23:07:17 -0700 Subject: [PATCH 08/15] make Windows compile. Untested. --- src/kanata/windows/interception.rs | 32 +++++++++++++++++++++++++++++- src/oskbd/windows/interception.rs | 1 + src/oskbd/windows/mod.rs | 3 +++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/kanata/windows/interception.rs b/src/kanata/windows/interception.rs index df45ef34c..8f4412d2d 100644 --- a/src/kanata/windows/interception.rs +++ b/src/kanata/windows/interception.rs @@ -70,13 +70,14 @@ impl Kanata { }; KeyEvent { code, value } } - ic::Stroke::Mouse { state, .. } => { + ic::Stroke::Mouse { state, rolling, .. } => { if let Some(hwid) = mouse_to_intercept_hwid { log::trace!("checking mouse stroke {:?}", strokes[i]); if let Some(event) = mouse_state_to_event( dev, &hwid, state, + rolling, &intrcptn, &mut is_dev_interceptable, ) { @@ -123,6 +124,7 @@ fn mouse_state_to_event( input_dev: ic::Device, allowed_hwid: &[u8; HWID_ARR_SZ], state: ic::MouseState, + rolling: i16, intrcptn: &ic::Interception, is_dev_interceptable: &mut HashMap, ) -> Option { @@ -191,6 +193,34 @@ fn mouse_state_to_event( code: OsCode::BTN_EXTRA, value: KeyValue::Release, }) + } else if state.contains(ic::MouseState::WHEEL) { + let osc = if rolling >= 0 { + OsCode::MouseWheelUp + } else { + OsCode::MouseWheelDown + }; + if !MAPPED_KEYS.lock().contains(&osc) { + Some(KeyEvent { + code: osc, + value: KeyValue::Tap, + }) + } else { + None + } + } else if state.contains(ic::MouseState::HWHEEL) { + let osc = if rolling >= 0 { + OsCode::MouseWheelRight + } else { + OsCode::MouseWheelLeft + }; + if !MAPPED_KEYS.lock().contains(&osc) { + Some(KeyEvent { + code: osc, + value: KeyValue::Tap, + }) + } else { + None + } } else { None } diff --git a/src/oskbd/windows/interception.rs b/src/oskbd/windows/interception.rs index 672f4c9d7..434575345 100644 --- a/src/oskbd/windows/interception.rs +++ b/src/oskbd/windows/interception.rs @@ -24,6 +24,7 @@ impl InputEvent { match val { KeyValue::Press | KeyValue::Repeat => KeyState::DOWN, KeyValue::Release => KeyState::UP, + KeyValue::Tap => panic!("invalid value attempted to be sent"), }, true, ); diff --git a/src/oskbd/windows/mod.rs b/src/oskbd/windows/mod.rs index d9c952999..fae97fbf1 100644 --- a/src/oskbd/windows/mod.rs +++ b/src/oskbd/windows/mod.rs @@ -20,6 +20,8 @@ pub use self::interception::*; #[cfg(feature = "interception_driver")] pub use interception_convert::*; +pub const HI_RES_SCROLL_UNITS_IN_LO_RES: u16 = 120; + fn send_uc(c: char, up: bool) { log::debug!("sending unicode {c}"); let mut inputs: [INPUT; 2] = unsafe { mem::zeroed() }; @@ -54,6 +56,7 @@ fn write_code(code: u16, value: KeyValue) -> Result<(), std::io::Error> { match value { KeyValue::Press | KeyValue::Repeat => false, KeyValue::Release => true, + KeyValue::Tap => panic!("invalid value attempted to be sent"), }, ); Ok(()) From 37f16129572fce319bc9c700de2f2c45f7bc359b Mon Sep 17 00:00:00 2001 From: rszyma Date: Sun, 29 Oct 2023 15:12:04 +0100 Subject: [PATCH 09/15] fix: scroll_wheel_mapped not updated after live-reload --- src/kanata/linux.rs | 9 ++------- src/kanata/mod.rs | 11 +++++++++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/kanata/linux.rs b/src/kanata/linux.rs index 340302daa..5fe908e7c 100644 --- a/src/kanata/linux.rs +++ b/src/kanata/linux.rs @@ -31,11 +31,6 @@ impl Kanata { Kanata::set_repeat_rate(&k.defcfg_items)?; drop(k); - let scroll_wheel_mapped = MAPPED_KEYS.lock().contains(&OsCode::MouseWheelUp) - || MAPPED_KEYS.lock().contains(&OsCode::MouseWheelDown) - || MAPPED_KEYS.lock().contains(&OsCode::MouseWheelLeft) - || MAPPED_KEYS.lock().contains(&OsCode::MouseWheelRight); - loop { let events = kbd_in.read().map_err(|e| anyhow!("failed read: {}", e))?; log::trace!("{events:?}"); @@ -81,7 +76,7 @@ impl Kanata { let osc: OsCode = sev .try_into() .expect("standard scroll should have OsCode mapping"); - if scroll_wheel_mapped { + if kanata.lock().scroll_wheel_mapped { if MAPPED_KEYS.lock().contains(&osc) { // Send this event to processing loop. } else { @@ -119,7 +114,7 @@ impl Kanata { .. }) => { // Don't passthrough hi-res mouse wheel events when scroll wheel is remapped, - if scroll_wheel_mapped { + if kanata.lock().scroll_wheel_mapped { continue; } // Passthrough if none of the scroll wheel events are mapped diff --git a/src/kanata/mod.rs b/src/kanata/mod.rs index 624d12634..53dbd8799 100644 --- a/src/kanata/mod.rs +++ b/src/kanata/mod.rs @@ -154,6 +154,7 @@ pub struct Kanata { /// gets stored in this buffer and if the next movemouse action is opposite axis /// than the one stored in the buffer, both events are outputted at the same time. movemouse_buffer: Option<(Axis, CalculatedMouseMove)>, + scroll_wheel_mapped: bool, } #[derive(PartialEq, Clone, Copy)] @@ -333,6 +334,11 @@ impl Kanata { .map(|s| !FALSE_VALUES.contains(&s.to_lowercase().as_str())) .unwrap_or(true); + let scroll_wheel_mapped = cfg.mapped_keys.contains(&OsCode::MouseWheelUp) + || cfg.mapped_keys.contains(&OsCode::MouseWheelDown) + || cfg.mapped_keys.contains(&OsCode::MouseWheelLeft) + || cfg.mapped_keys.contains(&OsCode::MouseWheelRight); + *MAPPED_KEYS.lock() = cfg.mapped_keys; Ok(Self { @@ -392,6 +398,7 @@ impl Kanata { waiting_for_idle: HashSet::default(), ticks_since_idle: 0, movemouse_buffer: None, + scroll_wheel_mapped, }) } @@ -436,6 +443,10 @@ impl Kanata { .get("movemouse-inherit-accel-state") .map(|s| TRUE_VALUES.contains(&s.to_lowercase().as_str())) .unwrap_or_default(); + self.scroll_wheel_mapped = cfg.mapped_keys.contains(&OsCode::MouseWheelUp) + || cfg.mapped_keys.contains(&OsCode::MouseWheelDown) + || cfg.mapped_keys.contains(&OsCode::MouseWheelLeft) + || cfg.mapped_keys.contains(&OsCode::MouseWheelRight); *MAPPED_KEYS.lock() = cfg.mapped_keys; Kanata::set_repeat_rate(&cfg.items)?; log::info!("Live reload successful"); From f8e2c04e3ffc07b77dd526f7f4d2f08d1bdddc66 Mon Sep 17 00:00:00 2001 From: rszyma Date: Sun, 29 Oct 2023 16:41:32 +0100 Subject: [PATCH 10/15] refactor: remove SupportedInputEvent --- parser/src/custom_action.rs | 16 +++++ parser/src/keys/mod.rs | 5 ++ src/kanata/linux.rs | 116 ++++++++++++++++++------------------ src/oskbd/linux.rs | 71 +++++++++------------- src/oskbd/mod.rs | 22 +++---- 5 files changed, 114 insertions(+), 116 deletions(-) diff --git a/parser/src/custom_action.rs b/parser/src/custom_action.rs index 28143c5d9..b4592ef73 100644 --- a/parser/src/custom_action.rs +++ b/parser/src/custom_action.rs @@ -6,6 +6,8 @@ use anyhow::{anyhow, Result}; use kanata_keyberon::key_code::KeyCode; +use crate::keys::OsCode; + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum CustomAction { Cmd(Vec), @@ -104,6 +106,20 @@ pub enum MWheelDirection { Right, } +impl TryFrom for MWheelDirection { + type Error = (); + fn try_from(value: OsCode) -> Result { + use OsCode::*; + Ok(match value { + MouseWheelUp | MouseWheelUpHiRes => MWheelDirection::Up, + MouseWheelDown | MouseWheelDownHiRes => MWheelDirection::Down, + MouseWheelLeft | MouseWheelLeftHiRes => MWheelDirection::Left, + MouseWheelRight | MouseWheelRightHiRes => MWheelDirection::Right, + _ => return Err(()), + }) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum MoveDirection { Up, diff --git a/parser/src/keys/mod.rs b/parser/src/keys/mod.rs index 80313d61c..5f8c51d74 100644 --- a/parser/src/keys/mod.rs +++ b/parser/src/keys/mod.rs @@ -1038,6 +1038,11 @@ pub enum OsCode { MouseWheelLeft = 747, MouseWheelRight = 748, + MouseWheelUpHiRes = 749, + MouseWheelDownHiRes = 750, + MouseWheelLeftHiRes = 751, + MouseWheelRightHiRes = 752, + KEY_MAX = 767, } diff --git a/src/kanata/linux.rs b/src/kanata/linux.rs index 5fe908e7c..9143c1202 100644 --- a/src/kanata/linux.rs +++ b/src/kanata/linux.rs @@ -36,7 +36,7 @@ impl Kanata { log::trace!("{events:?}"); for in_event in events.into_iter() { - let supported_in_event = match SupportedInputEvent::try_from(in_event) { + let key_event = match KeyEvent::try_from(in_event) { Ok(ev) => ev, _ => { // Pass-through non-key and non-scroll events @@ -49,56 +49,61 @@ impl Kanata { } }; - if let SupportedInputEvent::KeyEvent(key_event) = supported_in_event { - check_for_exit(&key_event); - } + check_for_exit(&key_event); - match supported_in_event { - SupportedInputEvent::KeyEvent(kev) => { - // Check if this keycode is mapped in the configuration. - // If it hasn't been mapped, send it immediately. - if !MAPPED_KEYS.lock().contains(&kev.code) { - let mut kanata = kanata.lock(); - kanata - .kbd_out - .write_raw(in_event) - .map_err(|e| anyhow!("failed write: {}", e))?; - continue; - }; - } - SupportedInputEvent::ScrollEvent( - sev @ ScrollEvent { - kind: ScrollEventKind::Standard, - direction, - distance, - }, - ) => { - let osc: OsCode = sev - .try_into() - .expect("standard scroll should have OsCode mapping"); - if kanata.lock().scroll_wheel_mapped { - if MAPPED_KEYS.lock().contains(&osc) { - // Send this event to processing loop. + let KeyEvent { code, value } = key_event; + + if value == KeyValue::Tap { + // Scroll event for sure. Only scroll events produce Tap. + + let direction: MWheelDirection = code.try_into().unwrap(); + + match code + .try_into() + .expect("scroll event OsCode should not fail this") + { + ScrollEventKind::Standard => { + if kanata.lock().scroll_wheel_mapped { + if MAPPED_KEYS.lock().contains(&code) { + // Send this event to processing loop. + } else { + // We can't simply passthough with `write_raw`, because hi-res events will not + // be passed-through when scroll_wheel_mapped==true. If we just used + // `write_raw` here, some of the scrolls issued by kanata would be + // REL_WHEEL_HI_RES + REL_HWHEEL and some just REL_HWHEEL and an issue + // like this one would happen: https://github.com/jtroo/kanata/issues/395 + // + // So to fix this case, we need to use `scroll` + // which will also send hi-res scrolls along normal scrolls. + + let scroll_distance = in_event.value().unsigned_abs() as u16; + + let mut kanata = kanata.lock(); + kanata + .kbd_out + .scroll( + direction, + scroll_distance * HI_RES_SCROLL_UNITS_IN_LO_RES, + ) + .map_err(|e| anyhow!("failed write: {}", e))?; + continue; + } } else { - // We can't simply passthough with `write_raw`, because hi-res events will not - // be passed-through when scroll_wheel_mapped==true. If we just used - // `write_raw` here, some of the scrolls issued by kanata would be - // REL_WHEEL_HI_RES + REL_HWHEEL and some just REL_HWHEEL and an issue - // like this one would happen: https://github.com/jtroo/kanata/issues/395 - // - // So to fix this case, we need to use `scroll` - // which will also send hi-res scrolls along normal scrolls. + // Passthrough if none of the scroll wheel events are mapped + // in the configuration. let mut kanata = kanata.lock(); kanata .kbd_out - .scroll( - direction, - distance as u16 * HI_RES_SCROLL_UNITS_IN_LO_RES, - ) + .write_raw(in_event) .map_err(|e| anyhow!("failed write: {}", e))?; continue; } - } else { + } + ScrollEventKind::HiRes => { + // Don't passthrough hi-res mouse wheel events when scroll wheel is remapped, + if kanata.lock().scroll_wheel_mapped { + continue; + } // Passthrough if none of the scroll wheel events are mapped // in the configuration. let mut kanata = kanata.lock(); @@ -109,30 +114,23 @@ impl Kanata { continue; } } - SupportedInputEvent::ScrollEvent(ScrollEvent { - kind: ScrollEventKind::HiRes, - .. - }) => { - // Don't passthrough hi-res mouse wheel events when scroll wheel is remapped, - if kanata.lock().scroll_wheel_mapped { - continue; - } - // Passthrough if none of the scroll wheel events are mapped - // in the configuration. + } else { + // Handle normal keypresses. + + // Check if this keycode is mapped in the configuration. + // If it hasn't been mapped, send it immediately. + if !MAPPED_KEYS.lock().contains(&code) { let mut kanata = kanata.lock(); kanata .kbd_out .write_raw(in_event) .map_err(|e| anyhow!("failed write: {}", e))?; continue; - } - }; + }; + } // Send key events to the processing loop - if let Err(e) = tx.try_send(supported_in_event.try_into().expect( - "only hi-res scroll can fail this conversion, - but this should be unreachable for it", - )) { + if let Err(e) = tx.try_send(key_event) { bail!("failed to send on channel: {}", e) } } diff --git a/src/oskbd/linux.rs b/src/oskbd/linux.rs index 3119cda26..26196e6d2 100644 --- a/src/oskbd/linux.rs +++ b/src/oskbd/linux.rs @@ -262,66 +262,49 @@ pub fn is_input_device(device: &Device) -> bool { impl TryFrom for KeyEvent { type Error = (); fn try_from(item: InputEvent) -> Result { + use OsCode::*; match item.kind() { evdev::InputEventKind::Key(k) => Ok(Self { code: OsCode::from_u16(k.0).ok_or(())?, value: KeyValue::from(item.value()), }), - _ => Err(()), - } - } -} - -impl TryFrom for SupportedInputEvent { - type Error = (); - fn try_from(item: InputEvent) -> Result { - match item.kind() { - evdev::InputEventKind::Key(k) => Ok(Self::KeyEvent(KeyEvent { - code: OsCode::from_u16(k.0).ok_or(())?, - value: KeyValue::from(item.value()), - })), evdev::InputEventKind::RelAxis(axis_type) => { - let (direction, kind) = match (axis_type, item.value()) { - (RelativeAxisType::REL_WHEEL, dist) => ( + let dist = item.value(); + let code: OsCode = match axis_type { + RelativeAxisType::REL_WHEEL => { if dist > 0 { - MWheelDirection::Up + MouseWheelUp } else { - MWheelDirection::Down - }, - ScrollEventKind::Standard, - ), - (RelativeAxisType::REL_HWHEEL, dist) => ( + MouseWheelDown + } + } + RelativeAxisType::REL_HWHEEL => { if dist > 0 { - MWheelDirection::Right + MouseWheelRight } else { - MWheelDirection::Left - }, - ScrollEventKind::Standard, - ), - (RelativeAxisType::REL_WHEEL_HI_RES, dist) => ( + MouseWheelLeft + } + } + RelativeAxisType::REL_WHEEL_HI_RES => { if dist > 0 { - MWheelDirection::Up + MouseWheelUpHiRes } else { - MWheelDirection::Down - }, - ScrollEventKind::HiRes, - ), - (RelativeAxisType::REL_HWHEEL_HI_RES, dist) => ( + MouseWheelDownHiRes + } + } + RelativeAxisType::REL_HWHEEL_HI_RES => { if dist > 0 { - MWheelDirection::Right + MouseWheelRightHiRes } else { - MWheelDirection::Left - }, - ScrollEventKind::HiRes, - ), + MouseWheelLeftHiRes + } + } _ => return Err(()), }; - - Ok(Self::ScrollEvent(ScrollEvent { - kind, - direction, - distance: item.value().unsigned_abs(), - })) + Ok(KeyEvent { + code, + value: KeyValue::Tap, + }) } _ => Err(()), } diff --git a/src/oskbd/mod.rs b/src/oskbd/mod.rs index c8be937a4..90227f7e5 100644 --- a/src/oskbd/mod.rs +++ b/src/oskbd/mod.rs @@ -91,21 +91,17 @@ impl TryFrom for OsCode { } } -#[derive(Debug, Clone, Copy)] -pub enum SupportedInputEvent { - KeyEvent(KeyEvent), - ScrollEvent(ScrollEvent), -} - -impl TryFrom for KeyEvent { +impl TryFrom for ScrollEventKind { type Error = (); - fn try_from(value: SupportedInputEvent) -> Result { + fn try_from(value: OsCode) -> Result { + use OsCode::*; Ok(match value { - SupportedInputEvent::KeyEvent(kev) => kev, - SupportedInputEvent::ScrollEvent(sev) => KeyEvent { - code: sev.try_into()?, - value: KeyValue::Tap, - }, + MouseWheelUp | MouseWheelDown | MouseWheelLeft | MouseWheelRight => { + ScrollEventKind::Standard + } + MouseWheelUpHiRes | MouseWheelDownHiRes | MouseWheelLeftHiRes + | MouseWheelRightHiRes => ScrollEventKind::HiRes, + _ => return Err(()), }) } } From e381af017924cf960968c5ef491e85a066ca3fd2 Mon Sep 17 00:00:00 2001 From: rszyma Date: Sun, 29 Oct 2023 16:49:33 +0100 Subject: [PATCH 11/15] remove ScrollEvent --- src/oskbd/mod.rs | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/oskbd/mod.rs b/src/oskbd/mod.rs index 90227f7e5..2f90e0f06 100644 --- a/src/oskbd/mod.rs +++ b/src/oskbd/mod.rs @@ -67,30 +67,6 @@ pub enum ScrollEventKind { HiRes, } -#[derive(Debug, Clone, Copy)] -pub struct ScrollEvent { - pub kind: ScrollEventKind, - pub direction: MWheelDirection, - /// Unit: scroll notches if ScrollEventKind::Standard or - /// scroll notches * 120 if ScrollEventKind::HiRes - pub distance: u32, -} - -impl TryFrom for OsCode { - type Error = (); - fn try_from(value: ScrollEvent) -> Result { - match value.kind { - ScrollEventKind::Standard => Ok(match value.direction { - MWheelDirection::Up => OsCode::MouseWheelUp, - MWheelDirection::Down => OsCode::MouseWheelDown, - MWheelDirection::Left => OsCode::MouseWheelLeft, - MWheelDirection::Right => OsCode::MouseWheelRight, - }), - ScrollEventKind::HiRes => Err(()), - } - } -} - impl TryFrom for ScrollEventKind { type Error = (); fn try_from(value: OsCode) -> Result { From 15e394959f53e5e5148286ef337f9d1e0a1a0837 Mon Sep 17 00:00:00 2001 From: rszyma Date: Mon, 30 Oct 2023 00:40:16 +0100 Subject: [PATCH 12/15] add scroll mappings to interception --- parser/src/keys/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/parser/src/keys/mod.rs b/parser/src/keys/mod.rs index 5f8c51d74..7d46dc876 100644 --- a/parser/src/keys/mod.rs +++ b/parser/src/keys/mod.rs @@ -242,14 +242,14 @@ pub fn str_to_oscode(s: &str) -> Option { "mfwd" | "mouseforward" => OsCode::BTN_EXTRA, "mbck" | "mousebackward" => OsCode::BTN_SIDE, - // NOTE: these are linux-only right now due to missing implementation for windows - #[cfg(any(target_os = "linux", target_os = "unknown"))] + // NOTE: these are linux and interception-only due to missing implementation for LLHOOK + #[cfg(any(target_os = "linux", target_os = "unknown", feature = "interception_driver"))] "mwu" | "mousewheelup" => OsCode::MouseWheelUp, - #[cfg(any(target_os = "linux", target_os = "unknown"))] + #[cfg(any(target_os = "linux", target_os = "unknown", feature = "interception_driver"))] "mwd" | "mousewheeldown" => OsCode::MouseWheelDown, - #[cfg(any(target_os = "linux", target_os = "unknown"))] + #[cfg(any(target_os = "linux", target_os = "unknown", feature = "interception_driver"))] "mwl" | "mousewheelleft" => OsCode::MouseWheelLeft, - #[cfg(any(target_os = "linux", target_os = "unknown"))] + #[cfg(any(target_os = "linux", target_os = "unknown", feature = "interception_driver"))] "mwr" | "mousewheelright" => OsCode::MouseWheelRight, "hmpg" | "homepage" => OsCode::KEY_HOMEPAGE, From 8ae0b829193f7075c292e955a2eb261fcde7cd1d Mon Sep 17 00:00:00 2001 From: Jan Tache Date: Mon, 30 Oct 2023 09:51:18 -0700 Subject: [PATCH 13/15] refactor: simplify code, remove extra bits --- parser/src/custom_action.rs | 8 +-- parser/src/keys/mod.rs | 5 -- src/kanata/linux.rs | 133 +++++++++++++++++++----------------- src/kanata/mod.rs | 11 --- src/oskbd/linux.rs | 18 +---- src/oskbd/mod.rs | 23 +------ 6 files changed, 76 insertions(+), 122 deletions(-) diff --git a/parser/src/custom_action.rs b/parser/src/custom_action.rs index b4592ef73..5ea9baa84 100644 --- a/parser/src/custom_action.rs +++ b/parser/src/custom_action.rs @@ -111,10 +111,10 @@ impl TryFrom for MWheelDirection { fn try_from(value: OsCode) -> Result { use OsCode::*; Ok(match value { - MouseWheelUp | MouseWheelUpHiRes => MWheelDirection::Up, - MouseWheelDown | MouseWheelDownHiRes => MWheelDirection::Down, - MouseWheelLeft | MouseWheelLeftHiRes => MWheelDirection::Left, - MouseWheelRight | MouseWheelRightHiRes => MWheelDirection::Right, + MouseWheelUp => MWheelDirection::Up, + MouseWheelDown => MWheelDirection::Down, + MouseWheelLeft => MWheelDirection::Left, + MouseWheelRight => MWheelDirection::Right, _ => return Err(()), }) } diff --git a/parser/src/keys/mod.rs b/parser/src/keys/mod.rs index 7d46dc876..873014c15 100644 --- a/parser/src/keys/mod.rs +++ b/parser/src/keys/mod.rs @@ -1038,11 +1038,6 @@ pub enum OsCode { MouseWheelLeft = 747, MouseWheelRight = 748, - MouseWheelUpHiRes = 749, - MouseWheelDownHiRes = 750, - MouseWheelLeftHiRes = 751, - MouseWheelRightHiRes = 752, - KEY_MAX = 767, } diff --git a/src/kanata/linux.rs b/src/kanata/linux.rs index 9143c1202..56e899241 100644 --- a/src/kanata/linux.rs +++ b/src/kanata/linux.rs @@ -1,4 +1,5 @@ use anyhow::{anyhow, bail, Result}; +use evdev::{InputEvent, InputEventKind, RelativeAxisType}; use log::info; use parking_lot::Mutex; use std::convert::TryFrom; @@ -35,7 +36,7 @@ impl Kanata { let events = kbd_in.read().map_err(|e| anyhow!("failed read: {}", e))?; log::trace!("{events:?}"); - for in_event in events.into_iter() { + for in_event in events.iter().copied() { let key_event = match KeyEvent::try_from(in_event) { Ok(ev) => ev, _ => { @@ -51,75 +52,16 @@ impl Kanata { check_for_exit(&key_event); - let KeyEvent { code, value } = key_event; - - if value == KeyValue::Tap { + if key_event.value == KeyValue::Tap { // Scroll event for sure. Only scroll events produce Tap. - - let direction: MWheelDirection = code.try_into().unwrap(); - - match code - .try_into() - .expect("scroll event OsCode should not fail this") - { - ScrollEventKind::Standard => { - if kanata.lock().scroll_wheel_mapped { - if MAPPED_KEYS.lock().contains(&code) { - // Send this event to processing loop. - } else { - // We can't simply passthough with `write_raw`, because hi-res events will not - // be passed-through when scroll_wheel_mapped==true. If we just used - // `write_raw` here, some of the scrolls issued by kanata would be - // REL_WHEEL_HI_RES + REL_HWHEEL and some just REL_HWHEEL and an issue - // like this one would happen: https://github.com/jtroo/kanata/issues/395 - // - // So to fix this case, we need to use `scroll` - // which will also send hi-res scrolls along normal scrolls. - - let scroll_distance = in_event.value().unsigned_abs() as u16; - - let mut kanata = kanata.lock(); - kanata - .kbd_out - .scroll( - direction, - scroll_distance * HI_RES_SCROLL_UNITS_IN_LO_RES, - ) - .map_err(|e| anyhow!("failed write: {}", e))?; - continue; - } - } else { - // Passthrough if none of the scroll wheel events are mapped - // in the configuration. - let mut kanata = kanata.lock(); - kanata - .kbd_out - .write_raw(in_event) - .map_err(|e| anyhow!("failed write: {}", e))?; - continue; - } - } - ScrollEventKind::HiRes => { - // Don't passthrough hi-res mouse wheel events when scroll wheel is remapped, - if kanata.lock().scroll_wheel_mapped { - continue; - } - // Passthrough if none of the scroll wheel events are mapped - // in the configuration. - let mut kanata = kanata.lock(); - kanata - .kbd_out - .write_raw(in_event) - .map_err(|e| anyhow!("failed write: {}", e))?; - continue; - } + if !handle_scroll(&kanata, in_event, key_event.code, &events)? { + continue; } } else { // Handle normal keypresses. - // Check if this keycode is mapped in the configuration. // If it hasn't been mapped, send it immediately. - if !MAPPED_KEYS.lock().contains(&code) { + if !MAPPED_KEYS.lock().contains(&key_event.code) { let mut kanata = kanata.lock(); kanata .kbd_out @@ -180,3 +122,66 @@ impl Kanata { Ok(()) } } + +/// Returns true if the scroll event should be sent to the processing loop, otherwise returns +/// false. +fn handle_scroll( + kanata: &Mutex, + in_event: InputEvent, + code: OsCode, + all_events: &[InputEvent], +) -> Result { + let direction: MWheelDirection = code.try_into().unwrap(); + let scroll_distance = in_event.value().unsigned_abs() as u16; + match in_event.kind() { + InputEventKind::RelAxis(axis_type) => { + match axis_type { + RelativeAxisType::REL_WHEEL | RelativeAxisType::REL_HWHEEL => { + if MAPPED_KEYS.lock().contains(&code) { + return Ok(true); + } + // If we just used `write_raw` here, some of the scrolls issued by kanata would be + // REL_WHEEL_HI_RES + REL_WHEEL and some just REL_WHEEL and an issue like this one + // would happen: https://github.com/jtroo/kanata/issues/395 + // + // So to fix this case, we need to use `scroll` which will also send hi-res scrolls + // along normal scrolls. + // + // However, if this is a normal scroll event, it may be sent alongside a + let mut kanata = kanata.lock(); + if !all_events.iter().any(|ev| { + matches!( + ev.kind(), + InputEventKind::RelAxis( + RelativeAxisType::REL_WHEEL_HI_RES + | RelativeAxisType::REL_HWHEEL_HI_RES + ) + ) + }) { + kanata + .kbd_out + .scroll(direction, scroll_distance * HI_RES_SCROLL_UNITS_IN_LO_RES) + .map_err(|e| anyhow!("failed write: {}", e))?; + } + Ok(false) + } + RelativeAxisType::REL_WHEEL_HI_RES | RelativeAxisType::REL_HWHEEL_HI_RES => { + if !MAPPED_KEYS.lock().contains(&code) { + // Passthrough if the scroll wheel event is not mapped + // in the configuration. + let mut kanata = kanata.lock(); + kanata + .kbd_out + .scroll(direction, scroll_distance) + .map_err(|e| anyhow!("failed write: {}", e))?; + } + // Kanata will not handle high resolution scroll events for now. + // Full notch scrolling only. + Ok(false) + } + _ => unreachable!("expect to be handling a wheel event"), + } + } + _ => unreachable!("expect to be handling a wheel event"), + } +} diff --git a/src/kanata/mod.rs b/src/kanata/mod.rs index 53dbd8799..624d12634 100644 --- a/src/kanata/mod.rs +++ b/src/kanata/mod.rs @@ -154,7 +154,6 @@ pub struct Kanata { /// gets stored in this buffer and if the next movemouse action is opposite axis /// than the one stored in the buffer, both events are outputted at the same time. movemouse_buffer: Option<(Axis, CalculatedMouseMove)>, - scroll_wheel_mapped: bool, } #[derive(PartialEq, Clone, Copy)] @@ -334,11 +333,6 @@ impl Kanata { .map(|s| !FALSE_VALUES.contains(&s.to_lowercase().as_str())) .unwrap_or(true); - let scroll_wheel_mapped = cfg.mapped_keys.contains(&OsCode::MouseWheelUp) - || cfg.mapped_keys.contains(&OsCode::MouseWheelDown) - || cfg.mapped_keys.contains(&OsCode::MouseWheelLeft) - || cfg.mapped_keys.contains(&OsCode::MouseWheelRight); - *MAPPED_KEYS.lock() = cfg.mapped_keys; Ok(Self { @@ -398,7 +392,6 @@ impl Kanata { waiting_for_idle: HashSet::default(), ticks_since_idle: 0, movemouse_buffer: None, - scroll_wheel_mapped, }) } @@ -443,10 +436,6 @@ impl Kanata { .get("movemouse-inherit-accel-state") .map(|s| TRUE_VALUES.contains(&s.to_lowercase().as_str())) .unwrap_or_default(); - self.scroll_wheel_mapped = cfg.mapped_keys.contains(&OsCode::MouseWheelUp) - || cfg.mapped_keys.contains(&OsCode::MouseWheelDown) - || cfg.mapped_keys.contains(&OsCode::MouseWheelLeft) - || cfg.mapped_keys.contains(&OsCode::MouseWheelRight); *MAPPED_KEYS.lock() = cfg.mapped_keys; Kanata::set_repeat_rate(&cfg.items)?; log::info!("Live reload successful"); diff --git a/src/oskbd/linux.rs b/src/oskbd/linux.rs index 26196e6d2..ca9b348d1 100644 --- a/src/oskbd/linux.rs +++ b/src/oskbd/linux.rs @@ -271,34 +271,20 @@ impl TryFrom for KeyEvent { evdev::InputEventKind::RelAxis(axis_type) => { let dist = item.value(); let code: OsCode = match axis_type { - RelativeAxisType::REL_WHEEL => { + RelativeAxisType::REL_WHEEL | RelativeAxisType::REL_WHEEL_HI_RES => { if dist > 0 { MouseWheelUp } else { MouseWheelDown } } - RelativeAxisType::REL_HWHEEL => { + RelativeAxisType::REL_HWHEEL | RelativeAxisType::REL_HWHEEL_HI_RES => { if dist > 0 { MouseWheelRight } else { MouseWheelLeft } } - RelativeAxisType::REL_WHEEL_HI_RES => { - if dist > 0 { - MouseWheelUpHiRes - } else { - MouseWheelDownHiRes - } - } - RelativeAxisType::REL_HWHEEL_HI_RES => { - if dist > 0 { - MouseWheelRightHiRes - } else { - MouseWheelLeftHiRes - } - } _ => return Err(()), }; Ok(KeyEvent { diff --git a/src/oskbd/mod.rs b/src/oskbd/mod.rs index 2f90e0f06..05664ac1c 100644 --- a/src/oskbd/mod.rs +++ b/src/oskbd/mod.rs @@ -46,7 +46,7 @@ impl From for bool { } } -use kanata_parser::{custom_action::MWheelDirection, keys::OsCode}; +use kanata_parser::keys::OsCode; #[derive(Debug, Clone, Copy)] pub struct KeyEvent { @@ -60,24 +60,3 @@ impl KeyEvent { Self { code, value } } } - -#[derive(Debug, Clone, Copy)] -pub enum ScrollEventKind { - Standard, - HiRes, -} - -impl TryFrom for ScrollEventKind { - type Error = (); - fn try_from(value: OsCode) -> Result { - use OsCode::*; - Ok(match value { - MouseWheelUp | MouseWheelDown | MouseWheelLeft | MouseWheelRight => { - ScrollEventKind::Standard - } - MouseWheelUpHiRes | MouseWheelDownHiRes | MouseWheelLeftHiRes - | MouseWheelRightHiRes => ScrollEventKind::HiRes, - _ => return Err(()), - }) - } -} From 2dcd5b5eb38c383951e9d3da14a4afa153dd04a1 Mon Sep 17 00:00:00 2001 From: Jan Tache Date: Mon, 30 Oct 2023 22:12:44 -0700 Subject: [PATCH 14/15] fix comment --- src/kanata/linux.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/kanata/linux.rs b/src/kanata/linux.rs index 56e899241..0497b2269 100644 --- a/src/kanata/linux.rs +++ b/src/kanata/linux.rs @@ -147,7 +147,9 @@ fn handle_scroll( // So to fix this case, we need to use `scroll` which will also send hi-res scrolls // along normal scrolls. // - // However, if this is a normal scroll event, it may be sent alongside a + // However, if this is a normal scroll event, it may be sent alongside a hi-res + // scroll event. In this scenario, the hi-res event should be used to call + // scroll, and not the normal event. Otherwise, too much scrolling will happen. let mut kanata = kanata.lock(); if !all_events.iter().any(|ev| { matches!( From 418f2defe2a0805dcb6e70541f53cabffb03fef2 Mon Sep 17 00:00:00 2001 From: jtroo Date: Fri, 3 Nov 2023 21:36:39 -0700 Subject: [PATCH 15/15] test and fix interception --- src/kanata/windows/interception.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/kanata/windows/interception.rs b/src/kanata/windows/interception.rs index 8f4412d2d..88682dce2 100644 --- a/src/kanata/windows/interception.rs +++ b/src/kanata/windows/interception.rs @@ -38,12 +38,7 @@ impl Kanata { if mouse_to_intercept_hwid.is_some() { intrcptn.set_filter( ic::is_mouse, - ic::Filter::MouseFilter( - ic::MouseState::all() - & (!ic::MouseState::WHEEL) - & (!ic::MouseState::HWHEEL) - & (!ic::MouseState::MOVE), - ), + ic::Filter::MouseFilter(ic::MouseState::all() & (!ic::MouseState::MOVE)), ); } let mut is_dev_interceptable: HashMap = HashMap::default(); @@ -199,7 +194,7 @@ fn mouse_state_to_event( } else { OsCode::MouseWheelDown }; - if !MAPPED_KEYS.lock().contains(&osc) { + if MAPPED_KEYS.lock().contains(&osc) { Some(KeyEvent { code: osc, value: KeyValue::Tap, @@ -213,7 +208,7 @@ fn mouse_state_to_event( } else { OsCode::MouseWheelLeft }; - if !MAPPED_KEYS.lock().contains(&osc) { + if MAPPED_KEYS.lock().contains(&osc) { Some(KeyEvent { code: osc, value: KeyValue::Tap,