summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--Cargo.lock99
-rw-r--r--Cargo.toml9
-rw-r--r--src/main.rs198
4 files changed, 309 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b7b7050
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/target
+**/*.rs.bk
+.*.sw*
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..edf30b7
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,99 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "anyhow"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "cc"
+version = "1.0.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "evdev-rs"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "evdev-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "evdev-sys"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "evremap"
+version = "0.1.0"
+dependencies = [
+ "anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
+ "evdev-rs 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "log"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "nix"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "void"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[metadata]
+"checksum anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c"
+"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+"checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76"
+"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+"checksum evdev-rs 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "95f73dad019df28348aad51f059684bdf628822325c26d34fbe126e513369799"
+"checksum evdev-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "378763626609036d7177780326a4f589516dd058c81c99277a5a31d63bb938c0"
+"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
+"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
+"checksum nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b"
+"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
+"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..9b91172
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "evremap"
+version = "0.1.0"
+authors = ["Wez Furlong"]
+edition = "2018"
+
+[dependencies]
+evdev-rs = "0.3"
+anyhow = "1.0"
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..b8fa5e3
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,198 @@
+use anyhow::*;
+use evdev::enums::{EventCode, EV_KEY as KeyCode};
+use evdev::{Device, GrabMode, InputEvent, ReadFlag, TimeVal, UInputDevice};
+use evdev_rs as evdev;
+use std::collections::HashMap;
+use std::path::Path;
+use std::time::Duration;
+
+enum KeyEventType {
+ Release,
+ Press,
+ Repeat,
+ Unknown(i32),
+}
+
+impl KeyEventType {
+ fn from_value(value: i32) -> Self {
+ match value {
+ 0 => KeyEventType::Release,
+ 1 => KeyEventType::Press,
+ 2 => KeyEventType::Repeat,
+ _ => KeyEventType::Unknown(value),
+ }
+ }
+
+ fn value(&self) -> i32 {
+ match self {
+ Self::Release => 0,
+ Self::Press => 1,
+ Self::Repeat => 2,
+ Self::Unknown(n) => *n,
+ }
+ }
+}
+
+fn timeval_diff(newer: &TimeVal, older: &TimeVal) -> Duration {
+ const MICROS_PER_SECOND: i64 = 1000000;
+ let secs = newer.tv_sec - older.tv_sec;
+ let usecs = newer.tv_usec - older.tv_usec;
+
+ let (secs, usecs) = if usecs < 0 {
+ (secs - 1, usecs + MICROS_PER_SECOND)
+ } else {
+ (secs, usecs)
+ };
+
+ Duration::from_micros(((secs * MICROS_PER_SECOND) + usecs) as u64)
+}
+
+struct InputPair {
+ input: Device,
+ output: UInputDevice,
+ /// If present in this map, the key is down since the instant
+ /// of its associated value
+ input_state: HashMap<KeyCode, TimeVal>,
+
+ /// The most recent candidate for a tap function is held here
+ tapping: Option<KeyCode>,
+}
+
+impl InputPair {
+ pub fn create_mapper<P: AsRef<Path>>(path: P) -> Result<Self> {
+ let path = path.as_ref();
+ let f = std::fs::File::open(path).context(format!("opening {}", path.display()))?;
+ let mut input = Device::new().ok_or_else(|| anyhow!("failed to make new Device"))?;
+ input
+ .set_fd(f)
+ .context(format!("assigning fd for {} to Device", path.display()))?;
+
+ input.set_name(&format!("evremap Virtual input for {}", path.display()));
+ let output = UInputDevice::create_from_device(&input)
+ .context(format!("creating UInputDevice from {}", path.display()))?;
+
+ input
+ .grab(GrabMode::Grab)
+ .context(format!("grabbing exclusive access on {}", path.display()))?;
+
+ Ok(Self {
+ input,
+ output,
+ input_state: HashMap::new(),
+ tapping: None,
+ })
+ }
+
+ pub fn update_with_event(&mut self, event: &InputEvent, code: KeyCode) -> Result<()> {
+ let event_type = KeyEventType::from_value(event.value);
+ match event_type {
+ KeyEventType::Release => {
+ let pressed_at = self.input_state.remove(&code);
+
+ if pressed_at.is_none() {
+ self.sync_event(event)?;
+ return Ok(());
+ }
+
+ if code == KeyCode::KEY_CAPSLOCK {
+ if let Some(pressed_at) = pressed_at {
+ // If released quickly enough, becomes an ESC key press.
+ // Regardless, we'll release the CTRL value that we mapped it to first.
+ self.emit_key(KeyCode::KEY_LEFTCTRL, &event.time, KeyEventType::Release)?;
+
+ // If no other key went down since the caps key, then this may be a short
+ // tap on that key; if so, remap to escape
+ if let Some(KeyCode::KEY_CAPSLOCK) = self.tapping.take() {
+ if timeval_diff(&event.time, &pressed_at) <= Duration::from_millis(200)
+ {
+ self.emit_key(KeyCode::KEY_ESC, &event.time, KeyEventType::Press)?;
+ self.emit_key(
+ KeyCode::KEY_ESC,
+ &event.time,
+ KeyEventType::Release,
+ )?;
+ }
+ }
+ } else {
+ self.sync_event(event)?;
+ }
+ } else {
+ self.sync_event(event)?;
+ }
+ }
+ KeyEventType::Press => {
+ self.input_state.insert(code.clone(), event.time.clone());
+
+ if code == KeyCode::KEY_CAPSLOCK {
+ // Remap caps to ctrl
+ self.emit_key(KeyCode::KEY_LEFTCTRL, &event.time, KeyEventType::Press)?;
+ self.tapping.replace(KeyCode::KEY_CAPSLOCK);
+ } else {
+ self.cancel_pending_tap();
+ self.sync_event(event)?;
+ }
+ }
+ KeyEventType::Repeat => {
+ self.sync_event(event)?;
+ }
+ KeyEventType::Unknown(_) => {
+ self.sync_event(event)?;
+ }
+ }
+
+ Ok(())
+ }
+
+ fn cancel_pending_tap(&mut self) {
+ self.tapping.take();
+ }
+
+ fn emit_key(&self, key: KeyCode, time: &TimeVal, event_type: KeyEventType) -> Result<()> {
+ let event = make_event(key, time, event_type);
+ self.sync_event(&event)?;
+ Ok(())
+ }
+
+ fn sync_event(&self, event: &InputEvent) -> Result<()> {
+ println!("OUT: {:?}", event);
+ self.output.write_event(&event)?;
+ self.output.write_event(&InputEvent::new(
+ &event.time,
+ &EventCode::EV_SYN(evdev_rs::enums::EV_SYN::SYN_REPORT),
+ 0,
+ ))?;
+ Ok(())
+ }
+}
+
+fn make_event(key: KeyCode, time: &TimeVal, event_type: KeyEventType) -> InputEvent {
+ InputEvent::new(time, &EventCode::EV_KEY(key), event_type.value())
+}
+
+fn main() -> Result<()> {
+ println!("Short delay: release any keys now!");
+ std::thread::sleep(Duration::new(2, 0));
+
+ let path = "/dev/input/event2";
+
+ let mut pair = InputPair::create_mapper(path)?;
+
+ println!("Going into read loop");
+ loop {
+ let (status, event) = pair
+ .input
+ .next_event(ReadFlag::NORMAL | ReadFlag::BLOCKING)?;
+ match status {
+ evdev::ReadStatus::Success => {
+ if let EventCode::EV_KEY(ref key) = event.event_code {
+ println!("IN {:?}", event);
+ pair.update_with_event(&event, key.clone())?;
+ } else {
+ println!("PASSTHRU {:?}", event);
+ pair.output.write_event(&event)?;
+ }
+ }
+ evdev::ReadStatus::Sync => bail!("ReadStatus::Sync!"),
+ }
+ }
+}