Linux
archLinux

xkeysnailでキーリマップする

xkeysnailでキーコードの交換やワンショットモディファイヤができるとわかったので、試してみました。

Antergos、Xorg、GDMの環境にインストールします。

$ lsb_release -a
LSB Version:    1.4
Distributor ID: Arch
Description:    Arch Linux
Release:        rolling
Codename:       ISO-Rolling
$ uname -r
4.14.15-1-ARCH
$ python --version
Python 3.6.4

導入

venvで/opt/xkeysnail配下にインストールします。

$ sudo python -m venv /opt/xkeysnail
[sudo] miy4 のパスワード:
$ . /opt/xkeysnail/bin/activate
(xkeysnail) $ sudo pip install xkeysnail
Collecting xkeysnail
  Downloading xkeysnail-0.1.0.tar.gz
Collecting evdev (from xkeysnail)
  Downloading evdev-0.7.0.tar.gz
Collecting python-xlib (from xkeysnail)
  Downloading python_xlib-0.21-py2.py3-none-any.whl (123kB)
    100% |████████████████████████████████| 133kB 345kB/s
Collecting six>=1.10.0 (from python-xlib->xkeysnail)
  Downloading six-1.11.0-py2.py3-none-any.whl
Installing collected packages: evdev, six, python-xlib, xkeysnail
  Running setup.py install for evdev ... done
  Running setup.py install for xkeysnail ... done
Successfully installed evdev-0.7.0 python-xlib-0.21 six-1.11.0 xkeysnail-0.1.0
(xkeysnail) $ deactivate

キーリマップの設定

以下の方針で設定します。

  • CapsLockキー
    • 単体で押すとESC、他のキーと一緒に押すと(つまり修飾キーとして使うと)Ctrlとして使う
    • 単純にCapsLockCtrlを交換するなら、define_modmapが使える
  • 変換キー
    • 単体で押すと変換、修飾キーとして使うとCtrl
  • 無変換キー
    • 単体で押すと無変換、修飾キーとして使うとAlt

右手、左手、それぞれでCtrlAltを押しやすくなるので、Emacsを使う環境でよく設定しています。

/etc/opt/xkeysnail/config.py
# -*- coding: utf-8 -*-

import re
from xkeysnail.transform import *

#define_modmap({
#    Key.CAPSLOCK: Key.LEFT_CTRL
#})

define_multipurpose_modmap({
    Key.CAPSLOCK: [Key.ESC, Key.LEFT_CTRL],
    Key.MUHENKAN: [Key.MUHENKAN, Key.LEFT_ALT],
    Key.HENKAN: [Key.HENKAN, Key.RIGHT_CTRL]
})

これだけ!かんたん!

起動:失敗

$ sudo /opt/xkeysnail/bin/xkeysnail /etc/opt/xkeysnail/config.py                    

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

Traceback (most recent call last):                                                                 
  File "/opt/xkeysnail/bin/xkeysnail", line 6, in <module>                                         
    cli_main()                                                                                     
  File "/opt/xkeysnail/lib/python3.6/site-packages/xkeysnail/__init__.py", line 44, in cli_main    
    eval_file(args.config)                                                                         
  File "/opt/xkeysnail/lib/python3.6/site-packages/xkeysnail/__init__.py", line 5, in eval_file    
    exec(compile(file.read(), path, 'exec'), globals())                                            
  File "/etc/opt/xkeysnail/config.py", line 4, in <module>                                         
    from xkeysnail.transform import *                                                              
  File "/opt/xkeysnail/lib/python3.6/site-packages/xkeysnail/transform.py", line 13, in <module>   
    def get_active_window_wm_class(display=Xlib.display.Display()):                                
  File "/opt/xkeysnail/lib/python3.6/site-packages/Xlib/display.py", line 89, in __init__          
    self.display = _BaseDisplay(display)                                                           
  File "/opt/xkeysnail/lib/python3.6/site-packages/Xlib/display.py", line 71, in __init__          
    protocol_display.Display.__init__(self, *args, **keys)                                         
  File "/opt/xkeysnail/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 ":1": b'No protocol specified\n'       

Can't connect to display ":1"と言っており、Xサーバに繋がらなかったようです。
xhostで確認すると、rootの接続は許可されていないのがわかります。

$ xhost
access control enabled, only authorized clients can connect
SI:localuser:miy4

起動:成功

試しにxhost +SI:localuser:rootでアクセスコントロールリストにrootを追加して、xkeysnailが起動することを確認しました。

$ xhost +SI:localuser:root
localuser:root being added to access control list
$ xhost
access control enabled, only authorized clients can connect
SI:localuser:root
SI:localuser:miy4
$ sudo /opt/xkeysnail/bin/xkeysnail /etc/opt/xkeysnail/config.py

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

No keyboard devices specified via (--devices) option.
xkeysnail picks up keyboard-ish devices from the list below:

-----------------------------------------------------------------------------------
Device               Name                                Phys
-----------------------------------------------------------------------------------
/dev/input/event0    AT Translated Set 2 keyboard        isa0060/serio0/input0
/dev/input/event1    Lid Switch                          PNP0C0D/button/input0
/dev/input/event2    Sleep Button                        PNP0C0E/button/input0
/dev/input/event3    Power Button                        LNXPWRBN/button/input0
/dev/input/event4    PC Speaker                          isa0061/input0
/dev/input/event5    ThinkPad Extra Buttons              thinkpad_acpi/input0
/dev/input/event6    Integrated Camera: Integrated C     usb-0000:00:14.0-8/button
/dev/input/event7    SynPS/2 Synaptics TouchPad          isa0060/serio1/input0
/dev/input/event8    Video Bus                           LNXVIDEO/video/input0
/dev/input/event9    HDA Digital PCBeep                  card0/codec#0/beep0
/dev/input/event10   HDA Intel PCH Mic                   ALSA
/dev/input/event11   HDA Intel PCH Headphone             ALSA
/dev/input/event12   HDA Intel PCH HDMI/DP,pcm=3         ALSA
/dev/input/event13   HDA Intel PCH HDMI/DP,pcm=7         ALSA
/dev/input/event14   HDA Intel PCH HDMI/DP,pcm=8         ALSA
/dev/input/event15   HDA Intel PCH HDMI/DP,pcm=9         ALSA
/dev/input/event16   HDA Intel PCH HDMI/DP,pcm=10        ALSA
/dev/input/event17   TPPS/2 IBM TrackPoint               synaptics-pt/serio0/input0
/dev/input/event18   py-evdev-uinput                     py-evdev-uinput

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

------------------------------------------------------------------------------
Device               Name                                Phys
------------------------------------------------------------------------------
/dev/input/event0    AT Translated Set 2 keyboard        isa0060/serio0/input0

自動起動

ここまでをふまえて、ログイン時にxkeysnailを自動的に起動するようにしたいです。
Xサーバへの接続やuinputの読み書きのアクセスコントロールが必要なので、rootでやるのは怖さを感じる。xkeysnail用のユーザを作ることにします。

起動用のユーザを作成

$ sudo groupadd uinput
$ sudo useradd -G input,uinput -s /sbin/nologin xkeysnail

inputグループは/dev/input/*の所有者グループです。
xkeysnailユーザには/dev/input/*/dev/uinputへのアクセス権限を与えます。

/etc/udev/rules.d/40-udev-xkeysnail.rules
KERNEL=="uinput", GROUP="uinput"
/etc/modules-load.d/uinput.conf
uinput

xkeysnailユーザにsudoし、xkeysnailを実行する時は、パスワードは省略します。

/etc/sudoers.d/10-installer
miy4 ALL=(ALL) ALL,\
         (xkeysnail) NOPASSWD: /opt/xkeysnail/bin/xkeysnail

10-installerはAntergos (Arch Linux)導入時に用意されたsudoersファイルです。

設定

GDMはXセッション開始時に~/.xprofileを実行するので、ここでxkeysnailを起動します。

~/.xprofile
if [ -x /opt/xkeysnail/bin/xkeysnail ]; then
  xhost +SI:localuser:xkeysnail
  sudo -u xkeysnail DISPLAY=:1 /opt/xkeysnail/bin/xkeysnail /etc/opt/xkeysnail/config.py &
fi

Linuxを再起動し、GDMからログインし、キーリマップが働いていることを確認します。

課題

Thinkpad keyboardのトラックポイントが効かない

上記のBluetoothキーボードを使っているのだけど、xkeysnailで同キーボードの/dev/input/event*を指定すると、トラックポイントが使えなくなる。キーボードのリマップはできている。

外付けではない、Thinkpad内臓のキーボードではトラックポイントも問題なく使えているので、同キーボード固有の問題なのだと思う。要調査。