diff options
author | Wez Furlong <wez@wezfurlong.org> | 2019-12-30 19:43:10 -0800 |
---|---|---|
committer | Wez Furlong <wez@wezfurlong.org> | 2019-12-30 19:43:10 -0800 |
commit | 5fe5a92c4e77340a0cd40fee04124b7f6bf1370a (patch) | |
tree | 73d3f5170e3ea5776f4e9b4826707f9a143b6320 |
Basic caps->ctrl/esc handling
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | Cargo.lock | 99 | ||||
-rw-r--r-- | Cargo.toml | 9 | ||||
-rw-r--r-- | src/main.rs | 198 |
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!"), + } + } +} |