Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/joshgoebel/keyszer
a smart, flexible keymapper for X11 (a fork/reboot of xkeysnail )
https://github.com/joshgoebel/keyszer
emacs-keybindings keyboard-shortcuts keymapping linux uinput x11 xwindows
Last synced: 12 days ago
JSON representation
a smart, flexible keymapper for X11 (a fork/reboot of xkeysnail )
- Host: GitHub
- URL: https://github.com/joshgoebel/keyszer
- Owner: joshgoebel
- License: other
- Created: 2022-05-25T13:12:46.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2024-01-16T01:49:19.000Z (10 months ago)
- Last Synced: 2024-10-11T09:26:10.870Z (28 days ago)
- Topics: emacs-keybindings, keyboard-shortcuts, keymapping, linux, uinput, x11, xwindows
- Language: Python
- Homepage:
- Size: 497 KB
- Stars: 69
- Watchers: 4
- Forks: 15
- Open Issues: 47
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Security: SECURITY.md
Awesome Lists containing this project
README
# keyszer - a smart key remapper for Linux/X11
[![latest version](https://badgen.net/pypi/v/keyszer?label=beta)](https://github.com/joshgoebel/keyszer/releases)
[![python 3.10](https://badgen.net/badge/python/3.10/blue)]()
[![license](https://badgen.net/badge/license/GPL3/keyszer?color=cyan)](https://github.com/joshgoebel/keyszer/blob/main/LICENSE)
[![code quality](https://badgen.net/lgtm/grade/g/joshgoebel/keyszer/js?label=code+quality)](https://lgtm.com/projects/g/joshgoebel/keyszer/?mode=list)
[![discord](https://badgen.net/badge/icon/discord?icon=discord&label&color=pink)](https://discord.gg/nX6qSC8mer)[![open issues](https://badgen.net/github/open-issues/joshgoebel/keyszer?label=issues)](https://github.com/joshgoebel/keyszer/issues)
[![help welcome issues](https://badgen.net/github/label-issues/joshgoebel/keyszer/help%20welcome/open)](https://github.com/joshgoebel/keyszer/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+welcome%22)
[![good first issue](https://badgen.net/github/label-issues/joshgoebel/keyszer/good%20first%20issue/open)](https://github.com/joshgoebel/keyszer/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
![build and CI status](https://badgen.net/github/checks/joshgoebel/keyszer)Keyszer is a smart key remapper for Linux (and X11) written in Python. It's similar to `xmodmap` but allows far more flexible remappings. Keyszer was forked from [xkeysnail](https://github.com/mooz/xkeysnail) which no longer seems actively maintained.
### How does it work?
Keyszer works at quite a low-level. It grabs input directly from the kernel's [`evdev`](https://www.freedesktop.org/wiki/Software/libevdev/) input devices ( `/dev/input/event*`) and then creates an emulated [`uinput`](https://www.kernel.org/doc/html/v4.12/input/uinput.html) device to inject those inputs back into the kernel. During this process the input stream is transformed on the fly as necessary to remap keys.
**Upgrading from xkeysnail**
- Some small configuration changes will be needed.
- A few command line arguments have changed.
- For xkeysnail 0.4.0 see [UPGRADING_FROM_XKEYSNAIL.md](https://github.com/joshgoebel/keyszer/blob/main/UPGRADE_FROM_XKEYSNAIL.md).
- For xkeysnail (Kinto variety) see [USING_WITH_KINTO.md](https://github.com/joshgoebel/keyszer/blob/main/USING_WITH_KINTO.md) and [Using with Kinto v1.2-13](https://github.com/joshgoebel/keyszer/issues/36).#### Key Highlights
- Low-level library usage (`evdev` and `uinput`) allows remapping to work from the console all the way into X11.
- High-level and incredibly flexible remapping mechanisms:
- _per-application keybindings_ - bindings that change depending on the active X11 application or window
- _multiple stroke keybindings_ - `Ctrl+x Ctrl+c` could map to `Ctrl+q`
- _very flexible output_ - `Ctrl-s` could type out `:save`, and then hit enter
- _stateful key combos_ - build Emacs style combos with shift/mark
- _multipurpose bindings_ - a regular key can become a modifier when held
- _arbitrary functions_ - a key combo can run custom Python function**New Features (since xkeysnail 0.4.0)**
- simpler and more flexible configuration scripting APIs
- better debugging tools
- configurable `EMERGENCY EJECT` hotkey
- configurable `DIAGNOSTIC` hotkey
- fully supports running as semi-privileged user (using `root` is now deprecated)
- adds `include` to allow config to pull in other Python files
- adds `throttle_delays` to allow control of output speed of macros/combos
- adds `immediately` to nested keymaps
- adds `Meta`, `Command` and `Cmd` aliases for Super/Meta modifier
- add `C` combo helper (eventually to replace `K`)
- supports custom modifiers via `add_modifier` (such as `Hyper`)
- supports `Fn` as a potential modifier (on hardware where it works)
- adds `bind` helper to support persistent holds across multiple combos
- most frequently used for persistent Mac OS style `Cmd-tab` app switcher panels
- adds `--check` for checking the config file for issues
- adds `wm_name` context for all conditionals (PR #40)
- adds `device_name` context for all conditionals (including keymaps)
- (fix) `xmodmap` cannot be used until some keys are first pressed on the emulated output
- (fix) ability to avoid unintentional Alt/Super false keypresses in many setups
- (fix) fixes multi-combo nested keymaps (vs Kinto's xkeysnail)
- (fix) properly cleans up pressed keys before termination
- individually configurable timeouts (`multipurpose` and `suspend`)
- (fix) removed problematic `launch` macro
- (fix) suspend extra keys during sequential sequences to create less key "noise"
- (fix) handle X Display errors without crashing or bugging out---
## Installation
Requires **Python 3**.
_Over time we should add individual instructions for various distros here._
### From source
Just download the source and install.
git clone https://github.com/joshgoebel/keyszer.git
cd keyszer
pip3 install --user --upgrade .### For testing/hacking/contributing
Using a Python `venv` might be the simplest way to get started:
git clone https://github.com/joshgoebel/keyszer.git
cd keyszer
python -m venv .venv
source .venv/bin/activate
pip3 install -e .
./bin/keyszer -c config_file## System Requirements
Keyszer requires read/write access to:
- `/dev/input/event*` - to grab input from your `evdev` input devices
- `/dev/uinput` - to provide an emulated keyboard to the kernel### Running as a semi-privileged user
It's best to create an entirely isolated user to run the keymapper. Group or ACL based permissions can be used to provide this user access to the necessary devices. You'll need only a few `udev` rules to ensure that the input devices are all given correct permissions.
#### ACL based permissions (narrow, more secure)
First, lets make a new user:
sudo useradd keymapper
...then use udev and ACL to grant our new user access:
Manually edit `/etc/udev/rules.d/90-keymapper-acl.rules` to include the following:
KERNEL=="event*", SUBSYSTEM=="input", RUN+="/usr/bin/setfacl -m user:keymapper:rw /dev/input/%k"
KERNEL=="uinput", SUBSYSTEM=="misc", RUN+="/usr/bin/setfacl -m user:keymapper:rw /dev/uinput"...or do it by copypasting these lines into a shell:
cat <-)*")`.
`` is one of the following:
- `C` or `Ctrl` -> Control key
- `Alt` -> Alt key
- `Shift` -> Shift key
- `Super`, `Win`, `Command`, `Cmd`, `Meta` -> Super/Windows/Command key
- `Fn` -> Function key (on supported keyboards)You can specify left/right modifiers by adding the prefixes `L` or `R`.
`` is any key whose name is defined in [`key.py`](https://github.com/joshgoebel/keyszer/blob/main/src/keyszer/models/key.py).
Some combo examples:
- `C("LC-Alt-j")`: left Control, Alt, `j`
- `C("Ctrl-m")`: Left or Right Control, `m`
- `C("Win-o")`: Cmd/Windows, `o`
- `C("Alt-Shift-comma")`: Alt, Left or Right Shift, comma#### Multiple Stroke Keys
To use multiple stroke keys, simply define a nested keymap. For example, the
following example remaps `C-x C-c` to `C-q`.```python
keymap("multi stroke", {
C("C-x"): {
C("C-c"): C("C-q"),
}
})
```If you'd like the first keystroke to also produce it's own output, `immediately` can be used:
```python
keymap("multi stroke", {
C("C-x"): {
# immediately output "x" when Ctrl-X is pressed
immediately: C("x"),
C("C-c"): C("C-q"),
}
})
```#### Finding out the proper `Key.NAME` literal for a key on your keyboard
From a terminal session run `evtest` and select your keyboard's input device. Now hit the key in question.
```
Event: time 1655723568.594844, type 1 (EV_KEY), code 69 (KEY_NUMLOCK), value 1
Event: time 1655723568.594844, -------------- SYN_REPORT ------------
```Above I've just pressed "clear" on my numpad and see `code 69 (KEY_NUMLOCK)` in the output. For Keyszer this would translate to `Key.NUMLOCK`. You can also browse the [full list of key names](https://github.com/joshgoebel/keyszer/blob/main/src/keyszer/models/key.py) in the source.
#### Finding an Application's `WM_CLASS` and `WM_NAME` using `xprop`
Use the `xprop` command from a terminal:
xprop WM_CLASS WM_NAME
...then click an application window. Let's try it with Google Chrome:
WM_CLASS(STRING) = "google-chrome", "Google-chrome"
WM_NAME(UTF8_STRING) = "README - Google Chrome"Use the second `WM_CLASS` value (in this case `Google-chrome`) when matching `context.wm_class`.
#### Example of Case Insensitive Matching
```py
terminals = ["gnome-terminal","konsole","io.elementary.terminal","sakura"]
terminals = [term.casefold() for term in terminals]
USING_TERMINAL_RE = re.compile("|".join(terminals), re.IGNORECASE)modmap("not in terminal", {
Key.LEFT_ALT: Key.RIGHT_CTRL,
# ...
}, when = lambda ctx: ctx.wm_class.casefold() not in terminals
)modmap("terminals", {
Key.RIGHT_ALT: Key.RIGHT_CTRL,
# ...
}, when = lambda ctx: USING_TERMINAL_RE.search(ctx.wm_class)
)
```## FAQ
**Can I remap the keyboard's `Fn` key?**
_It depends._ Most laptops do not allow this as the `Fn` keypress events are not _directly_ exposed to the operating system. On some keyboards, it's just another key. To find out you can run `evtest`. Point it to your keyboard device and then hit a few keys; then try `Fn`. If you get output, then you can map `Fn`. If not, you can't.
Here is an example from a full size Apple keyboard I have:
```
Event: time 1654948033.572989, type 1 (EV_KEY), code 464 (KEY_FN), value 1
Event: time 1654948033.572989, -------------- SYN_REPORT ------------
Event: time 1654948033.636611, type 1 (EV_KEY), code 464 (KEY_FN), value 0
Event: time 1654948033.636611, -------------- SYN_REPORT ------------
```**What if my keyboard seems laggy or is not repeating keys fast enough?**
You likely need to set the [virtual] keyboards repeat rate to match your actual keyboard.
Here is the command I use:
xset r rate 200 20
For best results your real keyboard and Keyszer [virtual] keyboard should have matching repeat rates. That seems to work best for me. Anytime you restart keyszer you'll need to reconfigure the repeat rate because each time a new virtual keyboard device is created... or maybe it's that there is only a single repeat rate and every time you "plug in" a new keyboard it changes?
_If you could shed some light on this, please [get in touch](https://github.com/joshgoebel/keyszer/issues/55)._
**Does Keyszer support FreeBSD/NetBSD or other BSDs?**
Not at the moment, perhaps never. If you're an expert on the BSD kernel's input layers please
[join the discussion](https://github.com/joshgoebel/keyszer/issues/46). I'm at the very least open to the discussion to find out if this is possible, a good idea, etc...**Does this work with Wayland?**
[Not yet.](https://github.com/joshgoebel/keyszer/issues/27) This is desires but seems impossible at the moment until there is a standardized system to *quickly and easily* determine the app/window that has input focus on Wayland, just like we do so easily on X11.
**Is keyszer compatible with [Kinto.sh](https://github.com/rbreaves/kinto)?**
*That is certainly the plan.* The major reason Kinto.sh required it's own fork [has been resolved](https://github.com/joshgoebel/keyszer/issues/11). Kinto.sh should simply "just work" with `keyszer` (with a few tiny config changes). In fact, hopefully it works better than before since many quirks with the Kinto fork should be resolved. (such as nested combos not working, etc)
Reference:
- [Kinto GitHub issue](https://github.com/rbreaves/kinto/issues/718) regarding the transition.
- Instructions on altering your `kinto.py` config slightly. See [USING_WITH_KINTO.md](https://github.com/joshgoebel/keyszer/blob/main/USING_WITH_KINTO.md).**How can I help or contribute?**
Please open an issue to discuss how you'd like to get involved or respond on one of the existing issues. Also feel free to open new issues for feature requests. Many issues are tagged [good first issue](https://github.com/joshgoebel/keyszer/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) or [help welcome](https://github.com/joshgoebel/keyszer/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+welcome%22).
## License
`keyszer` is distributed under GPL3. See [LICENSE](https://github.com/joshgoebel/keyszer/blob/main/LICENSE).