diff options
-rw-r--r-- | README.md | 109 | ||||
-rw-r--r-- | evremap.service | 11 | ||||
-rw-r--r-- | src/remapper.rs | 8 |
3 files changed, 127 insertions, 1 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..a18ed10 --- /dev/null +++ b/README.md @@ -0,0 +1,109 @@ +# evremap + +*A keyboard input remapper for Linux/Wayland systems, written by <a href="https://github.com/wez/">@wez</a>* + +## Why? + +I couldn't find a good solution for the following: + +* Remap the `CAPSLOCK` key so that it produces `CTRL` when held, but `ESC` if tapped +* Remap N keys to M keys. Eg: `F3` -> `CTRL+c`, and `ALT+LEFT` to `HOME` + +## How? + +`evremap` works by grabbing exclusive access to an input device and maintaining +a model of the keys that are pressed. It then applies your remapping configuration +to produce the effective set of pressed keys and emits appropriate changes to a virtual +output device. + +## Configuration + +Here's an example configuration that makes capslock useful: + +```toml +# The name of the device to remap. +# Run `sudo evremap list-devices` to see the devices available +# on your system. +device_name = "AT Translated Set 2 keyboard" + +# Configure CAPSLOCK as a Dual Role key. +# Holding it produces LEFTCTRL, but tapping it +# will produce ESC. +# Both `tap` and `hold` can expand to multiple output keys. +[[dual_role]] +input = "KEY_CAPSLOCK" +hold = ["KEY_LEFTCTRL"] +tap = ["KEY_ESC"] +``` + +You can also express simple remapping entries: + +``` +# This config snippet is useful if your keyboard has an arrow +# cluster, but doesn't have page up, page down, home or end +# keys. Here we're configuring ALT+arrow to map to those functions. +[[remap]] +input = ["KEY_LEFTALT", "KEY_UP"] +output = ["KEY_PAGEUP"] + +[[remap]] +input = ["KEY_LEFTALT", "KEY_DOWN"] +output = ["KEY_PAGEDOWN"] + +[[remap]] +input = ["KEY_LEFTALT", "KEY_LEFT"] +output = ["KEY_HOME"] + +[[remap]] +input = ["KEY_LEFTALT", "KEY_RIGHT"] +output = ["KEY_END"] +``` + +When applying remapping configuration, ordering is important: + +* Dual Role entries are always processed first +* Remap entries are applied in the order that they appear in + your configuration file + +Here's an example where ordering is important: on the PixelBook Go keyboard, +the function key row has alternate functions on the keycaps. It is natural +to want the mute button to mute by default, but to emit the F8 key when +holding alt. We can express that with the following configuration: + +``` +[[remap]] +input = ["KEY_LEFTALT", "KEY_F8"] +# When our `input` is matched, our list of `output` is prevented from +# matching as the `input` of subsequent rules. +output = ["KEY_F8"] + +[[remap]] +input = ["KEY_F8"] +output = ["KEY_MUTE"] +``` + +## Building it + +``` +$ sudo dnf install libevdev-devel +$ cargo build --release +``` + +## Running it + +To run the remapper, invoke it *as root* (so that it can grab exclusive access to the input device): + +``` +sudo target/release/evremap remap my-config-file.toml +``` + +## Systemd + +A sample system service unit is included in the repo. You'll want to adjust the paths to match +your system and then install and enable it: + +``` +$ sudo cp evremap.service /usr/lib/systemd/system/ +$ sudo systemctl enable evremap.service +$ sudo systemctl start evremap.service +``` diff --git a/evremap.service b/evremap.service new file mode 100644 index 0000000..cc38bb7 --- /dev/null +++ b/evremap.service @@ -0,0 +1,11 @@ +[Service] +WorkingDirectory=/ +# For reasons I don't care to troubleshoot, Fedora 31 won't let me start this +# unless I use `bash -c` around it. Putting the command line in directly +# yields a 203 permission denied error with no logs about what it didn't like. +ExecStart=bash -c "/home/wez/github/evremap/target/release/evremap remap /home/wez/github/evremap/pixelbookgo.toml" +Restart=always + +[Install] +WantedBy=gdm.service + diff --git a/src/remapper.rs b/src/remapper.rs index 4292c53..b33c0e2 100644 --- a/src/remapper.rs +++ b/src/remapper.rs @@ -156,15 +156,21 @@ impl InputMapper { } } + let mut keys_minus_remapped = keys.clone(); + // Second pass to apply Remap items for map in &self.mappings { if let Mapping::Remap { input, output } = map { - if input.is_subset(&keys) { + if input.is_subset(&keys_minus_remapped) { for i in input { keys.remove(i); + keys_minus_remapped.remove(i); } for o in output { keys.insert(o.clone()); + // Outputs that apply are not visible as + // inputs for later remap rules + keys_minus_remapped.remove(o); } } } |