mirror of https://github.com/k0kubun/xremap
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
677 lines
28 KiB
Rust
677 lines
28 KiB
Rust
use evdev::EventType;
|
|
use evdev::InputEvent;
|
|
use evdev::Key;
|
|
use indoc::indoc;
|
|
use nix::sys::timerfd::{ClockId, TimerFd, TimerFlags};
|
|
use std::path::Path;
|
|
use std::time::Duration;
|
|
|
|
use crate::client::{Client, WMClient};
|
|
use crate::device::InputDeviceInfo;
|
|
use crate::{
|
|
action::Action,
|
|
config::{keymap::build_keymap_table, Config},
|
|
event::{Event, KeyEvent, KeyValue, RelativeEvent},
|
|
event_handler::EventHandler,
|
|
};
|
|
|
|
struct StaticClient {
|
|
current_application: Option<String>,
|
|
}
|
|
|
|
impl Client for StaticClient {
|
|
fn supported(&mut self) -> bool {
|
|
true
|
|
}
|
|
fn current_window(&mut self) -> Option<String> {
|
|
None
|
|
}
|
|
|
|
fn current_application(&mut self) -> Option<String> {
|
|
self.current_application.clone()
|
|
}
|
|
}
|
|
|
|
fn get_input_device_info<'a>() -> InputDeviceInfo<'a> {
|
|
InputDeviceInfo {
|
|
name: "Some Device",
|
|
path: &Path::new("/dev/input/event0"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_basic_modmap() {
|
|
assert_actions(
|
|
indoc! {"
|
|
modmap:
|
|
- remap:
|
|
a: b
|
|
"},
|
|
vec![
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_A, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_A, KeyValue::Release)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_B, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_B, KeyValue::Release)),
|
|
],
|
|
vec![
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_B, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_B, KeyValue::Release)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_B, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_B, KeyValue::Release)),
|
|
],
|
|
)
|
|
}
|
|
|
|
/* 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(
|
|
get_input_device_info(),
|
|
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(get_input_device_info(), RelativeEvent::new_with(_REL_X, _POSITIVE)),
|
|
Event::RelativeEvent(get_input_device_info(), 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), true) {
|
|
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), true) {
|
|
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(
|
|
indoc! {"
|
|
keymap:
|
|
- remap:
|
|
M-f: C-right
|
|
"},
|
|
vec![
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_F, KeyValue::Press)),
|
|
],
|
|
vec![
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Release)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_RIGHT, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_RIGHT, KeyValue::Release)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
],
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_exact_match_true() {
|
|
assert_actions(
|
|
indoc! {"
|
|
keymap:
|
|
- exact_match: true
|
|
remap:
|
|
M-f: C-right
|
|
"},
|
|
vec![
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_F, KeyValue::Press)),
|
|
],
|
|
vec![
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_F, KeyValue::Press)),
|
|
],
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_exact_match_false() {
|
|
assert_actions(
|
|
indoc! {"
|
|
keymap:
|
|
- exact_match: false
|
|
remap:
|
|
M-f: C-right
|
|
"},
|
|
vec![
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_F, KeyValue::Press)),
|
|
],
|
|
vec![
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Release)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_RIGHT, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_RIGHT, KeyValue::Release)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
],
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_exact_match_default() {
|
|
assert_actions(
|
|
indoc! {"
|
|
keymap:
|
|
- remap:
|
|
M-f: C-right
|
|
"},
|
|
vec![
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_F, KeyValue::Press)),
|
|
],
|
|
vec![
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Release)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_RIGHT, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_RIGHT, KeyValue::Release)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
],
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_exact_match_true_nested() {
|
|
assert_actions(
|
|
indoc! {"
|
|
keymap:
|
|
- exact_match: true
|
|
remap:
|
|
C-x:
|
|
remap:
|
|
h: C-a
|
|
"},
|
|
vec![
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Release)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_H, KeyValue::Press)),
|
|
],
|
|
vec![
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Release)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_H, KeyValue::Press)),
|
|
],
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_exact_match_false_nested() {
|
|
assert_actions(
|
|
indoc! {"
|
|
keymap:
|
|
- exact_match: false
|
|
remap:
|
|
C-x:
|
|
remap:
|
|
h: C-a
|
|
"},
|
|
vec![
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Release)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_H, KeyValue::Press)),
|
|
],
|
|
vec![
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Release)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_A, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_A, KeyValue::Release)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
],
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_application_override() {
|
|
let config = indoc! {"
|
|
keymap:
|
|
|
|
- name: firefox
|
|
application:
|
|
only: [firefox]
|
|
remap:
|
|
a: C-c
|
|
|
|
- name: generic
|
|
remap:
|
|
a: C-b
|
|
"};
|
|
|
|
assert_actions(
|
|
config,
|
|
vec![Event::KeyEvent(
|
|
get_input_device_info(),
|
|
KeyEvent::new(Key::KEY_A, KeyValue::Press),
|
|
)],
|
|
vec![
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_B, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_B, KeyValue::Release)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
],
|
|
);
|
|
|
|
assert_actions_with_current_application(
|
|
config,
|
|
Some(String::from("firefox")),
|
|
vec![Event::KeyEvent(
|
|
get_input_device_info(),
|
|
KeyEvent::new(Key::KEY_A, KeyValue::Press),
|
|
)],
|
|
vec![
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_C, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_C, KeyValue::Release)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_device_override() {
|
|
let config = indoc! {"
|
|
keymap:
|
|
|
|
- name: event1
|
|
device:
|
|
only: [event1]
|
|
remap:
|
|
a: C-c
|
|
|
|
- name: event0
|
|
remap:
|
|
a: C-b
|
|
"};
|
|
|
|
assert_actions(
|
|
config,
|
|
vec![Event::KeyEvent(
|
|
InputDeviceInfo {
|
|
name: "Some Device",
|
|
path: &Path::new("/dev/input/event0"),
|
|
},
|
|
KeyEvent::new(Key::KEY_A, KeyValue::Press),
|
|
)],
|
|
vec![
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_B, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_B, KeyValue::Release)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
],
|
|
);
|
|
|
|
assert_actions(
|
|
config,
|
|
vec![Event::KeyEvent(
|
|
InputDeviceInfo {
|
|
name: "Other Device",
|
|
path: &Path::new("/dev/input/event1"),
|
|
},
|
|
KeyEvent::new(Key::KEY_A, KeyValue::Press),
|
|
)],
|
|
vec![
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_C, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_C, KeyValue::Release)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_merge_remaps() {
|
|
let config = indoc! {"
|
|
keymap:
|
|
- remap:
|
|
C-x:
|
|
remap:
|
|
h: C-a
|
|
- remap:
|
|
C-x:
|
|
remap:
|
|
k: C-w
|
|
"};
|
|
|
|
assert_actions(
|
|
config,
|
|
vec![
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Release)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_H, KeyValue::Press)),
|
|
],
|
|
vec![
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Release)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_A, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_A, KeyValue::Release)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
],
|
|
);
|
|
|
|
assert_actions(
|
|
config,
|
|
vec![
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Release)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_K, KeyValue::Press)),
|
|
],
|
|
vec![
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Release)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_W, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_W, KeyValue::Release)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
],
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_merge_remaps_with_override() {
|
|
let config = indoc! {"
|
|
keymap:
|
|
- remap:
|
|
C-x:
|
|
remap:
|
|
h: C-a
|
|
- remap:
|
|
C-x:
|
|
remap:
|
|
h: C-b
|
|
c: C-q
|
|
"};
|
|
|
|
assert_actions(
|
|
config,
|
|
vec![
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Release)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_H, KeyValue::Press)),
|
|
],
|
|
vec![
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Release)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_A, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_A, KeyValue::Release)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
],
|
|
);
|
|
|
|
assert_actions(
|
|
config,
|
|
vec![
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Press)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Release)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_C, KeyValue::Press)),
|
|
],
|
|
vec![
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Release)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_Q, KeyValue::Press)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_Q, KeyValue::Release)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::Delay(Duration::from_nanos(0)),
|
|
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
|
|
],
|
|
)
|
|
}
|
|
|
|
fn assert_actions(config_yaml: &str, events: Vec<Event>, actions: Vec<Action>) {
|
|
assert_actions_with_current_application(config_yaml, None, events, actions);
|
|
}
|
|
|
|
fn assert_actions_with_current_application(
|
|
config_yaml: &str,
|
|
current_application: Option<String>,
|
|
events: Vec<Event>,
|
|
actions: Vec<Action>,
|
|
) {
|
|
let timer = TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()).unwrap();
|
|
let mut config: Config = serde_yaml::from_str(config_yaml).unwrap();
|
|
config.keymap_table = build_keymap_table(&config.keymap);
|
|
let mut event_handler = EventHandler::new(
|
|
timer,
|
|
"default",
|
|
Duration::from_micros(0),
|
|
WMClient::new("static", Box::new(StaticClient { current_application })),
|
|
);
|
|
let mut actual: Vec<Action> = vec![];
|
|
|
|
actual.append(&mut event_handler.on_events(&events, &config).unwrap());
|
|
|
|
assert_eq!(format!("{:?}", actions), format!("{:?}", actual));
|
|
}
|