Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add mouse wheel events to defsrc #592

Merged
merged 16 commits into from
Nov 4, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions parser/src/cfg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1209,6 +1209,34 @@ fn parse_action_atom(ac_span: &Spanned<String>, 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)),
Expand Down
19 changes: 19 additions & 0 deletions parser/src/custom_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>),
Expand All @@ -29,6 +31,9 @@ pub enum CustomAction {
interval: u16,
distance: u16,
},
MWheelNotch {
direction: MWheelDirection,
},
MoveMouse {
direction: MoveDirection,
interval: u16,
Expand Down Expand Up @@ -101,6 +106,20 @@ pub enum MWheelDirection {
Right,
}

impl TryFrom<OsCode> for MWheelDirection {
type Error = ();
fn try_from(value: OsCode) -> Result<Self, Self::Error> {
use OsCode::*;
Ok(match value {
MouseWheelUp => MWheelDirection::Up,
MouseWheelDown => MWheelDirection::Down,
MouseWheelLeft => MWheelDirection::Left,
MouseWheelRight => MWheelDirection::Right,
_ => return Err(()),
})
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MoveDirection {
Up,
Expand Down
20 changes: 20 additions & 0 deletions parser/src/keys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,16 @@ pub fn str_to_oscode(s: &str) -> Option<OsCode> {
"mfwd" | "mouseforward" => OsCode::BTN_EXTRA,
"mbck" | "mousebackward" => OsCode::BTN_SIDE,

// 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", feature = "interception_driver"))]
"mwd" | "mousewheeldown" => OsCode::MouseWheelDown,
#[cfg(any(target_os = "linux", target_os = "unknown", feature = "interception_driver"))]
"mwl" | "mousewheelleft" => OsCode::MouseWheelLeft,
#[cfg(any(target_os = "linux", target_os = "unknown", feature = "interception_driver"))]
"mwr" | "mousewheelright" => OsCode::MouseWheelRight,

"hmpg" | "homepage" => OsCode::KEY_HOMEPAGE,
"mdia" | "media" => OsCode::KEY_MEDIA,
"mail" => OsCode::KEY_MAIL,
Expand Down Expand Up @@ -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,
}

Expand Down
96 changes: 85 additions & 11 deletions src/kanata/linux.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -35,11 +36,11 @@ 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,
_ => {
// Pass-through non-key events
// Pass-through non-key and non-scroll events
let mut kanata = kanata.lock();
kanata
.kbd_out
Expand All @@ -51,15 +52,23 @@ impl Kanata {

check_for_exit(&key_event);

// 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 mut kanata = kanata.lock();
kanata
.kbd_out
.write_key(key_event.code, key_event.value)
.map_err(|e| anyhow!("failed write key: {}", e))?;
continue;
if key_event.value == KeyValue::Tap {
// Scroll event for sure. Only scroll events produce Tap.
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(&key_event.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
Expand Down Expand Up @@ -113,3 +122,68 @@ impl Kanata {
Ok(())
}
}

/// Returns true if the scroll event should be sent to the processing loop, otherwise returns
/// false.
fn handle_scroll(
kanata: &Mutex<Kanata>,
in_event: InputEvent,
code: OsCode,
all_events: &[InputEvent],
) -> Result<bool> {
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 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.
jtroo marked this conversation as resolved.
Show resolved Hide resolved
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"),
}
}
19 changes: 14 additions & 5 deletions src/kanata/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ 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: &KeyEvent) -> Result<()> {
log::debug!("process recv ev {event:?}");
let evc: u16 = event.code.into();
self.ticks_since_idle = 0;
Expand All @@ -466,6 +466,11 @@ impl Kanata {
let ret = self.handle_repeat(event);
return ret;
}
KeyValue::Tap => {
jtroo marked this conversation as resolved.
Show resolved Hide resolved
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(())
Expand Down Expand Up @@ -990,6 +995,10 @@ impl Kanata {
})
}
},
CustomAction::MWheelNotch { direction } => {
self.kbd_out
.scroll(*direction, HI_RES_SCROLL_UNITS_IN_LO_RES)?;
}
CustomAction::MoveMouse {
direction,
interval,
Expand Down Expand Up @@ -1600,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,
Expand All @@ -1627,7 +1636,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(&kev) {
break e;
}

Expand Down Expand Up @@ -1662,7 +1671,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(&kev) {
break e;
}

Expand Down
32 changes: 31 additions & 1 deletion src/kanata/windows/interception.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
) {
Expand Down Expand Up @@ -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<ic::Device, bool>,
) -> Option<KeyEvent> {
Expand Down Expand Up @@ -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
}
Expand Down
25 changes: 25 additions & 0 deletions src/oskbd/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,11 +262,36 @@ pub fn is_input_device(device: &Device) -> bool {
impl TryFrom<InputEvent> for KeyEvent {
type Error = ();
fn try_from(item: InputEvent) -> Result<Self, Self::Error> {
use OsCode::*;
match item.kind() {
evdev::InputEventKind::Key(k) => Ok(Self {
code: OsCode::from_u16(k.0).ok_or(())?,
value: KeyValue::from(item.value()),
}),
evdev::InputEventKind::RelAxis(axis_type) => {
let dist = item.value();
let code: OsCode = match axis_type {
RelativeAxisType::REL_WHEEL | RelativeAxisType::REL_WHEEL_HI_RES => {
if dist > 0 {
MouseWheelUp
} else {
MouseWheelDown
}
}
RelativeAxisType::REL_HWHEEL | RelativeAxisType::REL_HWHEEL_HI_RES => {
if dist > 0 {
MouseWheelRight
} else {
MouseWheelLeft
}
}
_ => return Err(()),
};
Ok(KeyEvent {
code,
value: KeyValue::Tap,
})
}
_ => Err(()),
}
}
Expand Down
1 change: 1 addition & 0 deletions src/oskbd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub enum KeyValue {
Release = 0,
Press = 1,
Repeat = 2,
Tap,
}

impl From<i32> for KeyValue {
Expand Down
Loading