Support device-specific remapping in config (#380)

* begin threading InputDevice down to `find_modmap`/`find_keymap`

why did i comment this out?

* add to config

* new approach for event/device (wip)

* try passing input_device as its own thing -- same problem

* aha! this works!

* this works too!

* start wiring it together

* that might do it?

* fallback to false, like for application

* fix device.not

* tests (wip)

* tests (wip) | well it compiles

* tests (wip) | just placeholder for now

* move device out of event

* Revert "move device out of event"

This reverts commit c9486ed2adee5f001ebf96d630621fabcb2127a5.

* device desc struct (k0kubun's suggestion)

* get tests passing

* s/InputDeviceDescriptor/InputDeviceInfo/

* add a test

* misc cleanup

* use &str and &Path instead of String and PathBuf

* cargo fmt

* move InputDeviceInfo in device.rs

* more misc cleanup

* documentation

* get rid of extraneous `matches` function

* readme tweak
pull/381/head
Elliott Shugerman 6 months ago committed by GitHub
parent 6f298b93b8
commit fd3992e486
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -20,6 +20,7 @@
* Remap a key sequence as well. You could do something like Emacs's `C-x C-c`. * Remap a key sequence as well. You could do something like Emacs's `C-x C-c`.
* Remap a key to two different keys depending on whether it's pressed alone or held. * Remap a key to two different keys depending on whether it's pressed alone or held.
* Application-specific remapping. Even if it's not supported by your application, xremap can. * Application-specific remapping. Even if it's not supported by your application, xremap can.
* Device-specific remapping.
* Automatically remap newly connected devices by starting xremap with `--watch`. * Automatically remap newly connected devices by starting xremap with `--watch`.
* Support [Emacs-like key remapping](example/emacs.yml), including the mark mode. * Support [Emacs-like key remapping](example/emacs.yml), including the mark mode.
* Trigger commands on key press/release events. * Trigger commands on key press/release events.
@ -350,6 +351,31 @@ keymap:
Note how Alt-f and Alt-b work in all apps, but the definition of Alt-f is slightly different in LibreOffice Writer. When that app is active, the first definition overrides the second definition; but for any other app, only the second definition is found. This is because xremap uses the first matching definition that it finds. Note how Alt-f and Alt-b work in all apps, but the definition of Alt-f is slightly different in LibreOffice Writer. When that app is active, the first definition overrides the second definition; but for any other app, only the second definition is found. This is because xremap uses the first matching definition that it finds.
### device
Much like [`application`](#application), you may specify `{keymap,modmap}.device.{not,only}` in your configuration for device-specific remapping. Consistent with the global `--device` flag, device-matching strings may be any of:
- the full path of the device
- the filename of the device
- the device name
- a substring of the device name
To determine the names and paths of your devices, examine `xremap`'s log output at startup.
```yml
device:
not: '/dev/input/event0'
# or
not: ['event0', ...]
# or
only: 'Some Cool Device Name'
# or
only: ['Cool Device', ...]
# etc...
```
Unlike for `application`, regexs are not supported for `device`.
### virtual\_modifiers ### virtual\_modifiers
You can declare keys that should act like a modifier. You can declare keys that should act like a modifier.

@ -0,0 +1,12 @@
use crate::config::application::deserialize_string_or_vec;
use serde::Deserialize;
// TODO: Use trait to allow only either `only` or `not`
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Device {
#[serde(default, deserialize_with = "deserialize_string_or_vec")]
pub only: Option<Vec<String>>,
#[serde(default, deserialize_with = "deserialize_string_or_vec")]
pub not: Option<Vec<String>>,
}

@ -6,6 +6,7 @@ use evdev::Key;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use std::collections::HashMap; use std::collections::HashMap;
use super::device::Device;
use super::key_press::Modifier; use super::key_press::Modifier;
// Config interface // Config interface
@ -17,6 +18,7 @@ pub struct Keymap {
#[serde(deserialize_with = "deserialize_remap")] #[serde(deserialize_with = "deserialize_remap")]
pub remap: HashMap<KeyPress, Vec<KeymapAction>>, pub remap: HashMap<KeyPress, Vec<KeymapAction>>,
pub application: Option<Application>, pub application: Option<Application>,
pub device: Option<Device>,
#[serde(default, deserialize_with = "deserialize_string_or_vec")] #[serde(default, deserialize_with = "deserialize_string_or_vec")]
pub mode: Option<Vec<String>>, pub mode: Option<Vec<String>>,
#[serde(default)] #[serde(default)]
@ -40,6 +42,7 @@ pub struct KeymapEntry {
pub actions: Vec<KeymapAction>, pub actions: Vec<KeymapAction>,
pub modifiers: Vec<Modifier>, pub modifiers: Vec<Modifier>,
pub application: Option<Application>, pub application: Option<Application>,
pub device: Option<Device>,
pub mode: Option<Vec<String>>, pub mode: Option<Vec<String>>,
pub exact_match: bool, pub exact_match: bool,
} }
@ -62,6 +65,7 @@ pub fn build_keymap_table(keymaps: &Vec<Keymap>) -> HashMap<Key, Vec<KeymapEntry
actions: actions.to_vec(), actions: actions.to_vec(),
modifiers: key_press.modifiers.clone(), modifiers: key_press.modifiers.clone(),
application: keymap.application.clone(), application: keymap.application.clone(),
device: keymap.device.clone(),
mode: keymap.mode.clone(), mode: keymap.mode.clone(),
exact_match: keymap.exact_match, exact_match: keymap.exact_match,
}); });

@ -1,4 +1,5 @@
pub mod application; pub mod application;
pub mod device;
mod key; mod key;
pub mod key_press; pub mod key_press;
pub mod keymap; pub mod keymap;

@ -5,6 +5,8 @@ use evdev::Key;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use std::collections::HashMap; use std::collections::HashMap;
use super::device::Device;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct Modmap { pub struct Modmap {
@ -13,6 +15,7 @@ pub struct Modmap {
#[serde(deserialize_with = "deserialize_remap")] #[serde(deserialize_with = "deserialize_remap")]
pub remap: HashMap<Key, ModmapAction>, pub remap: HashMap<Key, ModmapAction>,
pub application: Option<Application>, pub application: Option<Application>,
pub device: Option<Device>,
} }
fn deserialize_remap<'de, D>(deserializer: D) -> Result<HashMap<Key, ModmapAction>, D::Error> fn deserialize_remap<'de, D>(deserializer: D) -> Result<HashMap<Key, ModmapAction>, D::Error>

@ -11,7 +11,7 @@ use std::error::Error;
use std::fs::read_dir; use std::fs::read_dir;
use std::os::unix::ffi::OsStrExt; use std::os::unix::ffi::OsStrExt;
use std::os::unix::prelude::AsRawFd; use std::os::unix::prelude::AsRawFd;
use std::path::PathBuf; use std::path::{Path, PathBuf};
use std::{io, process}; use std::{io, process};
static MOUSE_BTNS: [&str; 20] = [ static MOUSE_BTNS: [&str; 20] = [
@ -133,6 +133,31 @@ pub fn get_input_devices(
Ok(devices.into_iter().map(From::from).collect()) Ok(devices.into_iter().map(From::from).collect())
} }
#[derive(Debug)]
pub struct InputDeviceInfo<'a> {
pub name: &'a str,
pub path: &'a Path,
}
impl<'a> InputDeviceInfo<'a> {
pub fn matches(&self, filter: &String) -> bool {
let filter = filter.as_str();
// Check exact matches for explicit selection
if self.path.as_os_str() == filter || self.name == filter {
return true;
}
// eventXX shorthand for /dev/input/eventXX
if filter.starts_with("event") && self.path.file_name().expect("every device path has a file name") == filter {
return true;
}
// Allow partial matches for device names
if self.name.contains(filter) {
return true;
}
return false;
}
}
#[derive_where(PartialEq, PartialOrd, Ord)] #[derive_where(PartialEq, PartialOrd, Ord)]
pub struct InputDevice { pub struct InputDevice {
path: PathBuf, path: PathBuf,
@ -200,6 +225,13 @@ impl InputDevice {
pub fn bus_type(&self) -> BusType { pub fn bus_type(&self) -> BusType {
self.device.input_id().bus_type() self.device.input_id().bus_type()
} }
pub fn to_info(&self) -> InputDeviceInfo {
InputDeviceInfo {
name: self.device_name(),
path: &self.path,
}
}
} }
impl InputDevice { impl InputDevice {
@ -210,8 +242,8 @@ impl InputDevice {
(if device_filter.is_empty() { (if device_filter.is_empty() {
self.is_keyboard() || (mouse && self.is_mouse()) self.is_keyboard() || (mouse && self.is_mouse())
} else { } else {
self.matches(device_filter) self.matches_any(device_filter)
}) && (ignore_filter.is_empty() || !self.matches(ignore_filter)) }) && (ignore_filter.is_empty() || !self.matches_any(ignore_filter))
} }
// We can't know the device path from evdev::enumerate(). So we re-implement it. // We can't know the device path from evdev::enumerate(). So we re-implement it.
@ -246,31 +278,12 @@ impl InputDevice {
.any(|device| return device.device_name().contains(device_name)) .any(|device| return device.device_name().contains(device_name))
} }
fn matches(&self, filter: &[String]) -> bool { fn matches_any(&self, filter: &[String]) -> bool {
// Force unmatch its own device // Force unmatch its own device
if self.device_name() == Self::current_name() { if self.device_name() == Self::current_name() {
return false; return false;
} }
return filter.iter().any(|f| self.to_info().matches(f));
for device_opt in filter {
let device_opt = device_opt.as_str();
// Check exact matches for explicit selection
if self.path.as_os_str() == device_opt || self.device_name() == device_opt {
return true;
}
// eventXX shorthand for /dev/input/eventXX
if device_opt.starts_with("event")
&& self.path.file_name().expect("every device path has a file name") == device_opt
{
return true;
}
// Allow partial matches for device names
if self.device_name().contains(device_opt) {
return true;
}
}
false
} }
fn is_keyboard(&self) -> bool { fn is_keyboard(&self) -> bool {

@ -1,12 +1,14 @@
use evdev::{EventType, InputEvent, Key}; use evdev::{EventType, InputEvent, Key};
use crate::device::InputDeviceInfo;
// Input to EventHandler. This should only contain things that are easily testable. // Input to EventHandler. This should only contain things that are easily testable.
#[derive(Debug)] #[derive(Debug)]
pub enum Event { pub enum Event<'a> {
// InputEvent (EventType::KEY) sent from evdev // InputEvent (EventType::KEY) sent from evdev
KeyEvent(KeyEvent), KeyEvent(InputDeviceInfo<'a>, KeyEvent),
// InputEvent (EventType::Relative) sent from evdev // InputEvent (EventType::Relative) sent from evdev
RelativeEvent(RelativeEvent), RelativeEvent(InputDeviceInfo<'a>, RelativeEvent),
// Any other InputEvent type sent from evdev // Any other InputEvent type sent from evdev
OtherEvents(InputEvent), OtherEvents(InputEvent),
// Timer for nested override reached its timeout // Timer for nested override reached its timeout
@ -15,7 +17,7 @@ pub enum Event {
#[derive(Debug)] #[derive(Debug)]
pub struct KeyEvent { pub struct KeyEvent {
key: Key, pub key: Key,
value: KeyValue, value: KeyValue,
} }
@ -31,12 +33,12 @@ pub enum KeyValue {
Release, Release,
Repeat, Repeat,
} }
impl Event { impl<'a> Event<'a> {
// Convert evdev's raw InputEvent to xremap's internal Event // Convert evdev's raw InputEvent to xremap's internal Event
pub fn new(event: InputEvent) -> Event { pub fn new(device: InputDeviceInfo, event: InputEvent) -> Event {
let event = match event.event_type() { let event = match event.event_type() {
EventType::KEY => Event::KeyEvent(KeyEvent::new_with(event.code(), event.value())), EventType::KEY => Event::KeyEvent(device, KeyEvent::new_with(event.code(), event.value())),
EventType::RELATIVE => Event::RelativeEvent(RelativeEvent::new_with(event.code(), event.value())), EventType::RELATIVE => Event::RelativeEvent(device, RelativeEvent::new_with(event.code(), event.value())),
_ => Event::OtherEvents(event), _ => Event::OtherEvents(event),
}; };
event event

@ -6,8 +6,9 @@ use crate::config::keymap::{build_override_table, OverrideEntry};
use crate::config::keymap_action::KeymapAction; use crate::config::keymap_action::KeymapAction;
use crate::config::modmap_action::{Keys, ModmapAction, MultiPurposeKey, PressReleaseKey}; use crate::config::modmap_action::{Keys, ModmapAction, MultiPurposeKey, PressReleaseKey};
use crate::config::remap::Remap; use crate::config::remap::Remap;
use crate::device::InputDeviceInfo;
use crate::event::{Event, KeyEvent, RelativeEvent}; use crate::event::{Event, KeyEvent, RelativeEvent};
use crate::Config; use crate::{config, Config};
use evdev::Key; use evdev::Key;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use log::debug; use log::debug;
@ -85,12 +86,12 @@ impl EventHandler {
let mut mouse_movement_collection: Vec<RelativeEvent> = Vec::new(); let mut mouse_movement_collection: Vec<RelativeEvent> = Vec::new();
for event in events { for event in events {
match event { match event {
Event::KeyEvent(key_event) => { Event::KeyEvent(device, key_event) => {
self.on_key_event(key_event, config)?; self.on_key_event(key_event, config, device)?;
() ()
} }
Event::RelativeEvent(relative_event) => { Event::RelativeEvent(device, relative_event) => {
self.on_relative_event(relative_event, &mut mouse_movement_collection, config)? self.on_relative_event(relative_event, &mut mouse_movement_collection, config, device)?
} }
Event::OtherEvents(event) => self.send_action(Action::InputEvent(*event)), Event::OtherEvents(event) => self.send_action(Action::InputEvent(*event)),
@ -105,13 +106,18 @@ impl EventHandler {
} }
// Handle EventType::KEY // Handle EventType::KEY
fn on_key_event(&mut self, event: &KeyEvent, config: &Config) -> Result<bool, Box<dyn Error>> { fn on_key_event(
&mut self,
event: &KeyEvent,
config: &Config,
device: &InputDeviceInfo,
) -> Result<bool, Box<dyn Error>> {
self.application_cache = None; // expire cache self.application_cache = None; // expire cache
let key = Key::new(event.code()); let key = Key::new(event.code());
debug!("=> {}: {:?}", event.value(), &key); debug!("=> {}: {:?}", event.value(), &key);
// Apply modmap // Apply modmap
let mut key_values = if let Some(key_action) = self.find_modmap(config, &key) { let mut key_values = if let Some(key_action) = self.find_modmap(config, &key, device) {
self.dispatch_keys(key_action, key, event.value())? self.dispatch_keys(key_action, key, event.value())?
} else { } else {
vec![(key, event.value())] vec![(key, event.value())]
@ -132,7 +138,7 @@ impl EventHandler {
} else if is_pressed(value) { } else if is_pressed(value) {
if self.escape_next_key { if self.escape_next_key {
self.escape_next_key = false self.escape_next_key = false
} else if let Some(actions) = self.find_keymap(config, &key)? { } else if let Some(actions) = self.find_keymap(config, &key, device)? {
self.dispatch_actions(&actions, &key)?; self.dispatch_actions(&actions, &key)?;
continue; continue;
} }
@ -159,6 +165,7 @@ impl EventHandler {
event: &RelativeEvent, event: &RelativeEvent,
mouse_movement_collection: &mut Vec<RelativeEvent>, mouse_movement_collection: &mut Vec<RelativeEvent>,
config: &Config, config: &Config,
device: &InputDeviceInfo,
) -> Result<(), Box<dyn Error>> { ) -> Result<(), Box<dyn Error>> {
// Because a "full" RELATIVE event is only one event, // 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). // it doesn't translate very well into a KEY event (because those have a "press" event and an "unpress" event).
@ -200,7 +207,7 @@ impl EventHandler {
}; };
// Sending a RELATIVE event "disguised" as a "fake" KEY event press to on_key_event. // 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)? { match self.on_key_event(&KeyEvent::new_with(key, PRESS), config, &device)? {
// the boolean value is from a variable at the end of on_key_event from event_handler, // 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. // used to indicate whether the event got through unchanged.
true => { true => {
@ -229,7 +236,7 @@ impl EventHandler {
} }
// Sending the "unpressed" version of the "fake" KEY event. // Sending the "unpressed" version of the "fake" KEY event.
self.on_key_event(&KeyEvent::new_with(key, RELEASE), config)?; self.on_key_event(&KeyEvent::new_with(key, RELEASE), config, &device)?;
Ok(()) Ok(())
} }
@ -365,7 +372,7 @@ impl EventHandler {
} }
} }
fn find_modmap(&mut self, config: &Config, key: &Key) -> Option<ModmapAction> { fn find_modmap(&mut self, config: &Config, key: &Key, device: &InputDeviceInfo) -> Option<ModmapAction> {
for modmap in &config.modmap { for modmap in &config.modmap {
if let Some(key_action) = modmap.remap.get(key) { if let Some(key_action) = modmap.remap.get(key) {
if let Some(application_matcher) = &modmap.application { if let Some(application_matcher) = &modmap.application {
@ -373,13 +380,23 @@ impl EventHandler {
continue; continue;
} }
} }
if let Some(device_matcher) = &modmap.device {
if !self.match_device(device_matcher, device) {
continue;
}
}
return Some(key_action.clone()); return Some(key_action.clone());
} }
} }
None None
} }
fn find_keymap(&mut self, config: &Config, key: &Key) -> Result<Option<Vec<TaggedAction>>, Box<dyn Error>> { fn find_keymap(
&mut self,
config: &Config,
key: &Key,
device: &InputDeviceInfo,
) -> Result<Option<Vec<TaggedAction>>, Box<dyn Error>> {
if !self.override_remaps.is_empty() { if !self.override_remaps.is_empty() {
let entries: Vec<OverrideEntry> = self let entries: Vec<OverrideEntry> = self
.override_remaps .override_remaps
@ -436,6 +453,11 @@ impl EventHandler {
continue; continue;
} }
} }
if let Some(device_matcher) = &entry.device {
if !self.match_device(device_matcher, device) {
continue;
}
}
if let Some(modes) = &entry.mode { if let Some(modes) = &entry.mode {
if !modes.contains(&self.mode) { if !modes.contains(&self.mode) {
continue; continue;
@ -612,6 +634,16 @@ impl EventHandler {
false false
} }
fn match_device(&self, device_matcher: &config::device::Device, device: &InputDeviceInfo) -> bool {
if let Some(device_only) = &device_matcher.only {
return device_only.iter().any(|m| device.matches(m));
}
if let Some(device_not) = &device_matcher.not {
return device_not.iter().all(|m| !device.matches(m));
}
false
}
fn update_modifier(&mut self, key: Key, value: i32) { fn update_modifier(&mut self, key: Key, value: i32) {
if value == PRESS { if value == PRESS {
self.modifiers.insert(key); self.modifiers.insert(key);

@ -226,20 +226,18 @@ fn handle_input_events(
dispatcher: &mut ActionDispatcher, dispatcher: &mut ActionDispatcher,
config: &mut Config, config: &mut Config,
) -> anyhow::Result<bool> { ) -> anyhow::Result<bool> {
match input_device.fetch_events().map_err(|e| (e.raw_os_error(), e)) { let mut device_exists = true;
Err((Some(ENODEV), _)) => Ok(false), let events = match input_device.fetch_events().map_err(|e| (e.raw_os_error(), e)) {
Err((_, error)) => Err(error).context("Error fetching input events"), Err((Some(ENODEV), _)) => {
Ok(events) => { device_exists = false;
let mut input_events: Vec<Event> = Vec::new(); Ok(Vec::new())
for event in events {
let event = Event::new(event);
input_events.push(event);
}
handle_events(handler, dispatcher, config, input_events)?;
Ok(true)
} }
} Err((_, error)) => Err(error).context("Error fetching input events"),
Ok(events) => Ok(events.collect()),
}?;
let input_events = events.iter().map(|e| Event::new(input_device.to_info(), *e)).collect();
handle_events(handler, dispatcher, config, input_events)?;
Ok(device_exists)
} }
// Handle an Event with EventHandler, and dispatch Actions with ActionDispatcher // Handle an Event with EventHandler, and dispatch Actions with ActionDispatcher

@ -3,9 +3,11 @@ use evdev::InputEvent;
use evdev::Key; use evdev::Key;
use indoc::indoc; use indoc::indoc;
use nix::sys::timerfd::{ClockId, TimerFd, TimerFlags}; use nix::sys::timerfd::{ClockId, TimerFd, TimerFlags};
use std::path::Path;
use std::time::Duration; use std::time::Duration;
use crate::client::{Client, WMClient}; use crate::client::{Client, WMClient};
use crate::device::InputDeviceInfo;
use crate::{ use crate::{
action::Action, action::Action,
config::{keymap::build_keymap_table, Config}, config::{keymap::build_keymap_table, Config},
@ -27,6 +29,13 @@ impl Client for StaticClient {
} }
} }
fn get_input_device_info<'a>() -> InputDeviceInfo<'a> {
InputDeviceInfo {
name: "Some Device",
path: &Path::new("/dev/input/event0"),
}
}
#[test] #[test]
fn test_basic_modmap() { fn test_basic_modmap() {
assert_actions( assert_actions(
@ -36,10 +45,10 @@ fn test_basic_modmap() {
a: b a: b
"}, "},
vec![ vec![
Event::KeyEvent(KeyEvent::new(Key::KEY_A, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_A, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_A, KeyValue::Release)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_A, KeyValue::Release)),
Event::KeyEvent(KeyEvent::new(Key::KEY_B, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_B, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_B, KeyValue::Release)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_B, KeyValue::Release)),
], ],
vec![ vec![
Action::KeyEvent(KeyEvent::new(Key::KEY_B, KeyValue::Press)), Action::KeyEvent(KeyEvent::new(Key::KEY_B, KeyValue::Press)),
@ -93,7 +102,10 @@ fn test_relative_events() {
- remap: - remap:
XRIGHTCURSOR: b XRIGHTCURSOR: b
"}, "},
vec![Event::RelativeEvent(RelativeEvent::new_with(_REL_X, _POSITIVE))], vec![Event::RelativeEvent(
get_input_device_info(),
RelativeEvent::new_with(_REL_X, _POSITIVE),
)],
vec![ vec![
Action::KeyEvent(KeyEvent::new(Key::KEY_B, KeyValue::Press)), 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::Release)),
@ -122,8 +134,8 @@ fn test_mouse_movement_event_accumulation() {
assert_actions( assert_actions(
indoc! {""}, indoc! {""},
vec![ vec![
Event::RelativeEvent(RelativeEvent::new_with(_REL_X, _POSITIVE)), Event::RelativeEvent(get_input_device_info(), RelativeEvent::new_with(_REL_X, _POSITIVE)),
Event::RelativeEvent(RelativeEvent::new_with(_REL_Y, _POSITIVE)), Event::RelativeEvent(get_input_device_info(), RelativeEvent::new_with(_REL_Y, _POSITIVE)),
], ],
vec![Action::MouseMovementEventCollection(vec![ vec![Action::MouseMovementEventCollection(vec![
RelativeEvent::new_with(_REL_X, _POSITIVE), RelativeEvent::new_with(_REL_X, _POSITIVE),
@ -251,8 +263,8 @@ fn test_interleave_modifiers() {
M-f: C-right M-f: C-right
"}, "},
vec![ vec![
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_F, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_F, KeyValue::Press)),
], ],
vec![ vec![
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)), Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
@ -278,9 +290,9 @@ fn test_exact_match_true() {
M-f: C-right M-f: C-right
"}, "},
vec![ vec![
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_F, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_F, KeyValue::Press)),
], ],
vec![ vec![
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)), Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
@ -300,9 +312,9 @@ fn test_exact_match_false() {
M-f: C-right M-f: C-right
"}, "},
vec![ vec![
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_F, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_F, KeyValue::Press)),
], ],
vec![ vec![
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)), Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
@ -328,9 +340,9 @@ fn test_exact_match_default() {
M-f: C-right M-f: C-right
"}, "},
vec![ vec![
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_F, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_F, KeyValue::Press)),
], ],
vec![ vec![
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)), Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
@ -359,12 +371,12 @@ fn test_exact_match_true_nested() {
h: C-a h: C-a
"}, "},
vec![ vec![
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Release)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Release)),
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_H, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_H, KeyValue::Press)),
], ],
vec![ vec![
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)), Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
@ -388,12 +400,12 @@ fn test_exact_match_false_nested() {
h: C-a h: C-a
"}, "},
vec![ vec![
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Release)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Release)),
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_H, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_H, KeyValue::Press)),
], ],
vec![ vec![
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)), Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
@ -428,7 +440,10 @@ fn test_application_override() {
assert_actions( assert_actions(
config, config,
vec![Event::KeyEvent(KeyEvent::new(Key::KEY_A, KeyValue::Press))], vec![Event::KeyEvent(
get_input_device_info(),
KeyEvent::new(Key::KEY_A, KeyValue::Press),
)],
vec![ vec![
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)), 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::Press)),
@ -442,7 +457,65 @@ fn test_application_override() {
assert_actions_with_current_application( assert_actions_with_current_application(
config, config,
Some(String::from("firefox")), Some(String::from("firefox")),
vec![Event::KeyEvent(KeyEvent::new(Key::KEY_A, KeyValue::Press))], 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![ vec![
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)), 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::Press)),
@ -471,11 +544,11 @@ fn test_merge_remaps() {
assert_actions( assert_actions(
config, config,
vec![ vec![
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Release)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Release)),
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
Event::KeyEvent(KeyEvent::new(Key::KEY_H, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_H, KeyValue::Press)),
], ],
vec![ vec![
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)), Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
@ -493,11 +566,11 @@ fn test_merge_remaps() {
assert_actions( assert_actions(
config, config,
vec![ vec![
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Release)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Release)),
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
Event::KeyEvent(KeyEvent::new(Key::KEY_K, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_K, KeyValue::Press)),
], ],
vec![ vec![
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)), Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
@ -531,11 +604,11 @@ fn test_merge_remaps_with_override() {
assert_actions( assert_actions(
config, config,
vec![ vec![
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Release)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Release)),
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
Event::KeyEvent(KeyEvent::new(Key::KEY_H, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_H, KeyValue::Press)),
], ],
vec![ vec![
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)), Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
@ -553,11 +626,11 @@ fn test_merge_remaps_with_override() {
assert_actions( assert_actions(
config, config,
vec![ vec![
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Release)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_X, KeyValue::Release)),
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
Event::KeyEvent(KeyEvent::new(Key::KEY_C, KeyValue::Press)), Event::KeyEvent(get_input_device_info(), KeyEvent::new(Key::KEY_C, KeyValue::Press)),
], ],
vec![ vec![
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)), Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),

Loading…
Cancel
Save