summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md109
-rw-r--r--evremap.service11
-rw-r--r--src/remapper.rs8
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);
}
}
}