37

I've recently bought a Unicomp keyboard that comes with swapped right-alt and Windows keys. The keyboard identifies like this on lsusb:

Bus 003 Device 002: ID 17f6:0822 Unicomp, Inc 

Is there a way to have the kernel (i.e. not xmodmap-based) swap the right-alt and windows keys so every application sees them in the swapped places even if they get raw keyboard input (swapping stuff with xmodmap won't do that)? Is there a way to have that only for this one keyboard?

FUZxxl
  • 526
  • 1
  • 5
  • 13
  • I suspect you could do this with udev by matching the serial number of the keyboard and then calling a script which does the remapping. You'd probably need a similar script to be called on detach for the device to put it back. – jam May 30 '14 at 11:51
  • 2
    @jam Would this remap all attached keyboards? I can't imagine that Linux is so inflexible that it can only manage one mapping table for all attached (USB) keyboards. – FUZxxl May 30 '14 at 11:53
  • @jam Additionally, you would really help me if you could describe how to do the actual swapping. I did not manage to find anything useful on this, only xmodmap stuff (which I don't want to use). – FUZxxl May 30 '14 at 11:55
  • If you don't want to use xmodmap, what you're asking seems to be too specific for my knowledge to help you sorry. The method I proposed would use xmodmap to swap the keycodes for those keys for all devices, for the duration your specific keyboard is attached, and then put it back. Are you expecting to be using multiple keyboards simultaneously? – jam Jun 02 '14 at 07:31
  • 1
    @jam If I used Xmodmap, X programs would still see the wrong keycodes as the X server also sends untranslated key codes to the client. This matters for instance for video games. There is supposed to be a solution in the kernel that doesn't make my life more complicated with applications that read out scan codes. – FUZxxl Jun 02 '14 at 07:42
  • I want to do the same for my Naga mouse with 12 keys which work like numeric keys, so I guess Linux treats my mouse as a keyboard with a mouse. The plan is to reassign the numeric keys to useful shortcuts for FreeCAD. – Daniil Iaitskov Apr 02 '17 at 09:52

3 Answers3

37

Yes, it's possible using XKB. Unlike xmodmap, XKB can remap your keys for individual devices.

Note: Make sure you have xkbcomp > 1.2.0

First list your devices with:

xinput list

You'll get something like this:

⎡ Virtual core pointer                      id=2    [master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer                id=4    [slave  pointer  (2)]
⎜   ↳ Wacom Bamboo Pen Pen stylus               id=11   [slave  pointer  (2)]
⎜   ↳ Wacom Bamboo Pen Finger touch             id=12   [slave  pointer  (2)]
⎜   ↳ Logitech USB-PS/2 Optical Mouse           id=13   [slave  pointer  (2)]
⎜   ↳ Wacom Bamboo Pen Pen eraser               id=14   [slave  pointer  (2)]
⎜   ↳ Wacom Bamboo Pen Finger pad               id=15   [slave  pointer  (2)]
⎜   ↳ GASIA USB KB V11                          id=17   [slave  pointer  (2)]
⎣ Virtual core keyboard                     id=3    [master keyboard (2)]
    ↳ Virtual core XTEST keyboard               id=5    [slave  keyboard (3)]
    ↳ Power Button                              id=6    [slave  keyboard (3)]
    ↳ Power Button                              id=7    [slave  keyboard (3)]
    ↳ G19 Gaming Keyboard                       id=8    [slave  keyboard (3)]
    ↳ G19 Gaming Keyboard                       id=9    [slave  keyboard (3)]
    ↳ Logitech G19 Gaming Keyboard              id=10   [slave  keyboard (3)]
    ↳ GASIA USB KB V11                          id=16   [slave  keyboard (3)]

Identify the string of your device and edit the following shell script, changing the sed line with one that fits your device's name. Then change the keys you need remapped.

Example: Load xev and press a key you want to remap. Suppose you find out it's keycode 84. Lookup 84 in https://gist.github.com/zoqaeski/3880640. The key name there is <KP5>. Then lookup the key you want it replaced by (in the same link, farther below) and copy what's inside the brackets. Repeat the process for all the keys you want.

remote_id=$(
    xinput list |
    sed -n 's/.*GASIA.*id=\([0-9]*\).*keyboard.*/\1/p'
)
[ "$remote_id" ] || exit

# remap the following keys, only for my custom vintage atari joystick connected
# through an old USB keyboard:
#
# keypad 5 -> keypad 6
# . -> keypad 2
# [ -> keypad 8
# left shift -> left control

mkdir -p /tmp/xkb/symbols
# This is a name for the file, it could be anything you
# want. For us, we'll name it "custom". This is important
# later.
#
# The KP_* come from /usr/include/X11/keysymdef.h
# Also note the name, "remote" is there in the stanza
# definition.
cat >/tmp/xkb/symbols/custom <<\EOF

xkb_symbols "remote" {
    key <KP5>  { [ KP_Right, KP_6, U2192, U21D2 ]       };
    key <I129> { [ KP_Down, KP_2, U2193, U21D3 ]       };
    key <AD12> { [ KP_Up, KP_8, U2191, U21D1 ]  };
    key <LFSH> { [ Control_L ]        };
};
EOF

# (1) We list our current definition
# (2) Modify it to have a keyboard mapping using the name
#     we used above, in this case it's the "remote" definition
#     described in the file named "custom" which we specify in
#     this world as "custom(remote)".
# (3) Now we take that as input back into our definition of the
#     keyboard. This includes the file we just made, read in last,
#     so as to override any prior definitions.  Importantly we 
#     need to include the directory of the place we placed the file
#     to be considered when reading things in.
#
# Also notice that we aren't including exactly the 
# directory we specified above. In this case, it will be looking
# for a directory structure similar to /usr/share/X11/xkb
# 
# What we provided was a "symbols" file. That's why above we put
# the file into a "symbols" directory, which is not being included
# below.
setxkbmap -device $remote_id -print \
 | sed 's/\(xkb_symbols.*\)"/\1+custom(remote)"/' \
 | xkbcomp -I/tmp/xkb -i $remote_id -synch - $DISPLAY 2>/dev/null

Then source it (you can add it to your .xinitrc). All done! Now pressing the keys should generate the desired output, only for the device you specified.

Watcom
  • 498
  • 6
  • 11
  • Let me try this on monday when I get back to the computer where I use this keyboard. – FUZxxl Jan 25 '15 at 01:42
  • I just tried that out and it didn't have any effect at all. Can I send you the files I created so you can have a look at them? – FUZxxl Jun 12 '15 at 10:54
  • Sure. But there's one thing you may try that is to replace the `remote_id=...` line with something less fancy, such as the actual number of the device displayed in xinput list (in my case it would be `remote_id=16`, adjust it to your needs). Unfortunately this number can change after rebooting so you will eventually have to do it properly, like in my example. – Watcom Jun 12 '15 at 16:37
  • Actually, it started working some time after I posted the comment. I still don't know why. I'll let you know when it stops working once again. – FUZxxl Jun 12 '15 at 22:18
  • Ah, I really need this, but I don not fully understand the script. **a)** *sed line* How would I select the 'G19 Gaming Keyboard' in this example? `sed -n 's/.*GASIA.*id=\([0-9]*\).*G19 Gaming Keyboard.*/\1/p'` **b)** I think i was alright with finding the keycodes for the remapping with the link provieded. **c)** can I manually test the last command by replacing `$remote_id` with, say `10` to test it (it's not working for me though) Help is appreciated, thank you! – stats-hb Jun 19 '16 at 20:26
  • 1
    *a)* `sed -n 's/.*G19 Gaming Keyboard.*id=\([0-9]*\).*keyboard.*/\1/p'` *c)* Yes, you should definitely test it first replacing `$remote_id` with the id number. Note there are two references to `$remote_id` there, did you change both? – Watcom Jun 20 '16 at 02:29
  • 1
    Oh, and when testing the hard-coded `$remote_id`, make sure you comment-out the line `[ "$remote_id" ] || exit` if you haven't already otherwise it will just bail out. – Watcom Jun 20 '16 at 02:37
  • ah took me a long time to get back to this. Well, I run the script and I hardcode the `$remote_id` to `$9` (and replace it two times). I run `mkdir -p /tmp/xkb/symbols` `cat >/tmp/xkb/symbols/custom <<\EOF` `xkb_symbols "remote" {` `key { [ Control_L ] };` `key { [ Alt_L, Meta_L ] }; ` `};` `EOF` with `setxkbmap -device $9 -print | sed 's/\(xkb_symbols.*\)"/\1+custom(remote)"/' | xkbcomp -I/tmp/xkb -i $9 -synch - $DISPLAY 2>/dev/null` – stats-hb Aug 15 '16 at 20:27
  • @Watcom but this is the error I run into atm. Did I mess up the key definition? `X Error of failed request: 129` `Major opcode of failed request: 135 (XKEYBOARD)` `Minor opcode of failed request: 23 (XkbGetKbdByName)` `Value in failed request: 0xff000000` `Serial number of failed request: 9` `Current serial number in output stream: 9` – stats-hb Aug 15 '16 at 20:29
  • 1
    @stats-hb `$9` will not work, you meant `9`? – Watcom Aug 16 '16 at 22:15
  • ah, I thought I tried this also. Well the error is gone, the command goes through, but the remapping didn't seem to work. I try to get `a` mapped to `ctrl` and `c` mapped to `alt`. This is what I use: `key { [ Control_L ] };` `key { [ Alt_L, Meta_L ] };` Any ideas left? – stats-hb Aug 16 '16 at 22:29
  • hmm.. See if `setxkbmap -device 9 print` actually changes after you call xkbcomp. Also, make sure the device id is actually of a *keyboard* in `xinput list`. Mine had 2 entries there and one of them was a pointer (don't know why). – Watcom Aug 16 '16 at 23:33
  • I couldn't get the last line to work, but I was able to achieve what I wanted (swapping caps/escape for only my laptop keyboard, not my programmable, mechanical keyboard) with this: `remote_id=$(` `xinput list |` `sed -n 's/.*AT Translated Set 2.*id=\([0-9]*\).*keyboard.*/\1/p'` `)` `[ "$remote_id" ] || exit` `setxkbmap -device $remote_id -option "caps:swapescape"` – angrydust Sep 11 '16 at 16:06
  • 1
    this works for me https://lampjs.wordpress.com/2015/06/26/remapchange-your-secondaryusb-keyboard-keys/ – usil Jun 19 '17 at 18:56
  • @usil That works for me as well, using Debian 9.11, unlike this answer or anything else I was able to find. Can you post it as an answer, please? – Fabian Röling Oct 24 '19 at 20:42
  • @FabianRöling I did. – usil Oct 30 '19 at 23:39
  • @usil No, the only three mentions of your name on this page are the last three comments (now four), no answer. – Fabian Röling Oct 31 '19 at 09:05
  • @FabianRöling my answer was deleted. bc I added a link and only wrote that this works for me. – usil Nov 01 '19 at 23:45
  • This answer worked perfectly well for me, thanks much. – Nikana Reklawyks Mar 22 '20 at 08:33
20

For anyone else who's coming here off Google and wants an answer more in line with what the questioner was originally hoping for, I am aware of two ways to remap events at the evdev level so that the change applies to all applications:

  1. udev provides an API for modifying the hardware database entries which control the mappings between scancodes and keycodes. This ArchiWiki page, which contains instructions, explicitly says that it'll work for both X11 and console input.

    The gist is that you create a custom entry in /etc/udev/hwdb.d/ which consists of a device match pattern and some scancode-to-keycode remapping definitions, then run systemd-hwdb update to rebuild the database and udevadm trigger apply it without a reboot.

  2. Given that Wayland doesn't use X11's keyboard subsystem and major Wayland compositors like GNOME Shell and Weston don't implement UIs to configure the relevant aspects of libinput, someone wrote a daemon named evdevremapkeys which resolves the problem similarly to the G15Daemon userspace driver for Logitech G15 gaming keyboards.

    (It swallows events it intends to remap, so nothing else listening on the device can see them, then emits the corrected events via the uinput API for creating kernel-level input devices from userspace.)

ssokolow
  • 951
  • 10
  • 10
  • I'm coming here off DuckDuckGo, but thanks for the answer anyway (: – sm4rk0 Oct 09 '19 at 14:00
  • 3
    This answer is much better than the "chosen one". No custom dirty/hacky scripts. It led me directly to arch linux's wiki. Following the instructions, I created a hwdb file with a clean list of remappings. Worked like a charm on two different machines. Thanks!! – vvaltchev Nov 05 '20 at 23:33
  • Thanks for this comment. [Here](https://askubuntu.com/a/1390841/970934) I explain step-by-step how to replace the super and alt key on an apple keyboard, using your method. – nammerkage Feb 03 '22 at 08:33
1

For those who didn't succeed with @Watcom option, just put your new mapping file, smth like:

xkb_symbols "remote" {
    key <KP5>  { [ KP_Right, KP_6, U2192, U21D2 ]       };
    key <I129> { [ KP_Down, KP_2, U2193, U21D3 ]       };
    key <AD12> { [ KP_Up, KP_8, U2191, U21D1 ]  };
    key <LFSH> { [ Control_L ]        };
};

to /usr/share/X11/xkb/symbols/ as maybe root (ubuntu, may differ for your distrib), call the file 'custom'. Ask for your current layout string with setxkbmap -device <device id> -print | grep xkb_symbols and add +custom to it. Set new layout with remapped keys with and modified layout string:

setxkbmap -device <device id> -layout "us+ru:2+us:3+inet(evdev)+capslock(grouplock)+custom"

Effect is not permanent and unfortunately resets when other keyboard is connected, didn't figure how to fix it yet. You can add the command above to your .bashrc though, so keys are swapped on restart if necessary.