From 17d50df19404f31ec71160d9fa4f338109355c70 Mon Sep 17 00:00:00 2001 From: Perseus <55871725+GitPerseus@users.noreply.github.com> Date: Thu, 12 Jan 2023 09:14:56 +0100 Subject: [PATCH] Adds RELATIVE event catcher (#187) close #180 --- src/action.rs | 6 +- src/action_dispatcher.rs | 36 +++++++ src/config/key.rs | 74 ++++++++++++++ src/event.rs | 25 ++++- src/event_handler.rs | 135 ++++++++++++++++++++++++-- src/main.rs | 23 ++--- src/tests.rs | 202 ++++++++++++++++++++++++++++++++++++++- 7 files changed, 471 insertions(+), 30 deletions(-) diff --git a/src/action.rs b/src/action.rs index 8ef3761..fc6673c 100644 --- a/src/action.rs +++ b/src/action.rs @@ -2,13 +2,17 @@ use std::time::Duration; use evdev::InputEvent; -use crate::event::KeyEvent; +use crate::event::{KeyEvent, RelativeEvent}; // Input to ActionDispatcher. This should only contain things that are easily testable. #[derive(Debug)] pub enum Action { // InputEvent (EventType::KEY) sent to evdev KeyEvent(KeyEvent), + // InputEvent (EventType::RELATIVE, NOT mouse movement events) sent to evdev + RelativeEvent(RelativeEvent), + // InputEvent (EventType::RELATIVE, ONLY mouse movement events) a collection of mouse movement sent to evdev + MouseMovementEventCollection(Vec), // InputEvent of any event types. It's discouraged to use this for testing because // we don't have full control over timeval and it's not pattern-matching friendly. InputEvent(InputEvent), diff --git a/src/action_dispatcher.rs b/src/action_dispatcher.rs index 2c2fef4..aff6596 100644 --- a/src/action_dispatcher.rs +++ b/src/action_dispatcher.rs @@ -8,6 +8,7 @@ use nix::sys::signal; use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet}; use std::process::{exit, Command, Stdio}; +use crate::event::RelativeEvent; use crate::{action::Action, event::KeyEvent}; pub struct ActionDispatcher { @@ -29,6 +30,23 @@ impl ActionDispatcher { pub fn on_action(&mut self, action: Action) -> anyhow::Result<()> { match action { Action::KeyEvent(key_event) => self.on_key_event(key_event)?, + Action::RelativeEvent(relative_event) => self.on_relative_event(relative_event)?, + Action::MouseMovementEventCollection(mouse_movement_events) => { + // Sending all mouse movement events at once, unseparated by synchronization events. + self.send_mousemovement_event_batch(mouse_movement_events)?; + + // Mouse movement events need to be sent all at once because they would otherwise be separated by a synchronization event¹, + // which the OS handles differently from two unseparated mouse movement events. + // For example, + // a REL_X event², followed by a SYNCHRONIZATION event, followed by a REL_Y event³, followed by a SYNCHRONIZATION event, + // will move the mouse cursor by a different amount than + // a REL_X event followed by a REL_Y event followed by a SYNCHRONIZATION event. + + // ¹Because Xremap usually sends events one by one through evdev's "emit" function, which adds a synchronization event during each call. + // ²Mouse movement along the X (horizontal) axis. + // ³Mouse movement along the Y (vertical) axis. + } + Action::InputEvent(event) => self.send_event(event)?, Action::Command(command) => self.run_command(command), Action::Delay(duration) => thread::sleep(duration), @@ -41,6 +59,24 @@ impl ActionDispatcher { self.send_event(event) } + fn on_relative_event(&mut self, event: RelativeEvent) -> std::io::Result<()> { + let event = InputEvent::new_now(EventType::RELATIVE, event.code, event.value); + self.send_event(event) + } + + // a function that takes mouse movement events to send in a single batch, unseparated by synchronization events. + fn send_mousemovement_event_batch(&mut self, eventbatch: Vec) -> std::io::Result<()> { + let mut mousemovementbatch: Vec = Vec::new(); + for mouse_movement in eventbatch { + mousemovementbatch.push(InputEvent::new_now( + EventType::RELATIVE, + mouse_movement.code, + mouse_movement.value, + )); + } + self.device.emit(&mousemovementbatch) + } + fn send_event(&mut self, event: InputEvent) -> std::io::Result<()> { if event.event_type() == EventType::KEY { debug!("{}: {:?}", event.value(), Key::new(event.code())) diff --git a/src/config/key.rs b/src/config/key.rs index e07b5ce..a41a148 100644 --- a/src/config/key.rs +++ b/src/config/key.rs @@ -1,3 +1,4 @@ +use crate::event_handler::DISGUISED_EVENT_OFFSETTER; use evdev::Key; use serde::{Deserialize, Deserializer}; use std::error::Error; @@ -47,6 +48,79 @@ pub fn parse_key(input: &str) -> Result> { "SUPER_L" => Key::KEY_LEFTMETA, "WIN_R" => Key::KEY_RIGHTMETA, "WIN_L" => Key::KEY_LEFTMETA, + + // Custom aliases used in config files to represent scancodes for disguised relative events. + // Relative events are disguised into key events with those scancodes, + // and are then sent through modmap and keymap. + // + // These custom aliases are used in config files, like other aliases. + // The difference here is that since these scancodes don't map to any existing name, + // (on purpose, to avoid conflating disguised events and actual key events) + // we need to define them using scancodes instead of existing names. + // + // The DISGUISED_EVENT_OFFSETTER const is used here to make it easy to change the scancodes should it ever be necessary. + // Because configs use name and custom aliases, changing their assigned value doesn't change how to write configs; + // In other words, a config that works when DISGUISED_EVENT_OFFSETTER == 59974 + // will work exactly the same way if DISGUISED_EVENT_OFFSETTER == 46221 + // + // DISGUISED_EVENT_OFFSETTER is also used in tests.rs::verify_disguised_relative_events(), + // to prevent its modification to a number too low or too big. + // + // Cursor movement + "XRIGHTCURSOR" => Key(DISGUISED_EVENT_OFFSETTER), // Cursor right + "XLEFTCURSOR" => Key(DISGUISED_EVENT_OFFSETTER + 1), // Cursor left + "XDOWNCURSOR" => Key(DISGUISED_EVENT_OFFSETTER + 2), // Cursor down + "XUPCURSOR" => Key(DISGUISED_EVENT_OFFSETTER + 3), // Cursor up + // Cursor... forward and backwards? + "XREL_Z_AXIS_1" => Key(DISGUISED_EVENT_OFFSETTER + 4), + "XREL_Z_AXIS_2" => Key(DISGUISED_EVENT_OFFSETTER + 5), + // + // Rotative cursor movement? + "XREL_RX_AXIS_1" => Key(DISGUISED_EVENT_OFFSETTER + 6), // horizontal + "XREL_RX_AXIS_2" => Key(DISGUISED_EVENT_OFFSETTER + 7), + "XREL_RY_AXIS_1" => Key(DISGUISED_EVENT_OFFSETTER + 8), // vertical + "XREL_RY_AXIS_2" => Key(DISGUISED_EVENT_OFFSETTER + 9), + "XREL_RZ_AXIS_1" => Key(DISGUISED_EVENT_OFFSETTER + 10), // Whatever the third dimensional axis is called + "XREL_RZ_AXIS_2" => Key(DISGUISED_EVENT_OFFSETTER + 11), + // + "XRIGHTSCROLL" => Key(DISGUISED_EVENT_OFFSETTER + 12), // Rightscroll + "XLEFTSCROLL" => Key(DISGUISED_EVENT_OFFSETTER + 13), // Leftscroll + // + // ??? + "XREL_DIAL_1" => Key(DISGUISED_EVENT_OFFSETTER + 14), + "XREL_DIAL_2" => Key(DISGUISED_EVENT_OFFSETTER + 15), + // + "XUPSCROLL" => Key(DISGUISED_EVENT_OFFSETTER + 16), // Upscroll + "XDOWNSCROLL" => Key(DISGUISED_EVENT_OFFSETTER + 17), // Downscroll + // + // Something? + "XREL_MISC_1" => Key(DISGUISED_EVENT_OFFSETTER + 18), + "XREL_MISC_2" => Key(DISGUISED_EVENT_OFFSETTER + 19), + "XREL_RESERVED_1" => Key(DISGUISED_EVENT_OFFSETTER + 20), + "XREL_RESERVED_2" => Key(DISGUISED_EVENT_OFFSETTER + 21), + // + // High resolution version of scroll events, sent just after their non-high resolution version. + "XHIRES_UPSCROLL" => Key(DISGUISED_EVENT_OFFSETTER + 22), + "XHIRES_DOWNSCROLL" => Key(DISGUISED_EVENT_OFFSETTER + 23), + "XHIRES_RIGHTSCROLL" => Key(DISGUISED_EVENT_OFFSETTER + 24), + "XHIRES_LEFTSCROLL" => Key(DISGUISED_EVENT_OFFSETTER + 25), + /* Original Relative events and their values for quick reference. + REL_X = 0x00, + REL_Y = 0x01, + REL_Z = 0x02, + REL_RX = 0x03, + REL_RY = 0x04, + REL_RZ = 0x05, + REL_HWHEEL = 0x06, + REL_DIAL = 0x07, + REL_WHEEL = 0x08, + REL_MISC = 0x09, + REL_RESERVED = 0x0a, + REL_WHEEL_HI_RES = 0x0b, + REL_HWHEEL_HI_RES = 0x0c, + */ + // End of custom scancodes + // else _ => Key::KEY_RESERVED, }; diff --git a/src/event.rs b/src/event.rs index 4a14f55..24783c1 100644 --- a/src/event.rs +++ b/src/event.rs @@ -5,6 +5,10 @@ use evdev::{EventType, InputEvent, Key}; pub enum Event { // InputEvent (EventType::KEY) sent from evdev KeyEvent(KeyEvent), + // InputEvent (EventType::Relative) sent from evdev + RelativeEvent(RelativeEvent), + // Any other InputEvent type sent from evdev + OtherEvents(InputEvent), // Timer for nested override reached its timeout OverrideTimeout, } @@ -15,21 +19,27 @@ pub struct KeyEvent { value: KeyValue, } +#[derive(Debug)] +pub struct RelativeEvent { + pub code: u16, + pub value: i32, +} + #[derive(Debug)] pub enum KeyValue { Press, Release, Repeat, } - impl Event { // Convert evdev's raw InputEvent to xremap's internal Event - pub fn new(event: InputEvent) -> Option { + pub fn new(event: InputEvent) -> Event { let event = match event.event_type() { EventType::KEY => Event::KeyEvent(KeyEvent::new_with(event.code(), event.value())), - _ => return None, + EventType::RELATIVE => Event::RelativeEvent(RelativeEvent::new_with(event.code(), event.value())), + _ => Event::OtherEvents(event), }; - Some(event) + event } } @@ -55,6 +65,13 @@ impl KeyEvent { } } +// constructor for relative events. +impl RelativeEvent { + pub fn new_with(code: u16, value: i32) -> RelativeEvent { + RelativeEvent { code, value } + } +} + impl KeyValue { fn new(value: i32) -> Option { let event_value = match value { diff --git a/src/event_handler.rs b/src/event_handler.rs index a62b953..30ff952 100644 --- a/src/event_handler.rs +++ b/src/event_handler.rs @@ -6,7 +6,7 @@ use crate::config::keymap::{build_override_table, OverrideEntry}; use crate::config::keymap_action::KeymapAction; use crate::config::modmap_action::{ModmapAction, MultiPurposeKey, PressReleaseKey}; use crate::config::remap::Remap; -use crate::event::{Event, KeyEvent}; +use crate::event::{Event, KeyEvent, RelativeEvent}; use crate::Config; use evdev::Key; use lazy_static::lazy_static; @@ -17,6 +17,12 @@ use std::collections::{HashMap, HashSet}; use std::error::Error; use std::time::{Duration, Instant}; +// This const is a value used to offset RELATIVE events' scancodes +// so that they correspond to the custom aliases created in config::key::parse_key. +// This offset also prevents resulting scancodes from corresponding to non-Xremap scancodes, +// to prevent conflating disguised relative events with other events. +pub const DISGUISED_EVENT_OFFSETTER: u16 = 59974; + pub struct EventHandler { // Currently pressed modifier keys modifiers: HashSet, @@ -73,16 +79,32 @@ impl EventHandler { } // Handle an Event and return Actions. This should be the only public method of EventHandler. - pub fn on_event(&mut self, event: &Event, config: &Config) -> Result, Box> { - match event { - Event::KeyEvent(key_event) => self.on_key_event(key_event, config), - Event::OverrideTimeout => self.timeout_override(), - }?; + pub fn on_events(&mut self, events: &Vec, config: &Config) -> Result, Box> { + // a vector to collect mouse movement events to be able to send them all at once as one MouseMovementEventCollection. + let mut mouse_movement_collection: Vec = Vec::new(); + for event in events { + match event { + Event::KeyEvent(key_event) => { + self.on_key_event(key_event, config)?; + () + } + Event::RelativeEvent(relative_event) => { + self.on_relative_event(relative_event, &mut mouse_movement_collection, config)? + } + + Event::OtherEvents(event) => self.send_action(Action::InputEvent(*event)), + Event::OverrideTimeout => self.timeout_override()?, + }; + } + // if there is at least one mouse movement event, sending all of them as one MouseMovementEventCollection + if mouse_movement_collection.len() > 0 { + self.send_action(Action::MouseMovementEventCollection(mouse_movement_collection)); + } Ok(self.actions.drain(..).collect()) } // Handle EventType::KEY - fn on_key_event(&mut self, event: &KeyEvent, config: &Config) -> Result<(), Box> { + fn on_key_event(&mut self, event: &KeyEvent, config: &Config) -> Result> { self.application_cache = None; // expire cache let key = Key::new(event.code()); debug!("=> {}: {:?}", event.value(), &key); @@ -98,6 +120,7 @@ impl EventHandler { key_values = self.flush_timeout_keys(key_values); } + let mut send_original_relative_event = false; // Apply keymap for (key, value) in key_values.into_iter() { if config.virtual_modifiers.contains(&key) { @@ -113,8 +136,100 @@ impl EventHandler { continue; } } + // checking if there's a "disguised" key version of a relative event, + // (scancodes equal to and over DISGUISED_EVENT_OFFSETTER are only "disguised" custom events) + // and also if it's the same "key" and value as the one that came in. + if key.code() >= DISGUISED_EVENT_OFFSETTER && (key.code(), value) == (event.code(), event.value()) { + // if it is, setting send_original_relative_event to true to later tell on_relative_event to send the original event. + send_original_relative_event = true; + continue; + } self.send_key(&key, value); } + + // Using the Ok() to send a boolean to on_relative_event, which will be used to decide whether to send the original relative event. + // (True = send the original relative event, false = don't send it.) + Ok(send_original_relative_event) + } + + // Handle EventType::RELATIVE + fn on_relative_event( + &mut self, + event: &RelativeEvent, + mouse_movement_collection: &mut Vec, + config: &Config, + ) -> Result<(), Box> { + // Because a "full" RELATIVE event is only one event, + // it doesn't translate very well into a KEY event (because those have a "press" event and an "unpress" event). + // The solution used here is to send two events for each relative event : + // one for the press "event" and one for the "unpress" event. + + // These consts are used because 'RELEASE'/'PRESS' are better than '0'/'1' at indicating a button release/press. + const RELEASE: i32 = 0; + const PRESS: i32 = 1; + + // All relative events (except maybe those i haven't found information about (REL_DIAL, REL_MISC and REL_RESERVED)) + // can have either a positive value or a negative value. + // A negative value is associated with a different action than the positive value. + // Specifically, negative values are associated with the opposite of the action that would emit a positive value. + // For example, a positive value for a scroll event (REL_WHEEL) comes from an upscroll, while a negative value comes from a downscroll. + let key = match event.value { + // Positive and negative values can be really high because the events are relative, + // so their values are variable, meaning we have to match with all positive/negative values. + // Not sure if there is any relative event with a fixed value. + 1..=i32::MAX => (event.code * 2) + DISGUISED_EVENT_OFFSETTER, + // While some events may appear to have a fixed value, + // events like scrolling will have higher values with more "agressive" scrolling. + + // *2 to create a "gap" between events (since multiplying by two means that all resulting values will be even, the odd numbers between will be missing), + // +1 if the event has a negative value to "fill" the gap (since adding one shifts the parity from even to odd), + // and adding DISGUISED_EVENT_OFFSETTER, + // so that the total as a keycode corresponds to one of the custom aliases that + // are created in config::key::parse_key specifically for these "disguised" relative events. + i32::MIN..=-1 => (event.code * 2) + 1 + DISGUISED_EVENT_OFFSETTER, + + 0 => { + println!("This event has a value of zero : {:?}", event); + // A value of zero would be unexpected for a relative event, + // since changing something by zero is kinda useless. + // Just in case it can actually happen (and also because match arms need the same output type), + // we'll just act like the value of the event was a positive. + (event.code * 2) + DISGUISED_EVENT_OFFSETTER + } + }; + + // Sending a RELATIVE event "disguised" as a "fake" KEY event press to on_key_event. + match self.on_key_event(&KeyEvent::new_with(key, PRESS), config)? { + // the boolean value is from a variable at the end of on_key_event from event_handler, + // used to indicate whether the event got through unchanged. + true => { + // Sending the original RELATIVE event if the "press" version of the "fake" KEY event got through on_key_event unchanged. + let action = RelativeEvent::new_with(event.code, event.value); + if event.code <= 2 { + // If it's a mouse movement event (event.code <= 2), + // it is added to mouse_movement_collection to later be sent alongside all other mouse movement event, + // as a single MouseMovementEventCollection instead of potentially multiple RelativeEvent . + + // Mouse movement events need to be sent all at once because they would otherwise be separated by a synchronization event¹, + // which the OS handles differently from two unseparated mouse movement events. + // For example, a REL_X event², followed by a SYNCHRONIZATION event, followed by a REL_Y event³, followed by a SYNCHRONIZATION event, + // will move the mouse cursor by a different amount than a REL_X followed by a REL_Y followed by a SYNCHRONIZATION. + + // ¹Because Xremap usually sends events one by one through evdev's "emit" function, which adds a synchronization event during each call. + // ²Mouse movement along the X (horizontal) axis. + // ³Mouse movement along the Y (vertical) axis. + mouse_movement_collection.push(action); + } else { + // Otherwise, the event is directly sent as a relative event, to be dispatched like other events. + self.send_action(Action::RelativeEvent(action)); + } + } + false => {} + } + + // Sending the "unpressed" version of the "fake" KEY event. + self.on_key_event(&KeyEvent::new_with(key, RELEASE), config)?; + Ok(()) } @@ -140,7 +255,7 @@ impl EventHandler { } fn send_key(&mut self, key: &Key, value: i32) { - //let event = InputEvent::new(EventType::KEY, key.code(), value); + // let event = InputEvent::new(EventType::KEY, key.code(), value); let event = KeyEvent::new_with(key.code(), value); self.send_action(Action::KeyEvent(event)); } @@ -570,7 +685,7 @@ lazy_static! { ]; } -//--- +// --- fn is_pressed(value: i32) -> bool { value == PRESS || value == REPEAT @@ -581,7 +696,7 @@ static RELEASE: i32 = 0; static PRESS: i32 = 1; static REPEAT: i32 = 2; -//--- +// --- #[derive(Debug)] struct MultiPurposeKeyState { diff --git a/src/main.rs b/src/main.rs index 307f54f..b91cd5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ use crate::config::Config; use crate::device::{device_watcher, get_input_devices, output_device}; use crate::event_handler::EventHandler; -use action::Action; use action_dispatcher::ActionDispatcher; use anyhow::{anyhow, bail, Context}; use clap::{AppSettings, ArgEnum, IntoApp, Parser}; @@ -136,7 +135,9 @@ fn main() -> anyhow::Result<()> { match 'event_loop: loop { let readable_fds = select_readable(input_devices.values(), &watchers, timer_fd)?; if readable_fds.contains(timer_fd) { - if let Err(error) = handle_event(&mut handler, &mut dispatcher, &mut config, Event::OverrideTimeout) { + if let Err(error) = + handle_events(&mut handler, &mut dispatcher, &mut config, vec![Event::OverrideTimeout]) + { println!("Error on remap timeout: {error}") } } @@ -224,28 +225,28 @@ fn handle_input_events( Err((Some(ENODEV), _)) => Ok(false), Err((_, error)) => Err(error).context("Error fetching input events"), Ok(events) => { + let mut input_events: Vec = Vec::new(); for event in events { - if let Some(event) = Event::new(event) { - handle_event(handler, dispatcher, config, event)?; - } else { - dispatcher.on_action(Action::InputEvent(event))?; - } + let event = Event::new(event); + input_events.push(event); } + handle_events(handler, dispatcher, config, input_events)?; + Ok(true) } } } // Handle an Event with EventHandler, and dispatch Actions with ActionDispatcher -fn handle_event( +fn handle_events( handler: &mut EventHandler, dispatcher: &mut ActionDispatcher, config: &mut Config, - event: Event, + events: Vec, ) -> anyhow::Result<()> { let actions = handler - .on_event(&event, config) - .map_err(|e| anyhow!("Failed handling {event:?}:\n {e:?}"))?; + .on_events(&events, config) + .map_err(|e| anyhow!("Failed handling {events:?}:\n {e:?}"))?; for action in actions { dispatcher.on_action(action)?; } diff --git a/src/tests.rs b/src/tests.rs index d2ca7c2..1a322fe 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,3 +1,5 @@ +use evdev::EventType; +use evdev::InputEvent; use evdev::Key; use indoc::indoc; use nix::sys::timerfd::{ClockId, TimerFd, TimerFlags}; @@ -7,7 +9,7 @@ use crate::client::{Client, WMClient}; use crate::{ action::Action, config::{keymap::build_keymap_table, Config}, - event::{Event, KeyEvent, KeyValue}, + event::{Event, KeyEvent, KeyValue, RelativeEvent}, event_handler::EventHandler, }; @@ -48,6 +50,198 @@ fn test_basic_modmap() { ) } +/* Table to see which scancodes/custom key events correspond to which relative events + Original RELATIVE event | scancode | Custom keyname if | Info + | | positive value (+) | negative value (-) | + REL_X | 0 | XRIGHTCURSOR | XLEFTCURSOR | Cursor right and left + REL_Y | 1 | XDOWNCURSOR | XUPCURSOR | Cursor down and up + REL_Z | 2 | XREL_Z_AXIS_1 | XREL_Z_AXIS_2 | Cursor... forward and backwards? + REL_RX | 3 | XREL_RX_AXIS_1 | XREL_RX_AXIS_2 | Horizontally rotative cursor movement? + REL_RY | 4 | XREL_RY_AXIS_1 | XREL_RY_AXIS_2 | Vertical rotative cursor movement? + REL_RZ | 5 | XREL_RZ_AXIS_1 | XREL_RZ_AXIS_2 | "Whatever the third dimensional axis is called" rotative cursor movement? + REL_HWHEEL | 6 | XRIGHTSCROLL | XLEFTSCROLL | Rightscroll and leftscroll + REL_DIAL | 7 | XREL_DIAL_1 | XREL_DIAL_2 | ??? + REL_WHEEL | 8 | XUPSCROLL | XDOWNSCROLL | Upscroll and downscroll + REL_MISC | 9 | XREL_MISC_1 | XREL_MISC_2 | Something? + REL_RESERVED | 10 | XREL_RESERVED_1 | XREL_RESERVED_2 | Something? + REL_WHEEL_HI_RES | 11 | XHIRES_UPSCROLL | XHIRES_DOWNSCROLL | High resolution downscroll and upscroll, sent just after their non-high resolution version + REL_HWHEEL_HI_RES | 12 | XHIRES_RIGHTSCROLL | XHIRES_LEFTSCROLL | High resolution rightcroll and leftscroll, sent just after their non-high resolution version +*/ + +const _POSITIVE: i32 = 1; +const _NEGATIVE: i32 = -1; + +const _REL_X: u16 = 0; +const _REL_Y: u16 = 1; +const _REL_Z: u16 = 2; +const _REL_RX: u16 = 3; +const _REL_RY: u16 = 4; +const _REL_RZ: u16 = 5; +const _REL_HWHEEL: u16 = 6; +const _REL_DIAL: u16 = 7; +const _REL_WHEEL: u16 = 8; +const _REL_MISC: u16 = 9; +const _REL_RESERVED: u16 = 10; +const _REL_WHEEL_HI_RES: u16 = 11; +const _REL_HWHEEL_HI_RES: u16 = 12; + +#[test] +fn test_relative_events() { + assert_actions( + indoc! {" + modmap: + - remap: + XRIGHTCURSOR: b + "}, + vec![Event::RelativeEvent(RelativeEvent::new_with(_REL_X, _POSITIVE))], + vec![ + Action::KeyEvent(KeyEvent::new(Key::KEY_B, KeyValue::Press)), + Action::KeyEvent(KeyEvent::new(Key::KEY_B, KeyValue::Release)), + ], + ) +} + +#[test] +fn verify_disguised_relative_events() { + use crate::event_handler::DISGUISED_EVENT_OFFSETTER; + // Verifies that the event offsetter used to "disguise" relative events into key event + // is a bigger number than the biggest one a scancode had at the time of writing this (26 december 2022) + assert!(0x2e7 < DISGUISED_EVENT_OFFSETTER); + // and that it's not big enough that one of the "disguised" events's scancode would overflow. + // (the largest of those events is equal to DISGUISED_EVENT_OFFSETTER + 25) + assert!(DISGUISED_EVENT_OFFSETTER <= u16::MAX - 25) +} + +#[test] +fn test_mouse_movement_event_accumulation() { + // Tests that mouse movement events correctly get collected to be sent as one MouseMovementEventCollection, + // which is necessary to avoid separating mouse movement events with synchronization events, + // because such a separation would cause a bug with cursor movement. + + // Please refer to test_cursor_behavior_1 and test_cursor_behavior_2 for more information on said bug. + assert_actions( + indoc! {""}, + vec![ + Event::RelativeEvent(RelativeEvent::new_with(_REL_X, _POSITIVE)), + Event::RelativeEvent(RelativeEvent::new_with(_REL_Y, _POSITIVE)), + ], + vec![Action::MouseMovementEventCollection(vec![ + RelativeEvent::new_with(_REL_X, _POSITIVE), + RelativeEvent::new_with(_REL_Y, _POSITIVE), + ])], + ) +} + +#[test] +#[ignore] +// The OS interprets a REL_X event¹ combined with a REL_Y event² differently if they are separated by synchronization event. +// This test and test_cursor_behavior_2 are meant to be run to demonstrate that fact. + +// ¹Mouse movement along the X (horizontal) axis. +// ²Mouse movement along the Y (vertical) axis. + +// The only difference between test_cursor_behavior_1 and test_cursor_behavior_2 is that +// test_cursor_behavior_1 adds a synchronization event between REL_X and REL_Y events that would not normally be there. +// In other words, test_cursor_behavior_2 represents what would occur without Xremap intervention. + +// Here's how to proceed : +// 1 - Move your mouse cursor to the bottom left of your screen. +// 2 - either run this test with sudo privileges or while your environnment is properly set up (https:// github.com/k0kubun/xremap#running-xremap-without-sudo), +// so that your keyboard and/or mouse may be captured. + +// 3 - Press any button (don't move the mouse). +// 4 - Note where the cursor ended up. + +// 5 - Repeat steps 1 through 4 for test_cursor_behavior_2. +// 6 - Notice that the mouse cursor often ends up in a different position than when running test_cursor_behavior_1. + +// +// Notes : +// - Because emitting an event automatcially adds a synchronization event afterwards (see https:// github.com/emberian/evdev/blob/1d020f11b283b0648427a2844b6b980f1a268221/src/uinput.rs#L167), +// Mouse movement events should be batched together when emitted, +// to avoid separating them with a synchronization event. +// +// - Because a mouse will only ever send a maximum of one REL_X and one REL_Y (and maybe one REL_Z for 3D mice?) at once, +// the only point where a synchronization event can be added where it shouldn't by Xremap is between those events, +// meaning this bug is exclusive to diagonal mouse movement. +// +// - The call to std::thread::sleep for five milliseconds is meant to emulate +// the interval between events from a mouse with a frequency of ~200 Hz. +// A lower time interval between events (which would correspond to a mouse with a higher frequency) +// would cause the difference between test_cursor_behavior_1 and test_cursor_behavior_2 to become less noticeable. +// Conversely, a higher time interval would make the difference more noticeable. +// +fn test_cursor_behavior_1() { + use crate::device::InputDevice; + use crate::device::{get_input_devices, output_device}; + // Setup to be able to send events + let mut input_devices = match get_input_devices(&[String::from("/dev/input/event25")], &[], true, false) { + Ok(input_devices) => input_devices, + Err(e) => panic!("Failed to prepare input devices: {}", e), + }; + let mut output_device = match output_device(input_devices.values().next().map(InputDevice::bus_type)) { + Ok(output_device) => output_device, + Err(e) => panic!("Failed to prepare an output device: {}", e), + }; + for input_device in input_devices.values_mut() { + let _unused = input_device.fetch_events().unwrap(); + } + + // Looping 400 times amplifies the difference between test_cursor_behavior_1 and test_cursor_behavior_2 to visible levels. + for _ in 0..400 { + output_device + .emit(&[ + InputEvent::new_now(EventType::RELATIVE, _REL_X, _POSITIVE), + // + // This line is the only difference between test_cursor_behavior_1 and test_cursor_behavior_2. + InputEvent::new(EventType::SYNCHRONIZATION, 0, 0), + // + InputEvent::new_now(EventType::RELATIVE, _REL_Y, _NEGATIVE), + ]) + .unwrap(); + + // Creating a time interval between mouse movement events to simulate a mouse with a frequency of ~200 Hz. + // The smaller the time interval, the smaller the difference between test_cursor_behavior_1 and test_cursor_behavior_2. + std::thread::sleep(Duration::from_millis(5)); + } +} + +#[test] +#[ignore] +// The OS interprets a REL_X event combined with a REL_Y event differently if they are separated by synchronization event. +// This test and test_cursor_behavior_1 are meant to be run to demonstrate that fact. +// Please refer to the comment above test_cursor_behavior_1 for information on how to run these tests. +fn test_cursor_behavior_2() { + use crate::device::InputDevice; + use crate::device::{get_input_devices, output_device}; + // Setup to be able to send events + let mut input_devices = match get_input_devices(&[String::from("/dev/input/event25")], &[], true, false) { + Ok(input_devices) => input_devices, + Err(e) => panic!("Failed to prepare input devices: {}", e), + }; + let mut output_device = match output_device(input_devices.values().next().map(InputDevice::bus_type)) { + Ok(output_device) => output_device, + Err(e) => panic!("Failed to prepare an output device: {}", e), + }; + for input_device in input_devices.values_mut() { + let _unused = input_device.fetch_events().unwrap(); + } + + // Looping 400 times amplifies the difference between test_cursor_behavior_1 and test_cursor_behavior_2 to visible levels. + for _ in 0..400 { + output_device + .emit(&[ + InputEvent::new_now(EventType::RELATIVE, _REL_X, _POSITIVE), + InputEvent::new_now(EventType::RELATIVE, _REL_Y, _NEGATIVE), + ]) + .unwrap(); + + // Creating a time interval between mouse movement events to simulate a mouse with a frequency of ~200 Hz. + // The smaller the time interval, the smaller the difference between test_cursor_behavior_1 and test_cursor_behavior_2. + std::thread::sleep(Duration::from_millis(5)); + } +} + #[test] fn test_interleave_modifiers() { assert_actions( @@ -389,8 +583,8 @@ fn assert_actions_with_current_application( WMClient::new("static", Box::new(StaticClient { current_application })), ); let mut actual: Vec = vec![]; - for event in &events { - actual.append(&mut event_handler.on_event(event, &config).unwrap()); - } + + actual.append(&mut event_handler.on_events(&events, &config).unwrap()); + assert_eq!(format!("{:?}", actions), format!("{:?}", actual)); }