A smart, flexible keymapper for Linux.
Project description
keyszer - a smart key remapper for Linux
So this is a fork of xkeysnail?
Yes, this is a fork/reboot of the popular xkeysnail project. The xkeysnail project seems largely unmaintained since it's last release in Fall 2020; by unmaintained I mean that the author no longer seems involved (no commits, no releases, no comms on issues, no response to emails, no GitHub activity, etc).
Is it ready for me to use/test?
Certainly. I've been using it myself full time with a 99% stock Kinto.sh config file. If you're comfortable running from source and sending detailed bug reports we'd love to have your help. If you're comfortable hacking on the source, even better!
See UPGRADING_FROM_XKEYSNAIL.md to get started with your upgrade.
Is this compatible with Kinto.sh?
That is the plan. The major reason that Kinto.sh is using a fork has been resolved. Kinto.sh should simply "just work" with keyszer
. In fact it should work better than before since many quirks with the Kinto version of xkeysnail are resolved. (such as nested combos not working, etc)
Note: If you want to get ahead of the curve you will need to alter your kinto.py
config file just slightly. USING_WITH_KINTO.md
What features/fixes does it already have or have plans for in the near future?
- Slightly simpler configuration API
- more debugging logging
- initial tests framework
- more tests, tests, tests
- entirely rewritten multi-modmap support
- better conditional support (keymaps can now be conditional based on device name)
- #10 No more running as root
root
- #9
Alt
/Super
wrongly trigger other non-combos when used as part of a combo - #7 Support for
Hyper
as a modifier - #2 Support for
WM_NAME
conditionals - #11 Support "sticky"
Command-TAB
to proper support Kinto.sh
Can I help/contribute?
Sure. Just open an issue to discuss how you'd like to get involved or respond on one of the existing issues. Or feel free to open new issues for feature requests.
keyszer - a smart key remapper for Linux
keyszer
is a keyboard remapping tool for the X environment written in Python. It's similar xmodmap
but allows more flexible remappings.
Features
- High-level and flexible remapping mechanisms:
- per-application keybindings
- multiple stroke keybindings such as
Ctrl+x Ctrl+c
toCtrl+q
- multipurpose bindings a regular key can become a modifier when held
- Uses low-level libraries (
evdev
anduinput
), making remapping work almost everywhere
This project was forked from keyszer which itself was based on the older pykeymacs.
Installation
Requires Python 3.
From source
git clone https://github.com/joshgoebel/keyszer.git
cd keyszer
sudo pip3 install --upgrade .
For testing/hacking/contributing
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
Setup Requirements
We will need read/write access to:
/dev/input/event*
- to capture input from actual hardware input devices/dev/uinput
- to present a pretend keyboard to the kernel
Running as a user in the input
group (most secure)
Some distros already have an input group, or you can create one. You'll just need a few udev
rules to make sure that the input devices are all given read/write access to that group.
/etc/udev/rules.d/90-custom-input.rules
:
SUBSYSTEM=="input", GROUP="input"
KERNEL=="uinput", SUBSYSTEM=="misc", GROUP="input"
...and a new user that is a member of that group.
sudo useradd keymapper -G input
systemd
For a sample systemd service file please see keyszer.service.
Running as Your Logged in User
Caveats / Security Concerns
- any running programs can potentially log all your keystrokes (including your passwords!) simply by monitoring the input devices
udev rules:
/etc/udev/rules.d/90-custom-input.rules
:
SUBSYSTEM=="input", GROUP="input"
KERNEL=="uinput", SUBSYSTEM=="misc", OPTIONS+="static_node=uinput", TAG+="uaccess"
With Systemd
Would it make sense to use systemd here also?
With a graphical display manager?
HOW?
With .xinitrc
If you're using a minimal setup you can simply add us to your .xinitrc
. For example to start us up and then start Awesome WM.
keyszer &
exec awesome
Running as root (most insecure)
Caveats / Security Concerns
Don't do this, it's bad, dangerous, and wholly unnecessary.
Usage
keyszer
A successful startup will look a bit like:
keyszer v0.4.99
(--) CONFIG: /home/jgoebel/.config/keyszer/config.py
(+K) Grabbing Apple, Inc Apple Keyboard (/dev/input/event3)
(--) Ready to process input.
Limiting Devices
Limit remapping to specify devices with --devices
:
keyszer --devices /dev/input/event3 'Topre Corporation HHKB Professional'
The path or full device name can be used.
Other Options:
-c
,--config
- location of the configuration file-w
,--watch
- watch for new keyboard devices to hot-plug-v
- much increased verbosity to help with debugging--list-devices
- list all available input devices
Configuration
By default we will look for the configuration in ~/.config/keyszer/config.py
but you can override this location with the -c
/--config
switch. The configuration file is a Python script that defines modmaps, keymaps, and other configuration details.
For an example configuration please see example/config.py
.
The configuration API:
timeout(s)
add_modifier(name, aliases, key/keys)
keymap(name, map, when)
modmap(name, map)
multipurpose_modmap(name, map)
conditional(condition_fn, map)
- used to wrap maps and only apply them conditionally
timeout(s)
Sets the number of seconds before multi-purpose modmaps timeout... ie, how long you have to press and release a key before it's instead assuming it's part of a combo.
add_modifier(name, aliases, key/keys)
Allows you to add custom modifiers and then map them to actual keys.
add_modifier("HYPER", aliases = ["Hyper"], key = Key.F24)
wm_class_match(re_str)
Helper to make matching conditionals a tiny bit simpler.
conditional(wm_class_match("^Firefox$"),
keymap("Firefox",{
# ... keymap here
}))
not_wm_class_match(re_str)
The opposite of wm_class_match
, matches only when the regex is NOT a match.
modmap(name, mappings, when)
Entirely maps one key to a different key, in all contexts. Note that the default modmap will be overruled by any conditional modmaps that apply. when
can optionally be passed to make the modmap conditional.
modmap("default", {
# mapping caps lock to left control
Key.CAPSLOCK: Key.LEFT_CTRL
})
multipurpose_modmap(name, mappings)
Used to map a key with multiple-purposes, both for regular usage and use as a modifier (when held down).
multipurpose_modmap("default",
# Enter is enter when pressed and released. Control when held down.
{Key.ENTER: [Key.ENTER, Key.RIGHT_CTRL]}
)
keymap(name, mappings)
Defines a keymap consisting of mappings
of the input combos mapped to output equivalents.
keymap("Firefox", {
# when Cmd-S is hit instead send Ctrl-S
K("Cmd-s"): K("Ctrl-s"),
}, when = lambda ctx: ctx.wm_class == "Firefox")
Argument mappings
is a dictionary in the form of {key: command, key2: command2, ...}
where key
and command
take following forms:
key
: Key to override specified byK("YYY")
- For the syntax of key specification, please refer to the key specification section.
command
: one of the followingsK("YYY")
: Dispatch custom key to the application.[command1, command2, ...]
: Execute commands sequentially.{ ... }
: Sub-keymap. Used to define multiple stroke keybindings. See multiple stroke keys for details.escape_next_key
: Escape next key.- arbitrary function: The function is executed and the returned value is used as a command.
Argument name
specifies the keymap name. Every keymap should have a name. default
is suggested for non-conditional keymaps.
conditional(fn, map)
Applies a map conditionally only when the fn
function evaluates True
. The below example is a modmap that is only active when the current WM_CLASS
is Terminal
.
conditional(
lambda ctx: ctx.wm_class == "Terminal",
modmap({
# ...
})
)
The context
object passed to the fn
function has several attributes:
wm_class
- the WM_CLASS of the currently focused X11 windowdevice_name
- the name of the device an input event originated on
Key Specification
Key specification in a keymap is in a form of K("(<Modifier>-)*<Key>")
where
<Modifier>
is one of the following:
C
orCtrl
-> Control keyAlt
-> Alt keyShift
-> Shift keySuper
orWin
orCmd
-> Super/Windows/Command key
You can specify left/right modifiers by adding any one of prefixes L
/R
.
<Key>
is a key whose name is defined
in key.py
.
Here is a list of key specification examples:
K("LC-Alt-j")
:Left Ctrl
+Alt
+j
K("Ctrl-m")
:Ctrl
+m
K("Win-o")
:Super/Windows
+o
K("Alt-Shift-comma")
:Alt
+Shift
+comma
Multiple Stroke Keys
When you needs multiple stroke keys, define a nested keymap. For example, the
following example remaps C-x C-c
to C-q
.
keymap(None, {
K("C-x"): {
K("C-c"): K("C-q"),
}
})
Finding an Application's WM_CLASS
with xprop
To check WM_CLASS
of the application you want to have custom keymap, use
xprop
command:
xprop WM_CLASS
and then click the application. xprop
tells WM_CLASS
of the application as follows.
WM_CLASS(STRING) = "Navigator", "Firefox"
Use the second value (in this case Firefox
) when matching context.wm_class
when using a conditional
.
Example of Case Insensitivity Matching
terminals = ["gnome-terminal","konsole","io.elementary.terminal","sakura"]
terminals = [term.casefold() for term in terminals]
termStr = "|".join(str(x) for x in terminals)
conditional(
lambda ctx: ctx.wm_class.casefold() not in terminals,
modmap({
Key.LEFT_ALT: Key.RIGHT_CTRL, # WinMac
# ...
}))
conditional(
lambda ctx: re.compile(termStr, re.IGNORECASE).search(ctx.wm_class),
modmap("default", {
Key.LEFT_ALT: Key.RIGHT_CTRL, # WinMac
# ...
}))
FAQ
Can I remap the Fn
key?
Most laptops do not allow this as the Fn
key is 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 ------------
License
keyszer
is distributed under GPL. See our LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.