Code Monkey home page Code Monkey logo

xkeysnail's Introduction

xkeysnail

xkeysnail is yet another keyboard remapping tool for X environment written in Python. It's like xmodmap but allows more flexible remappings.

screenshot

  • Pros
    • Has high-level and flexible remapping mechanisms, such as
      • per-application keybindings can be defined
      • multiple stroke keybindings can be defined such as Ctrl+x Ctrl+c to Ctrl+q
      • not only key remapping but arbitrary commands defined by Python can be bound to a key
    • Runs in low-level layer (evdev and uinput), making remapping work in almost all the places
  • Cons
    • Runs in root-mode (requires sudo)

The key remapping mechanism of xkeysnail is based on pykeymacs (https://github.com/DreaminginCodeZH/pykeymacs).

Installation

Requires root privilege and Python 3.

Ubuntu

sudo apt install python3-pip
sudo pip3 install xkeysnail

# If you plan to compile from source
sudo apt install python3-dev

Fedora

sudo dnf install python3-pip
sudo pip3 install xkeysnail
# Add your user to input group if you don't want to run xkeysnail
# with sudo (log out and log in again to apply group change)
sudo usermod -a -G input $USER

# If you plan to compile from source
sudo dnf install python3-devel

Manjaro/Arch

# Some distros will need to compile evdev components 
# and may fail to do so if gcc is not installed.
sudo pacman -Syy
sudo pacman -S gcc

Solus

# Some distros will need to compile evdev components 
# and may fail to do so if gcc is not installed.
sudo eopkg install gcc
sudo eopkg install -c system.devel

From source

git clone --depth 1 https://github.com/mooz/xkeysnail.git
cd xkeysnail
sudo pip3 install --upgrade .

Usage

sudo xkeysnail config.py

When you encounter the errors like Xlib.error.DisplayConnectionError: Can't connect to display ":0.0": b'No protocol specified\n' , try

xhost +SI:localuser:root
sudo xkeysnail config.py

If you want to specify keyboard devices, use --devices option:

sudo xkeysnail config.py --devices /dev/input/event3 'Topre Corporation HHKB Professional'

If you have hot-plugging keyboards, use --watch option.

If you want to suppress output of key events, use -q / --quiet option especially when running as a daemon.

How to prepare config.py?

(If you just need Emacs-like keybindings, consider to use example/config.py, which contains Emacs-like keybindings).

Configuration file is a Python script that consists of several keymaps defined by define_keymap(condition, mappings, name)

define_keymap(condition, mappings, name)

Defines a keymap consists of mappings, which is activated when the condition is satisfied.

Argument condition specifies the condition of activating the mappings on an application and takes one of the following forms:

  • Regular expression (e.g., re.compile("YYY"))
    • Activates the mappings if the pattern YYY matches the WM_CLASS of the application.
    • Case Insensitivity matching against WM_CLASS via re.IGNORECASE (e.g. re.compile('Gnome-terminal', re.IGNORECASE))
  • lambda wm_class: some_condition(wm_class)
    • Activates the mappings if the WM_CLASS of the application satisfies the condition specified by the lambda function.
    • 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)
  • None: Refers to no condition. None-specified keymap will be a global keymap and is always enabled.

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.
      • Can be used to invoke UNIX commands.

Argument name specifies the keymap name. This is an optional argument.

Key Specification

Key specification in a keymap is in a form of K("(<Modifier>-)*<Key>") where

<Modifier> is one of the followings

  • C or Ctrl -> Control key
  • M or Alt -> Alt key
  • Shift -> Shift key
  • Super or Win -> Super/Windows key

You can specify left/right modifiers by adding any one of prefixes L/R.

And <Key> is a key whose name is defined in key.py.

Here is a list of key specification examples:

  • K("C-M-j"): Ctrl + Alt + j
  • K("Ctrl-m"): Ctrl + m
  • K("Win-o"): Super/Windows + o
  • K("M-Shift-comma"): Alt + Shift + comma (= Alt + >)

Multiple stroke keys

When you needs multiple stroke keys, define nested keymap. For example, the following example remaps C-x C-c to C-q.

define_keymap(None, {
    K("C-x"): {
      K("C-c"): K("C-q"),
      K("C-f"): K("C-q"),
    }
})

Checking 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) as the WM_CLASS value in your config.py.

Example config.py

See example/config.py.

Here is an excerpt of example/config.py.

from xkeysnail.transform import *

define_keymap(re.compile("Firefox|Google-chrome"), {
    # Ctrl+Alt+j/k to switch next/previous tab
    K("C-M-j"): K("C-TAB"),
    K("C-M-k"): K("C-Shift-TAB"),
}, "Firefox and Chrome")

define_keymap(re.compile("Zeal"), {
    # Ctrl+s to focus search area
    K("C-s"): K("C-k"),
}, "Zeal")

define_keymap(lambda wm_class: wm_class not in ("Emacs", "URxvt"), {
    # Cancel
    K("C-g"): [K("esc"), set_mark(False)],
    # Escape
    K("C-q"): escape_next_key,
    # C-x YYY
    K("C-x"): {
        # C-x h (select all)
        K("h"): [K("C-home"), K("C-a"), set_mark(True)],
        # C-x C-f (open)
        K("C-f"): K("C-o"),
        # C-x C-s (save)
        K("C-s"): K("C-s"),
        # C-x k (kill tab)
        K("k"): K("C-f4"),
        # C-x C-c (exit)
        K("C-c"): K("M-f4"),
        # cancel
        K("C-g"): pass_through_key,
        # C-x u (undo)
        K("u"): [K("C-z"), set_mark(False)],
    }
}, "Emacs-like keys")

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 modmap] Change modifier keys in certain applications
define_conditional_modmap(lambda wm_class: wm_class.casefold() not in terminals,{
    # Default Mac/Win
    Key.LEFT_ALT: Key.RIGHT_CTRL,   # WinMac
    Key.LEFT_META: Key.LEFT_ALT,    # WinMac
    Key.LEFT_CTRL: Key.LEFT_META,   # WinMac
    Key.RIGHT_ALT: Key.RIGHT_CTRL,  # WinMac
    Key.RIGHT_META: Key.RIGHT_ALT,  # WinMac
    Key.RIGHT_CTRL: Key.RIGHT_META, # WinMac
})

# [Conditional modmap] Change modifier keys in certain applications
define_conditional_modmap(re.compile(termStr, re.IGNORECASE), {

    # Default Mac/Win
    Key.LEFT_ALT: Key.RIGHT_CTRL,   # WinMac
    Key.LEFT_META: Key.LEFT_ALT,    # WinMac
    Key.LEFT_CTRL: Key.LEFT_CTRL,   # WinMac
    Key.RIGHT_ALT: Key.RIGHT_CTRL,  # WinMac
    Key.RIGHT_META: Key.RIGHT_ALT,  # WinMac
    Key.RIGHT_CTRL: Key.LEFT_CTRL,  # WinMac
})

FAQ

How do I fix Firefox capturing Alt before xkeysnail?

In the Firefox location bar, go to about:config, search for ui.key.menuAccessKeyFocuses, and set the Value to false.

License

xkeysnail is distributed under GPL.

xkeysnail
Copyright (C) 2018 Masafumi Oyamada

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

xkeysnail is based on pykeymacs (https://github.com/DreaminginCodeZH/pykeymacs), which is distributed under GPL.

pykeymacs
Copyright (C) 2015 Zhang Hai

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

xkeysnail's People

Contributors

amaya382 avatar ellakk avatar jvasile avatar kevinslashslash avatar lenbok avatar mbachry avatar mooz avatar nlfiasel avatar nobuto-m avatar rbreaves avatar tangxinfa avatar terop avatar watawuwu avatar wkwkw avatar yuya373 avatar zhanghai avatar zw963 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

xkeysnail's Issues

Define multiple layers for individual keys?

In .xmodmap it is possible to define multiple layers that gives control over what symbol appears when using modifiers like Shift or AltGR. A good example of what this allows to do is the .xmodmap used by the german neo_de layout (see: https://neo-layout.org/index_en.html and https://neo-layout.org/neo_de.xmodmap).

Looking at the provided example and the code I don't see the ability to define multiple layers for an individual key. Is there a way to do this using xkeysnail that I am missing?

Stuck control key

I am having a weird issue that I am trying to troubleshoot. Every once in a while, while running xkeysnail with the default config, the Ctrl key becomes stuck, in that all keys that I pressed are passed with a Ctrl modifier on all apps. After sometime (of me trying different combinations to unpress Ctrl), everything turns back to normal. I am sure that this is a problem with xkeysnail because I managed once to get to the terminal window that runs xkeysnail and pressed C (which was coupled with the stuck Ctrl) and that killed xkeysnail and everything went back to normal.

Any ideas? Or perhaps some way to debug this?

Second keyboard

Does this have support for a second keyboard? So it can tell the difference between the two

Thanks for your work, can't wait for xkeysnail

Mr. mooz , I just open an new issue to say thanks for your work and can't wait for xkeysnail, I am a big fan of the tools you build, including percol,js2-mode,and keysnail.I am here to express my appreciation and how excited I am :)

link hints

Hello,

I have tried xkeysnail and works like a charm, however I could not find any link hint functionality like the HoK plugin of keysnail. Are there plans to implement that?

Martí

Suspending with Ctrl-Z freezes all controls, hard reboot required

Now, I know this isn't a bug, per se, but more of a lack of user fault tolerance. As a newish Linux user, as I was trying to find a way to make the process run in the background, I hit Ctrl-Z and it suspended the process. Little did I know at the time that meant everything stopped and didn't run in the background. Because everything stopped my keyboard and mouse ceased to function. I had no known way to resume the process or fully quit it, so I held down the power button to force a restart.

Is there a workaround or trick to avoid that if I accidentally do that again in the future? Maybe there's something I can include in the command to help?

I don't know if there is a possible solution based on the way this is currently implemented, but I would have expected that if the process was suspended, all keys go back to their original function. I'm very curious.

Side note: This is an awesome tool. I was fed up with Xmodmap and its complexity. I'm glad I found this.

Breaking changes of mod key release in xkeysnail 0.3.0

Breaking changes in xkeysnail 0.3.0.

The behavior is very different when you operate the keyboard in the following order.

press Ctrl
press Alt
release Ctrl
press W

xkeysnail 0.2.0: perform Alt-w
xkeysnail 0.3.0: perform w

This behavior is especially problematic when you are operating Emacs.
I perform C-SPCC-eM-w.
When performing an operation such as, I have to be careful not to release the Ctrl key before hitting the Alt key but w will be typed.

I think this breaking change because
Release mapped modifiers after the original is released. closes #70. by rbreaves - Pull Request #71 - mooz/xkeysnail
I'm not sure why this change was necessary in the first place when I look at the issue.

I'd like to ask @rbreaves what his intentions were in making the change to fix the disruptive change while maintaining the significance of the change.


original strings

xkeysnail 0.3.0の破壊的変更について.

以下の順にキーボードを操作したとき動作が大きく異なります.

press Ctrl
press Alt
release Ctrl
press w

xkeysnail 0.2.0: perform Alt-w
xkeysnail 0.3.0: perform w

この動作は特にEmacsを操作している時に問題になります.
C-SPCC-eM-w
のような操作をした時に,
Altキーを押す前にCtrlキーを離さないように慎重に気をつけないとwが入力されてしまいます.

私はこの破壊的変更が
Release mapped modifiers after the original is released. Closes #70. by rbreaves · Pull Request #71 · mooz/xkeysnail
によって引き起こされたと思っていますが,
そもそもこの変更が何故必要だったのかissueを見てもよく分かりませんでした.

この変更の意義を保ったまま破壊的変更を修正するために @rbreaves に変更の意図を聞きたいです.

logging behavior

I'm trying to run xkeysnail as a systemd service. However, xkeysnail outputs key events to stdout to be logged to systemd journald. It would be nice to have some options like -q / --quiet to suppress key activities when running as a daemon. Or suppressing key activities by default and adding another option like -v / --verbose would also work.

From systemd point of view, StandardOutput=null to the unit file works though.

$ sudo systemctl status xkeysnail
● xkeysnail.service - Service for xkeysnail
   Loaded: loaded (/etc/systemd/system/xkeysnail.service; enabled; vendor preset: enabled)
   Active: active (running) since Sun 2018-06-24 17:34:43 JST; 3min 33s ago
 Main PID: 5116 (xkeysnail)
    Tasks: 1 (limit: 4915)
   CGroup: /system.slice/xkeysnail.service
           └─5116 /usr/bin/python3 /usr/local/bin/xkeysnail /etc/xkeysnail/config.py

Jun 24 17:37:17 lifebook xkeysnail[5116]: WM_CLASS 'Gnome-terminal' | active keymaps = []
Jun 24 17:37:17 lifebook xkeysnail[5116]: G
Jun 24 17:37:17 lifebook xkeysnail[5116]: WM_CLASS 'Gnome-terminal' | active keymaps = []
Jun 24 17:37:17 lifebook xkeysnail[5116]: I
Jun 24 17:37:17 lifebook xkeysnail[5116]: WM_CLASS 'Gnome-terminal' | active keymaps = []
Jun 24 17:37:17 lifebook xkeysnail[5116]: T
Jun 24 17:37:17 lifebook xkeysnail[5116]: WM_CLASS 'Gnome-terminal' | active keymaps = []
Jun 24 17:37:17 lifebook xkeysnail[5116]: SPACE

Release new version to PyPI

It's over 1 year since xkeysnail has been released to PyPI. Latest xkeysnail got many features like --watch, --quiet. Advices written in README is not applicable to released version. Why not release a new version, say 0.1.1?

A condition for define_modmap()

I want to use muhenkan key as an extra modifier key like this:

  • muhenkan+ (h j k l) -> left down up right
  • Shift+muhenkan+ (h j k l) -> Shift+ (left down up right)
  • C+muhenkan+ (h j k l) -> C+ (left down up right)
  • M+muhenkan+ (h j k l) -> M+ (left down up right)
  • Super+muhenkan+ (h j k l) -> Super+ (left down up right)
  • ...

It is impossible to write K("Muhenkan-h"): K("left"). But if define_modmap() receives a condition, the demand is satisfied.

For example (pseudo code):

define_modmap({
    Key.H: Key.LEFT,
    Key.J: Key.DOWN,
    Key.K: Key.UP,
    Key.L: Key.RIGHT,
}, condition=WHILE_MUHENKAN_PRESSED)

:)

RFE: define_conditional_multipurpose_modmap()

I would like to define xkeysnail transformations that just apply to certain keyboards (e.g different depending on whether using laptop built-in vs external keyboard).

define_conditional_modmap() already lets us create a modmap that can be controlled by a condition that includes the device_name

It would be great if we had define_conditional_multipurpose_modmap() so that multipurpose modmaps could similarly be controlled by a condition.

--watch not working

I tried running xkeysnail with --watch and plugged in a keyboard, but nothing happened.

After some easy debugging, I found that at plugging in a keyboard, add_new_devices() is called properly and getting event.name is ok, but no code after new_device = InputDevice("/dev/input/" + event.name) is not working.

My environment is as below.
Ubuntu: 16.0.4
xkeysnail: 0.1.0
python: 3.6.4
pip: 19.0.3

Thanks.

Adjusting keyboard repeat rate

Hi there!

This is a great package. One issue I've noticed though is that keyboard repeat rates are changed when using py-evdev-uinput. For example, normally when I press and hold down "a", I get a lot of repeating A's. With xkeysnail, though, there's a noticeable delay from when the repetitions start happening.

Using the utility xset -q, I can see that my initial settings are:
auto repeat delay: 200 repeat rate: 62
But with xkeysnail it gets set to:
auto repeat delay: 660 repeat rate: 25.

The workaround for me right now is to set it manually after starting xkeysnail using xset r rate 200 62.

There should be a way to set the repeat rate in evdev with a specific KbdInfo, but I haven't as of yet gotten it to work.
I'll try looking into it more.

Thanks again for this useful software!

Keep modifiers held down until the user releases it.

I have confirmed this through a lot of testing that xkeysnail releases the held down modifier once a standard key has been pressed and released instead of also keeping the modifier (possibly remapped modifier) held until the original modifier is released. I have been trying to go through the code to understand where I might be able to modify the K function to support holding down the modifier keys until they are truly released, but I have not been able to find this thus far.

There are three very common modifiers used under Linux (and other OS's too) that require this ability if there is any hope to properly remap them - Alt-Tab, Ctrl-Tab and Super-Tab.

During my testing I am wondering if the evdev library xkeysnail uses could be the limitation or how evtest operates even. They show the modifier keys repeating input up until another key is pressed then they become quiet - however they have never emitted a release signal (for the modifier until it is physically released), so in that context my assumption is the key is still being held and should be visible to the underlying OS, but perhaps that is not true or xkeysnail is somehow sending the modifier again on top of itself. I can't seem to verify that xkeysnail is doing that though with evtest or via "python -m evdev.evtest".

From what I can tell it behaves more like the modifier is being released with the combo of whatever other key is released and then the modifier is repeated if the key is pressed again. This is based on observation inside Sublime Text and opened files. It changes tabs as though I am tapping both keys but not holding down Ctrl - which causes it to only change between 2 files instead of all files.

Possible Workaround
If it is not possible to remap to a totally different modifier key while listening to the original's release signal then can we possibly just pass the modifier key through only while still remapping a destination key? That could also satisfy what I am after.

[nits] Function call for default value is evaluated once in python

In python, function call for default value is evaluated only once as the following:

$ python
Python 3.7.4 (default, Oct 13 2019, 09:39:19)
[GCC 7.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import random
>>> def f(x=random.random()):
...     return x
...
>>> f()
0.5294147256361105
>>> f()
0.5294147256361105

https://github.com/mooz/xkeysnail/blob/master/xkeysnail/transform.py#L15 uses this parttern.
IMHO, the following code is more predictable (or, if what really you mean is evaluate every time, I recommend to write that code).

DEFAULT_DISPLAY = Xlib.display.Display()

def get_active_window_wm_class(display=DEFAULT_DISPLAY):
    """Get active window's WM_CLASS"""

Firefox captures alt before xkeysnail

When trying to get emacs like key bindings in firefox the alt key is captured by firefox before xkeysnail. What follows is that every time you try to give (M-_) command the Firefox opens up some sort of file options menu and commands are lost in that.

Users should be informed this will happen because with this I can't really use xkeysnail for the main thing I wanted it for. I have run into this issue before with autohotkey on windows for example but I thought it might work under linux.

Xlib.error.DisplayConnectionError: Can't connect to display ":0": b'No protocol specified\n'

my env

  • DELL XPS13 (9343)
  • Ubuntu 18.04.1 LTS (Bionic Beaver) Desktop
  • xkeysnail 0.1.0

how to reproduce the error

kozo2@kozo2-XPS-13-9343:~/Downloads/xkeysnail-master/example$ which python3
/usr/bin/python3
kozo2@kozo2-XPS-13-9343:~/Downloads/xkeysnail-master/example$ python3 -V
Python 3.6.6
kozo2@kozo2-XPS-13-9343:~/Downloads/xkeysnail-master/example$ python3 -m pip -V
pip 18.1 from /usr/local/lib/python3.6/dist-packages/pip (python 3.6)
kozo2@kozo2-XPS-13-9343:~/Downloads/xkeysnail-master/example$ python3 -m pip list
Package               Version  
--------------------- ---------
apturl                0.5.2    
asn1crypto            0.24.0   
Brlapi                0.6.6    
certifi               2018.1.18
chardet               3.0.4    
command-not-found     0.3      
cryptography          2.1.4    
cupshelpers           1.0      
defer                 1.0.6    
distro-info           0.18     
evdev                 1.1.2    
httplib2              0.9.2    
idna                  2.6      
keyring               10.6.0   
keyrings.alt          3.0      
language-selector     0.1      
launchpadlib          1.10.6   
lazr.restfulclient    0.13.5   
lazr.uri              1.0.3    
louis                 3.5.0    
macaroonbakery        1.1.3    
Mako                  1.0.7    
MarkupSafe            1.0      
oauth                 1.0.1    
olefile               0.45.1   
pexpect               4.2.1    
Pillow                5.1.0    
pip                   18.1     
protobuf              3.0.0    
pycairo               1.16.2   
pycrypto              2.6.1    
pycups                1.9.73   
pygobject             3.26.1   
pymacaroons           0.13.0   
PyNaCl                1.1.2    
pyRFC3339             1.0      
python-apt            1.6.3    
python-debian         0.1.32   
python-xlib           0.23     
pytz                  2018.3   
pyxdg                 0.25     
PyYAML                3.12     
reportlab             3.4.0    
requests              2.18.4   
requests-unixsocket   0.1.5    
SecretStorage         2.3.1    
setuptools            39.0.1   
simplejson            3.13.2   
six                   1.11.0   
system-service        0.3      
systemd-python        234      
ubuntu-drivers-common 0.0.0    
ufw                   0.35     
unattended-upgrades   0.1      
urllib3               1.22     
usb-creator           0.3.3    
wadllib               1.3.2    
wheel                 0.30.0   
xkeysnail             0.1.0    
xkit                  0.0.0    
zope.interface        4.3.2    
kozo2@kozo2-XPS-13-9343:~/Downloads/xkeysnail-master/example$ which xkeysnail
/usr/local/bin/xkeysnail
kozo2@kozo2-XPS-13-9343:~/Downloads/xkeysnail-master/example$ sudo xkeysnail config.py
[sudo] password for kozo2: 

██╗  ██╗██╗  ██╗███████╗██╗   ██╗
╚██╗██╔╝██║ ██╔╝██╔════╝╚██╗ ██╔╝
 ╚███╔╝ █████╔╝ █████╗   ╚████╔╝
 ██╔██╗ ██╔═██╗ ██╔══╝    ╚██╔╝
██╔╝ ██╗██║  ██╗███████╗   ██║
╚═╝  ╚═╝╚═╝  ╚═╝╚══════╝   ╚═╝
  ███████╗███╗   ██╗ █████╗ ██╗██╗
  ██╔════╝████╗  ██║██╔══██╗██║██║
  ███████╗██╔██╗ ██║███████║██║██║
  ╚════██║██║╚██╗██║██╔══██║██║██║
  ███████║██║ ╚████║██║  ██║██║███████╗
  ╚══════╝╚═╝  ╚═══╝╚═╝  ╚═╝╚═╝╚══════╝
                             v0.1.0

Traceback (most recent call last):
  File "/usr/local/bin/xkeysnail", line 6, in <module>
    cli_main()
  File "/usr/local/lib/python3.6/dist-packages/xkeysnail/__init__.py", line 44, in cli_main
    eval_file(args.config)
  File "/usr/local/lib/python3.6/dist-packages/xkeysnail/__init__.py", line 5, in eval_file
    exec(compile(file.read(), path, 'exec'), globals())
  File "config.py", line 4, in <module>
    from xkeysnail.transform import *
  File "/usr/local/lib/python3.6/dist-packages/xkeysnail/transform.py", line 13, in <module>
    def get_active_window_wm_class(display=Xlib.display.Display()):
  File "/usr/local/lib/python3.6/dist-packages/Xlib/display.py", line 89, in __init__
    self.display = _BaseDisplay(display)
  File "/usr/local/lib/python3.6/dist-packages/Xlib/display.py", line 71, in __init__
    protocol_display.Display.__init__(self, *args, **keys)
  File "/usr/local/lib/python3.6/dist-packages/Xlib/protocol/display.py", line 167, in __init__
    raise error.DisplayConnectionError(self.display_name, r.reason)
Xlib.error.DisplayConnectionError: Can't connect to display ":0": b'No protocol specified\n'
kozo2@kozo2-XPS-13-9343:~/Downloads/xkeysnail-master/example$ cat config.py 
# -*- coding: utf-8 -*-

import re
from xkeysnail.transform import *

# [Global modemap] Change modifier keys as in xmodmap
define_modmap({
    Key.CAPSLOCK: Key.LEFT_CTRL
})

# [Conditional modmap] Change modifier keys in certain applications
define_conditional_modmap(re.compile(r'Emacs'), {
    Key.RIGHT_CTRL: Key.ESC,
})

# [Multipurpose modmap] Give a key two meanings. A normal key when pressed and
# released, and a modifier key when held down with another key. See Xcape,
# Carabiner and caps2esc for ideas and concept.
define_multipurpose_modmap(
    # Enter is enter when pressed and released. Control when held down.
    {Key.ENTER: [Key.ENTER, Key.RIGHT_CTRL]}

    # Capslock is escape when pressed and released. Control when held down.
    # {Key.CAPSLOCK: [Key.ESC, Key.LEFT_CTRL]
    # To use this example, you can't remap capslock with define_modmap.
)


# Keybindings for Firefox/Chrome
define_keymap(re.compile("Firefox|Google-chrome"), {
    # Ctrl+Alt+j/k to switch next/previous tab
    K("C-M-j"): K("C-TAB"),
    K("C-M-k"): K("C-Shift-TAB"),
    # Type C-j to focus to the content
    K("C-j"): K("C-f6"),
    # very naive "Edit in editor" feature (just an example)
    K("C-o"): [K("C-a"), K("C-c"), launch(["gedit"]), sleep(0.5), K("C-v")]
}, "Firefox and Chrome")
kozo2@kozo2-XPS-13-9343:~/Downloads/xkeysnail-master/example$ 

Feature request: multipurpose_modmap add elapsed time support

Current press alone and press together with other keys are different.

The not cover use case: user intends to press a key combination, and the modifier is pressed first, then user changes mind, and abort the operation, the modifier is released, but the key corresponding to press alone is sent by mistake.

The correct behavior is: if the single modifier is pressed longer than the elapsed time, don't do anything.

want to distinguish left/right modifier keys

I wanted to use Control_R to emacs key-binds and Control_L to standard key-binds.

I modified the key.py like following and successfully realized what I wanted.

@classmethod
def _get_modifier_map(cls):
    return {
        # cls.CONTROL: {Key.LEFT_CTRL, Key.RIGHT_CTRL},
        cls.CONTROL: {Key.RIGHT_CTRL},
        cls.ALT: {Key.LEFT_ALT, Key.RIGHT_ALT},
        .....

However, it will be better if we can specify right/left modifiers separately like: CR-a / CL-a, ShiftR-a/ShiftL-a, and so on.

Biding key 'screenshot' removes the keyboard

I am starting to use xkeysnail instead of setxkbmap and xbindkeys. I really appreciate your work, but there is one potential bug that is blocking my way:

I want to bind PRINT_SCREEN key to launch my script and take a screenshot, configured as below:

define_keymap(None, {
    K("SYSRQ"): launch(["screenshot"])
}, "Global")

However instead of invoking the program, my keyboard got removed and I had to switch to another keyboard to stop the program. Please see logs below:

WM_CLASS 'URxvt' | active keymaps = [Global]
SYSRQ
Device removed: Topre Corporation HHKB Professional

Is it a bug of xkeysnail, or a stupid error in my config file?

Thanks!

key down, key up の処理を記述したい

要望

key down key up の処理を記述したい

key down -> KD()
key up -> KU()
のような、設定項目を追加できないでしょうか?

下記の設定の場合、
TABキーをダウンしている間は、Aを送信し続ける
TABキーをアップしたら、Bを送信

define_keymap(re.compile("Foo"), {
    KD("TAB"): K("A"),
    KU("TAB"): K("B"),
}, "Foo")

利用目的

現在時刻の秒数が、偶数ならX、奇数ならY
など細かい制御を入れたいなと思っています
キーを押ししていると、XXXXXX... -> YYYYYY.... など
キーをアップせずに、途中で変えるのが目的です

最終的には、、、

下記の様に、設定したいです。

define_keymap(re.compile("Foo"), {
    KD("TAB"): myFunc1(),
    KU("TAB"): myFunc2(),
}, "Foo")

def myFunc1(self):
    if (datetime.now().strftime("%S") % 2 == 0)
        K('X')
        return
    
    K('Y')

def myFunc1(self):
    if (datetime.now().strftime("%S") % 2 == 0)
        K('A')
        return
    
    K('B')

warning: refname 'HEAD' is ambiguous

This is a nit picking, but there is a tag named as "HEAD" which gives a warning in git.

$ git clone https://github.com/mooz/xkeysnail.git
$ cd xkeysnail/

$ git status
warning: refname 'HEAD' is ambiguous.
warning: refname 'HEAD' is ambiguous.
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

$ git tag
HEAD

Running in background

Hi mooz, am trying out xkeysnail and am loving it. Thank you!

I'd like to know if there can be a setup where this could run in the background, as a daemon, rather than on the terminal which needs to be kept open? Is there already an option to do that? If not, perhaps some flag like: sudo xkeysnail -d config.py
to run it as a daemon in the background, with options to move it back to the foreground etc would be great!

Let me know your thoughts.

Thanks again.

Function Key does not map with Alt

Here is an example of my script that is failing to work. The Ctrl+Alt+G to Alt+F3 remap works as long as an F row key is not used. I think this may be a bug of some sort in xkeysnail? There is not conditional set on it at the moment so I could test with xbindkeys -mk.

I've commented out the other parts of my script that don't really matter. (Still does not work when ran.)

# -*- coding: utf-8 -*-

import re
from xkeysnail.transform import *

# [Global modemap] Change modifier keys as in xmodmap
# define_modmap({
#     Key.LEFT_ALT: Key.LEFT_CTRL,
#     Key.LEFT_CTRL: Key.LEFT_ALT,
# })

# [Conditional modmap] Change modifier keys in certain applications
# define_conditional_modmap(re.compile("Gnome-terminal|konsole|io.elementary.terminal|terminator|sakura|guake|tilda|xterm|eterm|kitty"), {
#     Key.LEFT_ALT: Key.LEFT_META,
#     Key.LEFT_META: Key.LEFT_ALT,
# })


# Keybindings for Sublime Text
# re.compile("Sublime_text")
define_keymap(None,{
    # Select All Matches
    K("M-C-g"): K("M-f3"),
}, "Sublime_text")

xkeysnail reset xkbset config

xkbsetというbounceなどを指定できるツールがあります.

Linux(X11)でキーボードのチャタリングをGnomeなどのアクセシビリティツールを使わずに抑止するにはxkbsetを使う - ncaq

この設定をxkeysnailは何故か消去してしまいます.

2019-04-24T12:08:20 ncaq@indigo/pts/0(0) ~/Desktop
% xkbset bouncekeys 50
2019-04-24T12:09:09 ncaq@indigo/pts/0(0) ~/Desktop
% xkbset q|rg Bounce
Bounce-Keys = On
Beep if Slow/Bounce-Keys about to be turned off = On
Beep on Bounce-Key Reject = On
2019-04-24T12:09:16 ncaq@indigo/pts/0(0) ~/Desktop
% systemctl --user restart xkeysnail.service 
2019-04-24T12:09:31 ncaq@indigo/pts/0(0) ~/Desktop
% xkbset q|rg Bounce
Bounce-Keys = Off
Beep if Slow/Bounce-Keys about to be turned off = On
Beep on Bounce-Key Reject = On

ならばxkeysnailをxkbsetの後に起動すれば良いのかと思うのですがそうもいかなくて,
何故かxkbsetを起動していると私の以下の設定が効かなくなります.

define_keymap(re.compile("Slack|discord"), {
    # j
    K("M-c"): [K("M-Down")],
    K("C-M-c"): [K("C-M-Down")],
    # k
    K("M-v"): [K("M-Up")],
    K("C-M-v"): [K("C-M-Up")],
}, "Slack and Discord switch channel")

デバッガ使って原因を探ってみましたが原因不明でした.
ひとまず諦めてissueだけ立てておきます.

Finding ID from device name

I'm trying to remap multiple keyboards' keybinding in a background process. I found that the --device option is suited in such a case. This option expects device file, e.g. /dev/input/event[:id].
However, this "id" file pattern changes every OS booting so that xkeysnail could not remap proper devices.
Is there any way to select "id" automatically from device name?

Start up script

#!/bin/bash
xkeysnail device.py --devices /dev/input/event2 &
pid=$!
trap "kill ${pid}" 2
# This is foreground process for experimental purposes
xkeysnail another_device.py --devices /dev/input/event5

Device Name mapping

❯ xkeysnail conf.py
:
Device               Name                                Phys
------------------------------------------------------------------------------------
/dev/input/event2    AT Translated Set 2 keyboard        isa0060/serio0/input0
/dev/input/event7    SEMITEK USB-HID Gaming Keyboard     usb-0000:00:14.0-3.4/input0
#               ^
#               changed this time

Device removed: SIGMACHIP USB Keyboard

Hey Mr. Mooz, I have a werid issue. As title, sometimes when I just stop typing for a while, xkeysnail will say:

Device removed: SIGMACHIP USB Keyboard

But my USE keyboard hasn't been removed, then xkeysnail just doesn't work. So I just restart xkeysnail, and it works again. I have no idea how to sovle this issue!
Environment information:
OS: Arch Linux
WM: i3wm

Crashes with "no signature found for built-in [...]"

I run

xkeysnail config.py

where config.py is the one from the examples folder. I'm added to the input and uinput group, so that I don't need to run it as root.

It crashes, printing the following stacktrace:

[...] (omitted)

Okay, now enable remapping on the following device(s):

-------------------------------------------------------------------------------------
Device               Name                                Phys
-------------------------------------------------------------------------------------
/dev/input/event3    Logitech USB Keyboard               usb-0000:00:14.0-2/input0
/dev/input/event5    Corsair Corsair K70R Gaming Keyboard usb-0000:00:1a.0-1.5/input0
/dev/input/event8    Corsair Corsair K70R Gaming Keyboard usb-0000:00:1a.0-1.5/input2

Traceback (most recent call last):
  File "/usr/bin/xkeysnail", line 6, in <module>
    cli_main()
  File "/usr/lib/python3.4/site-packages/xkeysnail/__init__.py", line 48, in cli_main
    loop(select_device(args.devices))
  File "/usr/lib/python3.4/site-packages/xkeysnail/input.py", line 89, in loop
    on_event(event, device.name)
  File "/usr/lib/python3.4/site-packages/xkeysnail/transform.py", line 261, in on_event
    if len(signature(condition).parameters) == 2:
  File "/usr/lib64/python3.4/inspect.py", line 2063, in signature
    return _signature_internal(obj)
  File "/usr/lib64/python3.4/inspect.py", line 1965, in _signature_internal
    skip_bound_arg=skip_bound_arg)
  File "/usr/lib64/python3.4/inspect.py", line 1890, in _signature_from_builtin
    raise ValueError("no signature found for builtin {!r}".format(func))
ValueError: no signature found for builtin <built-in method search of _sre.SRE_Pattern object at 0x7fb303281e00>

Please tell me if I need to provide additional debugging information.

Edit:
Seems to be a python 3.4 problem. Works flawlessly with python 3.6

Ways to bound C-g to set_mark(False) only

First of all, let me express my absolute gratitude for this package. I have been looking for something like this for the longest time and your package is absolutely amazing.

I am using the default configuration and I noticed that C-g is bound to esc followed by set_mark(False). This works as intended in most cases. However, in some cases this has undesirable effects. For example, in Firefox when editing the location bar if I select some text using C-SPC then press C-g to unset the mark, esc is pressed which cancels all editing.

I would like to bound C-g to set_mark(False) if a mark is set and esc otherwise. Or, if not possible, maybe bound C-g to set_mark(False) and C-g C-g to esc. Would that be doable?

OSError: could not open uinput device in write mode

> sudo xkeysnail config.py 

Traceback (most recent call last):
  File "/usr/bin/xkeysnail", line 6, in <module>
    cli_main()
  File "/usr/lib/python3.6/site-packages/xkeysnail/__init__.py", line 34, in cli_main
    if not has_access_to_uinput():
  File "/usr/lib/python3.6/site-packages/xkeysnail/__init__.py", line 11, in 
    has_access_to_uinput from xkeysnail.output import _uinput
  File "/usr/lib/python3.6/site-packages/xkeysnail/output.py", line 10, in <module>
    _uinput = UInput()
  File "/usr/lib/python3.6/site-packages/evdev/uinput.py", line 129, in __init__
    self.fd = _uinput.open(devnode)
OSError: could not open uinput device in write mode

Tested on ArchLinux.

Random rant: The sad thing is, I'm trying to replace xkeysnail and Firefox with cVim and Chromium, but Chromium is stubborn in keeping the shortcuts to himself.

Does not correctly identify WM_CLASS for Java applications

I run several different Java based applications, and for all of these, xkeysnail reports the WM_CLASS as 'FocusProxy' rather than the correct application specific WM_CLASS that gets reported by xprop WM_CLASS on that window.

For example, xkeysnail output when over JetBrains IntelliJ IDE:

WM_CLASS 'FocusProxy' | active keymaps = []

and xprop on that window gives:

$ xprop WM_CLASS
WM_CLASS(STRING) = "sun-awt-X11-XFramePeer", "jetbrains-idea-ce"

This makes it impossible to have different bindings for the different applications.

Autokey apparently also has a similar problem with Java apps: autokey/autokey#113

NameError: name 'with_or_set_mark' is not defined

Hi,
executing the command: "sudo xkeysnail config.py" results in (default config.py):
Traceback (most recent call last):
File "/usr/bin/xkeysnail", line 6, in
cli_main()
File "/usr/lib/python3.4/site-packages/xkeysnail/init.py", line 44, in cli_main
eval_file(args.config)
File "/usr/lib/python3.4/site-packages/xkeysnail/init.py", line 5, in eval_file
exec(compile(file.read(), path, 'exec'), globals())
File "config.py", line 84, in
K("C-M-space"): with_or_set_mark(K("C-right")),
NameError: name 'with_or_set_mark' is not defined

Can not set up xkeysnail, looking for help

Hey, Mr. mooz, I install xkeysnail as what README guides, but I still could not set it up. There is an error I try sudo xkeysnail examples/config.py:

Traceback (most recent call last):
  File "/usr/local/bin/xkeysnail", line 6, in <module>
    cli_main()
  File "/usr/local/lib/python3.6/site-packages/xkeysnail/__init__.py", line 44, in cli_main
    eval_file(args.config)
  File "/usr/local/lib/python3.6/site-packages/xkeysnail/__init__.py", line 5, in eval_file
    exec(compile(file.read(), path, 'exec'), globals())
  File "config.py", line 4, in <module>
    from xkeysnail.transform import *
  File "/usr/local/lib/python3.6/site-packages/xkeysnail/transform.py", line 13, in <module>
    def get_active_window_wm_class(display=Xlib.display.Display()):
  File "/usr/local/lib/python3.6/site-packages/Xlib/display.py", line 89, in __init__
    self.display = _BaseDisplay(display)
  File "/usr/local/lib/python3.6/site-packages/Xlib/display.py", line 71, in __init__
    protocol_display.Display.__init__(self, *args, **keys)
  File "/usr/local/lib/python3.6/site-packages/Xlib/protocol/display.py", line 167, in __init__
    raise error.DisplayConnectionError(self.display_name, r.reason)
Xlib.error.DisplayConnectionError: Can't connect to display ":0.0": b'No protocol specified\n'

Information about my OS:

  • fedora 27
  • XFCE environment
  • python2.7 and python3.6 installed

Is there something wrong with my environment ?

Transfer modifier keys per mapping

I try to configure Ctrl+j to be left arrow key (LEFT). To do this I apply this configuration:

from xkeysnail.transform import *

define_keymap(None, {
    K("C-j"): (K("left")),
})

It works fine, but when I want to generate Shift+Left, I press Ctrl+Shift+j, but Shift is not transfering - only LEFT is written.

How to modify <Modifier> to another <Modifier>key?

I use setxkbmap to remap Caps_lock as Ctrl (setxkbmap -option ctrl:nocaps). However, this does not work if I use xkeysnail.
So, how can I do setxkbmap's work in xkeysnail? Or, is there any workaround?

Change bindings at runtime / interop?

Hi,

I help make Tridactyl and I recommend xkeysnail to our users for working around the fact that we can't rebind some keys (e.g, Ctrl-N).

I'd like to be able to use xkeysnail to rebind keys per-mode in Tridactyl, so that I can bind e.g, d to Ctrl-W in Tridactyl's normal mode and then disable that bind in other modes, via e.g.

xkeysnail-remote --mode="normal" <- d bind activated

xkeysnail-remote --mode="insert" <- d bind deactivated

Is this something you'd be interested in adding? (Relevant-ish Tridactyl issue: tridactyl/tridactyl#359)

I'm keen to avoid people having to give sudo permissions to Tridactyl and I want to keep latency to a minimum (typing in a textbox and having the tab close would be lame), so I suspect killall xkeysnail; xkeysnail ignore.py wouldn't be good enough.

Thanks.

Key sequences not defined in wm_class specific keymap are not handled

I use the EXWM window manager, for example, if you have this config:

define_keymap(lambda wm_class: wm_class not in ("Emacs",), {
    # C-x YYY
    K("C-x"): {
        # C-x C-f (open)
        K("C-f"): K("C-o"),
        # C-x C-s (save)
        K("C-s"): K("C-s"),
        # C-x k (kill tab)
        K("k"): K("C-f4"),
        # C-x C-c (exit)
        K("C-c"): K("C-q"),
    }
}, "Emacs-like keys")

Press C-x b, the combination is not sent to the window manager, and we have to set it explicitly

define_keymap(lambda wm_class: wm_class not in ("Emacs",), {
    # C-x YYY
    K("C-x"): {
        K("b"): [K("C-x"), K("b")],
    }
}, "Emacs-like keys")

Why not make all not specified key bindings just send themselves default?

Mapping SUPER/HYPER

I need to remap my second spacebar, which is recognized as MENU key to HYPER (or SUPER). In xmodmap I use keycode 135 = Hyper_R. Since xmodmap doesn't work with xkeysnail, is it possible to do this in define_modmap?

feat: Cheatsheet feature?

I was thinking it could be very useful to have a cheatsheet like view while in an app.

Something along the lines of this app but instead of showing the original keymaps of the app have it parse through the xkeysnail python file for the current shortcut keys and maybe use any "# comment" either directly above or to the right of the key remap entry as the description.

https://mediaatelier.com/CheatSheet/

Would be interesting to combine it with the actual keymaps of the app as well, and simply overwrite the original if one is remapped.

Is not my function supported ?

Hi, thank you for this great product!

I have a problem, I made below config.py as xkeysnail.py

import re
from xkeysnail.transform import *

def char_to_string_of_a_stroke(x):
    return 'Shift-' + x.lower() if x.isupper() else x

def strokes(key_chars):
    xs = list(key_chars)
    xs = map(char_to_string_of_a_stroke, xs)
    xs = map(K, xs)
    return list(xs)

define_keymap(re.compile('Vivaldi-stable'), {
    K('C-n'): strokes('gt'),
    K('C-p'): strokes('gT'),
}, 'vivaldi')

and got

$ >>> sudo xkeysnail xkeysnail.py --devices /dev/input/event14

██╗  ██╗██╗  ██╗███████╗██╗   ██╗
╚██╗██╔╝██║ ██╔╝██╔════╝╚██╗ ██╔╝
 ╚███╔╝ █████╔╝ █████╗   ╚████╔╝
 ██╔██╗ ██╔═██╗ ██╔══╝    ╚██╔╝
██╔╝ ██╗██║  ██╗███████╗   ██║
╚═╝  ╚═╝╚═╝  ╚═╝╚══════╝   ╚═╝
  ███████╗███╗   ██╗ █████╗ ██╗██╗
  ██╔════╝████╗  ██║██╔══██╗██║██║
  ███████╗██╔██╗ ██║███████║██║██║
  ╚════██║██║╚██╗██║██╔══██║██║██║
  ███████║██║ ╚████║██║  ██║██║███████╗
  ╚══════╝╚═╝  ╚═══╝╚═╝  ╚═╝╚═╝╚══════╝
                             v0.0.9

Traceback (most recent call last):
  File "/bin/xkeysnail", line 6, in <module>
    cli_main()
  File "/usr/lib/python3.6/site-packages/xkeysnail/__init__.py", line 44, in cli_main
    eval_file(args.config)
  File "/usr/lib/python3.6/site-packages/xkeysnail/__init__.py", line 5, in eval_file
    exec(compile(file.read(), path, 'exec'))
  File "xkeysnail.py", line 16, in <module>
    K('C-n'): strokes('gt'),
  File "xkeysnail.py", line 11, in strokes
    xs = map(char_to_string_of_a_stroke, xs)
NameError: name 'char_to_string_of_a_stroke' is not defined

Is my strokes function supported ?

xkeysnail doesn't re-add a device that is unplugged and then re-plugged in

When I unplug (or reset) my keyboard, xkeysnail first notices that the keyboard is gone and outputs:

Device removed: <my keyboard  name>

But when the keyboard is re-attached, it doesn't add the keyboard back, and so I need to restart xkeysnail. Please allow xkeysnail to automatically add keyboards when they are plugged in.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.