{"id":13484653,"url":"https://github.com/mooz/xkeysnail","last_synced_at":"2025-05-15T18:03:50.200Z","repository":{"id":38317259,"uuid":"116007779","full_name":"mooz/xkeysnail","owner":"mooz","description":"Yet another keyboard remapping tool for X environment","archived":false,"fork":false,"pushed_at":"2023-05-16T10:22:04.000Z","size":145,"stargazers_count":905,"open_issues_count":90,"forks_count":113,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-03-31T21:48:20.790Z","etag":null,"topics":["emacs-keybindings","keyboard-shortcuts","linux","uinput","xwindow"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mooz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2018-01-02T11:22:47.000Z","updated_at":"2025-03-18T09:07:26.000Z","dependencies_parsed_at":"2022-08-03T10:00:42.360Z","dependency_job_id":"fcdca685-9d51-46ad-9555-832feaecff9c","html_url":"https://github.com/mooz/xkeysnail","commit_stats":{"total_commits":124,"total_committers":18,"mean_commits":6.888888888888889,"dds":0.4193548387096774,"last_synced_commit":"bf3c93b4fe6efd42893db4e6588e5ef1c4909cfb"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mooz%2Fxkeysnail","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mooz%2Fxkeysnail/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mooz%2Fxkeysnail/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mooz%2Fxkeysnail/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mooz","download_url":"https://codeload.github.com/mooz/xkeysnail/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247737788,"owners_count":20987721,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["emacs-keybindings","keyboard-shortcuts","linux","uinput","xwindow"],"created_at":"2024-07-31T17:01:28.514Z","updated_at":"2025-04-07T22:11:09.000Z","avatar_url":"https://github.com/mooz.png","language":"Python","funding_links":[],"categories":["Python","HarmonyOS"],"sub_categories":["Windows Manager"],"readme":"# xkeysnail\n\n`xkeysnail` is yet another keyboard remapping tool for X environment written in Python. It's like\n`xmodmap` but allows more flexible remappings.\n\n![screenshot](http://mooz.github.io/image/xkeysnail_screenshot.png)\n\n- **Pros**\n    - Has high-level and flexible remapping mechanisms, such as\n        - **per-application keybindings can be defined**\n        - **multiple stroke keybindings can be defined** such as `Ctrl+x Ctrl+c` to `Ctrl+q`\n        - **not only key remapping but arbitrary commands defined by Python can be bound to a key**\n    - Runs in low-level layer (`evdev` and `uinput`), making **remapping work in almost all the places**\n- **Cons**\n    - Runs in root-mode (requires `sudo`)\n\nThe key remapping mechanism of `xkeysnail` is based on `pykeymacs`\n(https://github.com/DreaminginCodeZH/pykeymacs).\n\n## Installation\n\nRequires root privilege and **Python 3**.\n\n### Ubuntu\n\n    sudo apt install python3-pip\n    sudo pip3 install xkeysnail\n    \n    # If you plan to compile from source\n    sudo apt install python3-dev\n\n### Fedora\n\n    sudo dnf install python3-pip\n    sudo pip3 install xkeysnail\n    # Add your user to input group if you don't want to run xkeysnail\n    # with sudo (log out and log in again to apply group change)\n    sudo usermod -a -G input $USER\n    \n    # If you plan to compile from source\n    sudo dnf install python3-devel\n    \n### Manjaro/Arch\n\n    # Some distros will need to compile evdev components \n    # and may fail to do so if gcc is not installed.\n    sudo pacman -Syy\n    sudo pacman -S gcc\n    \n### Solus\n\n    # Some distros will need to compile evdev components \n    # and may fail to do so if gcc is not installed.\n    sudo eopkg install gcc\n    sudo eopkg install -c system.devel\n\n### From source\n\n    git clone --depth 1 https://github.com/mooz/xkeysnail.git\n    cd xkeysnail\n    sudo pip3 install --upgrade .\n\n## Usage\n\n    sudo xkeysnail config.py\n\nWhen you encounter the errors like `Xlib.error.DisplayConnectionError: Can't connect to display \":0.0\": b'No protocol specified\\n'\n`, try\n\n    xhost +SI:localuser:root\n    sudo xkeysnail config.py\n\nIf you want to specify keyboard devices, use `--devices` option:\n\n    sudo xkeysnail config.py --devices /dev/input/event3 'Topre Corporation HHKB Professional'\n\nIf you have hot-plugging keyboards, use `--watch` option.\n\nIf you want to suppress output of key events, use `-q` / `--quiet` option especially when running as a daemon.\n\n## How to prepare `config.py`?\n\n(**If you just need Emacs-like keybindings, consider to\nuse\n[`example/config.py`](https://github.com/mooz/xkeysnail/blob/master/example/config.py),\nwhich contains Emacs-like keybindings)**.\n\nConfiguration file is a Python script that consists of several keymaps defined\nby `define_keymap(condition, mappings, name)`\n\n### `define_keymap(condition, mappings, name)`\n\nDefines a keymap consists of `mappings`, which is activated when the `condition`\nis satisfied.\n\nArgument `condition` specifies the condition of activating the `mappings` on an\napplication and takes one of the following forms:\n- Regular expression (e.g., `re.compile(\"YYY\")`)\n    - Activates the `mappings` if the pattern `YYY` matches the `WM_CLASS` of the application.\n    - Case Insensitivity matching against `WM_CLASS` via `re.IGNORECASE` (e.g. `re.compile('Gnome-terminal', re.IGNORECASE)`)\n- `lambda wm_class: some_condition(wm_class)`\n    - Activates the `mappings` if the `WM_CLASS` of the application satisfies the condition specified by the `lambda` function.\n    - Case Insensitivity matching via `casefold()` or `lambda wm_class: wm_class.casefold()` (see example below to see how to compare to a list of names)\n- `None`: Refers to no condition. `None`-specified keymap will be a global keymap and is always enabled.\n\nArgument `mappings` is a dictionary in the form of `{key: command, key2:\ncommand2, ...}` where `key` and `command` take following forms:\n- `key`: Key to override specified by `K(\"YYY\")`\n    - For the syntax of key specification, please refer to the [key specification section](#key-specification).\n- `command`: one of the followings\n    - `K(\"YYY\")`: Dispatch custom key to the application.\n    - `[command1, command2, ...]`: Execute commands sequentially.\n    - `{ ... }`: Sub-keymap. Used to define multiple stroke keybindings. See [multiple stroke keys](#multiple-stroke-keys) for details.\n    - `pass_through_key`: Pass through `key` to the application. Useful to override the global mappings behavior on certain applications.\n    - `escape_next_key`: Escape next key.\n    - Arbitrary function: The function is executed and the returned value is used as a command.\n        - Can be used to invoke UNIX commands.\n\nArgument `name` specifies the keymap name. This is an optional argument.\n\n#### Key Specification\n\nKey specification in a keymap is in a form of `K(\"(\u003cModifier\u003e-)*\u003cKey\u003e\")` where\n\n`\u003cModifier\u003e` is one of the followings\n- `C` or `Ctrl` -\u003e Control key\n- `M` or `Alt` -\u003e Alt key\n- `Shift` -\u003e Shift key\n- `Super` or `Win` -\u003e Super/Windows key\n\nYou can specify left/right modifiers by adding any one of prefixes `L`/`R`.\n\nAnd `\u003cKey\u003e` is a key whose name is defined\nin [`key.py`](https://github.com/mooz/xkeysnail/blob/master/xkeysnail/key.py).\n\nHere is a list of key specification examples:\n\n- `K(\"C-M-j\")`: `Ctrl` + `Alt` + `j`\n- `K(\"Ctrl-m\")`: `Ctrl` + `m`\n- `K(\"Win-o\")`: `Super/Windows` + `o`\n- `K(\"M-Shift-comma\")`: `Alt` + `Shift` + `comma` (= `Alt` + `\u003e`)\n\n#### Multiple stroke keys\n\nWhen you needs multiple stroke keys, define nested keymap. For example, the\nfollowing example remaps `C-x C-c` to `C-q`.\n\n```python\ndefine_keymap(None, {\n    K(\"C-x\"): {\n      K(\"C-c\"): K(\"C-q\"),\n      K(\"C-f\"): K(\"C-q\"),\n    }\n})\n```\n\n#### Checking an application's `WM_CLASS` with `xprop`\n\nTo check `WM_CLASS` of the application you want to have custom keymap, use\n`xprop` command:\n\n    xprop WM_CLASS\n\nand then click the application. `xprop` tells `WM_CLASS` of the application as follows.\n\n    WM_CLASS(STRING) = \"Navigator\", \"Firefox\"\n\nUse the second value (in this case `Firefox`) as the `WM_CLASS` value in your\n`config.py`.\n\n### Example `config.py`\n\nSee [`example/config.py`](https://github.com/mooz/xkeysnail/blob/master/example/config.py).\n\nHere is an excerpt of `example/config.py`.\n\n```python\nfrom xkeysnail.transform import *\n\ndefine_keymap(re.compile(\"Firefox|Google-chrome\"), {\n    # Ctrl+Alt+j/k to switch next/previous tab\n    K(\"C-M-j\"): K(\"C-TAB\"),\n    K(\"C-M-k\"): K(\"C-Shift-TAB\"),\n}, \"Firefox and Chrome\")\n\ndefine_keymap(re.compile(\"Zeal\"), {\n    # Ctrl+s to focus search area\n    K(\"C-s\"): K(\"C-k\"),\n}, \"Zeal\")\n\ndefine_keymap(lambda wm_class: wm_class not in (\"Emacs\", \"URxvt\"), {\n    # Cancel\n    K(\"C-g\"): [K(\"esc\"), set_mark(False)],\n    # Escape\n    K(\"C-q\"): escape_next_key,\n    # C-x YYY\n    K(\"C-x\"): {\n        # C-x h (select all)\n        K(\"h\"): [K(\"C-home\"), K(\"C-a\"), set_mark(True)],\n        # C-x C-f (open)\n        K(\"C-f\"): K(\"C-o\"),\n        # C-x C-s (save)\n        K(\"C-s\"): K(\"C-s\"),\n        # C-x k (kill tab)\n        K(\"k\"): K(\"C-f4\"),\n        # C-x C-c (exit)\n        K(\"C-c\"): K(\"M-f4\"),\n        # cancel\n        K(\"C-g\"): pass_through_key,\n        # C-x u (undo)\n        K(\"u\"): [K(\"C-z\"), set_mark(False)],\n    }\n}, \"Emacs-like keys\")\n```\n\n### Example of Case Insensitivity Matching\n\n```\nterminals = [\"gnome-terminal\",\"konsole\",\"io.elementary.terminal\",\"sakura\"]\nterminals = [term.casefold() for term in terminals]\ntermStr = \"|\".join(str(x) for x in terminals)\n\n# [Conditional modmap] Change modifier keys in certain applications\ndefine_conditional_modmap(lambda wm_class: wm_class.casefold() not in terminals,{\n    # Default Mac/Win\n    Key.LEFT_ALT: Key.RIGHT_CTRL,   # WinMac\n    Key.LEFT_META: Key.LEFT_ALT,    # WinMac\n    Key.LEFT_CTRL: Key.LEFT_META,   # WinMac\n    Key.RIGHT_ALT: Key.RIGHT_CTRL,  # WinMac\n    Key.RIGHT_META: Key.RIGHT_ALT,  # WinMac\n    Key.RIGHT_CTRL: Key.RIGHT_META, # WinMac\n})\n\n# [Conditional modmap] Change modifier keys in certain applications\ndefine_conditional_modmap(re.compile(termStr, re.IGNORECASE), {\n\n    # Default Mac/Win\n    Key.LEFT_ALT: Key.RIGHT_CTRL,   # WinMac\n    Key.LEFT_META: Key.LEFT_ALT,    # WinMac\n    Key.LEFT_CTRL: Key.LEFT_CTRL,   # WinMac\n    Key.RIGHT_ALT: Key.RIGHT_CTRL,  # WinMac\n    Key.RIGHT_META: Key.RIGHT_ALT,  # WinMac\n    Key.RIGHT_CTRL: Key.LEFT_CTRL,  # WinMac\n})\n```\n\n## FAQ\n\n### How do I fix Firefox capturing Alt before xkeysnail?\n\nIn the Firefox location bar, go to `about:config`, search for `ui.key.menuAccessKeyFocuses`, and set the Value to `false`.\n\n\n## License\n\n`xkeysnail` is distributed under GPL.\n\n    xkeysnail\n    Copyright (C) 2018 Masafumi Oyamada\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see \u003chttp://www.gnu.org/licenses/\u003e.\n\n`xkeysnail` is based on `pykeymacs`\n (https://github.com/DreaminginCodeZH/pykeymacs), which is distributed under\n GPL.\n\n    pykeymacs\n    Copyright (C) 2015 Zhang Hai\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see \u003chttp://www.gnu.org/licenses/\u003e.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmooz%2Fxkeysnail","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmooz%2Fxkeysnail","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmooz%2Fxkeysnail/lists"}