Skip to main content

A smart, flexible key remapper for Linux/X11.

Project description

keyszer - a smart key remapper for Linux/X11

work in progress discord

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 to use/test?

Maybe. I'd call it late alpha or early beta. I've been code refactoring (and adding much needed tests) more than directly using, but that is now changing. If you're comfortable running from the source and sending detailed bug reports I'd love to have your help. If you're comfortable hacking on the source, even better!

Is this compatible with Kinto.sh?

That is the goal. I haves plans to address the one major reason that kinto is using a fork with "sticky" modifier keys (for some combos)... at that point kinto should work just fine with keyszer - in fact even better since nested keymaps will start working again.

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
  • 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/X11

latest version license

discord open issues help welcome issues good first issue

keyszer is a keyboard remapping tool for 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 to Ctrl+q
    • multipurpose bindings a regular key can become a modifier when held
  • Uses low-level libraries (evdev and uinput), making remapping work almost everywhere

This project was originally forked from keyszer which itself was based on the older pykeymacs. The primary goals are to once again have an active maintainer and focus on improved reliability and security (no more root!).

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 - specify the location of the configuration file to load
  • -w, --watch - watch for new keyboard devices that may be hot-plugged
  • -q, --quiet - suppress output of key events, especially when running as a daemon.
  • --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)
  • keymap(name, map)
  • 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.

modmap(name, mappings)

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.

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("mac like", {
    # when Cmd-S is hit instead send Ctrl-S
    K("Cmd-s"): K("Ctrl-s"),
})

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 by K("YYY")
  • command: one of the followings
    • K("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.
    • pass_through_key: Pass through key to the application. Useful to override the global mappings behavior on certain applications.
    • 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 window
  • device_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 or Ctrl -> Control key
  • Alt -> Alt key
  • Shift -> Shift key
  • Super or Win or Cmd -> 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

None yet.

License

keyszer is distributed under GPL. See our LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

keyszer-0.4.99.tar.gz (33.2 kB view hashes)

Uploaded Source

Built Distribution

keyszer-0.4.99-py3-none-any.whl (31.1 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page