Skip to content

Commit

Permalink
Add support for relative and absolute input remapping
Browse files Browse the repository at this point in the history
  • Loading branch information
innovate-invent committed May 31, 2024
1 parent 4f3de4a commit 3606176
Show file tree
Hide file tree
Showing 4 changed files with 270 additions and 104 deletions.
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,42 @@ input = ["KEY_F8"]
output = ["KEY_MUTE"]
```

`REL_*` and `ABS_*` input types are also supported. These have an associated
positive or negative value depending on the relative direction of the input.
You can optionally specify the relative direction by appending a "+" or "-"
to the input name. For example, if you wanted to remap your scroll wheel to
a keypress:

```toml
[[remap]]
input = ["REL_WHEEL+"]
output = ["KEY_UP"]

[[remap]]
input = ["REL_WHEEL-"]
output = ["KEY_DOWN"]
```

Mapping relative inputs to relative inputs is also possible, you can include
a scaling factor to both the input value and output. The input value is divided
by the input scaling factor, and the output is multiplied by the output scaling
factor. Specify a negative scale factor on the output to invert the input. If you
provide an input scale, you need to remap both relative directions separately:

```toml
[[remap]]
input = ["REL_WHEEL"]
output = ["REL_X-2"]

[[remap]]
input = ["REL_HWHEEL_HI_RES+4"]
output = ["REL_Y+2"]

[[remap]]
input = ["REL_HWHEEL_HI_RES-4"]
output = ["REL_Y+2"]
```

* How do I list available input devices?
`sudo evremap list-devices`

Expand Down
7 changes: 6 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use crate::mapping::*;
use crate::remapper::*;
pub use evdev_rs::enums::EV_KEY;
use anyhow::{Context, Result};
use std::path::PathBuf;
use std::time::Duration;
use clap::Parser;
use evdev_rs::enums::{EV_ABS, EV_REL};

mod deviceinfo;
mod mapping;
Expand Down Expand Up @@ -40,10 +42,13 @@ enum Opt {
}

pub fn list_keys() -> Result<()> {
let mut keys: Vec<String> = EventCode::EV_KEY(KeyCode::KEY_RESERVED)
let mut keys: Vec<String> = EventCode::EV_KEY(EV_KEY::KEY_RESERVED)
.iter()
.chain(EventCode::EV_REL(EV_REL::REL_X).iter()).chain(EventCode::EV_ABS(EV_ABS::ABS_X).iter())
.filter_map(|code| match code {
EventCode::EV_KEY(_) => Some(format!("{}", code)),
EventCode::EV_REL(_) => Some(format!("{}", code)),
EventCode::EV_ABS(_) => Some(format!("{}", code)),
_ => None,
})
.collect();
Expand Down
84 changes: 69 additions & 15 deletions src/mapping.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use anyhow::Context;
pub use evdev_rs::enums::{EventCode, EventType, EV_KEY as KeyCode};
pub use evdev_rs::enums::{EventCode, EventCode as KeyCode, EventType};
use serde::Deserialize;
use std::collections::HashSet;
use std::path::Path;
use thiserror::Error;
use std::hash::{Hash, Hasher};

#[derive(Debug, Clone)]
pub struct MappingConfig {
Expand Down Expand Up @@ -34,28 +35,57 @@ impl MappingConfig {
}
}

#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug, Clone)]
pub enum Mapping {
DualRole {
input: KeyCode,
hold: Vec<KeyCode>,
tap: Vec<KeyCode>,
},
Remap {
input: HashSet<KeyCode>,
output: HashSet<KeyCode>,
input: HashSet<KeyCodeWrapper>,
output: HashSet<KeyCodeWrapper>,
},
}

#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, Clone)]
#[serde(try_from = "String")]
struct KeyCodeWrapper {
pub code: KeyCode,
pub struct KeyCodeWrapper {
pub code: EventCode,
pub scale: i32,
}

impl PartialEq for KeyCodeWrapper {
fn eq(&self, other: &Self) -> bool {
self.code == other.code && self.scale.is_negative() == other.scale.is_negative()
}
}

impl Into<KeyCode> for KeyCodeWrapper {
fn into(self) -> KeyCode {
self.code
impl Eq for KeyCodeWrapper {}

impl PartialEq<EventCode> for KeyCodeWrapper {
fn eq(&self, other: &EventCode) -> bool {
self.code == *other
}
}

impl PartialEq<KeyCodeWrapper> for EventCode {
fn eq(&self, other: &KeyCodeWrapper) -> bool {
*self == other.code
}
}

impl Hash for KeyCodeWrapper {
fn hash<H: Hasher>(&self, state: &mut H) {
self.code.hash(state);
// Hash the direction filter only but not the magnitude
(if self.scale.is_negative() {-1} else {1}).hash(state);
}
}

impl From<KeyCodeWrapper> for KeyCode {
fn from(value: KeyCodeWrapper) -> Self {
value.code
}
}

Expand All @@ -70,12 +100,36 @@ pub enum ConfigError {
impl std::convert::TryFrom<String> for KeyCodeWrapper {
type Error = ConfigError;
fn try_from(s: String) -> Result<KeyCodeWrapper, Self::Error> {
match EventCode::from_str(&EventType::EV_KEY, &s) {
Some(code) => match code {
EventCode::EV_KEY(code) => Ok(KeyCodeWrapper { code }),
_ => Err(ConfigError::ImpossibleParseKey),
let mut scale: i32 = 1;
let name: &str;
match s.rmatch_indices(&['+', '-']).next() {
None => {
name = &s;
scale = 0;
},
Some(m) => {
let _scale;
(name, _scale) = s.split_at(m.0);
if _scale.len() > 1 {
scale = _scale.parse::<i32>().unwrap();
} else if _scale == "-" {
scale = -1;
}
},
};
let mut prefix = name.split_once("_").unwrap().0;
if prefix == "BTN" {
prefix = "KEY";
}
if prefix == "KEY" && scale == 0 {
scale = 1;
}
match EventType::from_str(&*("EV_".to_string() + prefix)) {
Some(event_type) => match EventCode::from_str(&event_type, &name) {
Some(code) => Ok(KeyCodeWrapper { code, scale }),
None => Err(ConfigError::InvalidKey(name.to_string())),
},
None => Err(ConfigError::InvalidKey(s)),
None => Err(ConfigError::InvalidKey(name.to_string())),
}
}
}
Expand Down
Loading

0 comments on commit 3606176

Please sign in to comment.