I use an external keyboard with my laptop and a script to change the keyboard layout depending on whether the keyboard is plugged in. This works fine, but I find it shouldn't be necessary to run the script manually: it should run by itself whenever the external keyboard is plugged in or out. I've tried to set this up with a udev rule, but for some reason it doesn't work. My udev rule is:

KERNEL=="3-1.1", SUBSYSTEM=="usb", RUN+="/root/bin/udev.sh"

I have confirmed that this script runs once whenever I plug the keyboard in or out. The contents of the script are:

su - toothrot -c "/usr/local/bin/loadkb.sh" &

And the contents of /usr/local/bin/loadkb.sh are:


export DISPLAY=:0 
export XAUTHORITY=/home/toothrot/.Xauthority
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus

killall xcape

if lsusb | grep HHKB
then {
    setxkbmap us,gr \
        -variant carpalx-plus-intl,daedalus \
        -option grp:win_space_toggle \
        -option lv3:rwin_switch \
        -option altwin:meta_alt \
        -option shift:breaks_caps \
        -option lv5:lsgt_switch_lock
    xmodmap ~/.xmodmap/hh 
    xcape -e 'Shift_L=F13;Shift_R=F14;Super_L=Prior;Hyper_L=Prior'
else {
    setxkbmap us,gr \
        -variant carpalx-plus-intl,daedalus \
        -option grp:win_space_toggle \
        -option lv3:ralt_switch \
        -option altwin:meta_alt \
        -option shift:breaks_caps -option lv5:lsgt_switch_lock
    xmodmap ~/.xmodmap/apple
    xcape -e 'Control_L=Escape;Shift_L=F14;Shift_R=F13;Alt_L=Escape;Alt_R=Escape'

I have confirmed that running /root/bin/udev.sh as root works for my toothrot X session. Only when run by a udev rule, the script doesn't work. What am I missing?

Or is there a way to achieve the result I want without using a udev rule, maybe from within X?

  • As an experiment, you might place echo HHKB >/tmp/kbdlog at the top of the then block, and echo other >/tmp/kbdlog at the top of the else, to ensure the correct path is taken
    – Fox
    Mar 28, 2017 at 17:37
  • @Fox, I've confirmed that this writes the expected content to /tmp/kbdlog.
    – Toothrot
    Mar 28, 2017 at 20:56
  • Could you send the stdout and stderr of each command to a similar log? That is, command >/tmp/kbdlog 2>&1 for each command? See if any errors pop up there?
    – Fox
    Mar 28, 2017 at 21:00
  • 1
    Udev-rules are run from the kernel and apparently execute in some kind of restricted environment. There are multiple questions on this site where doing something fancy or X related inside an udev-rule didn't work. I don't know how to solve this, if it's solvable at all. However, if the differences are simple enough, alternatively you can try to change the scancode to keycode mapping on the input-layer with the hwdb integrated into udev.
    – dirkt
    Mar 29, 2017 at 11:10
  • askubuntu.com/questions/337411/… may help: someone has managed to do something similar. Jun 7, 2019 at 20:57

1 Answer 1


Inspired by this blog article I got xmodmap to work with the following rule in /etc/udev/rules.d/70-usb-keyboards.rules:

ACTION=="add", ATTRS{idVendor}=="0000", ATTRS{idProduct}=="0000", RUN+="/bin/sh -c 'DISPLAY=:0.0 XAUTHORITY=/home/toothrot/.Xauthority sh -c \"setxkbmap us,gr;xmodmap /home/toothrot/.xmodmap/hh\" &'"

Replace vendor and product id with the values of your device from lsusb and adjust the script to your liking, then don’t forget to reload the udev rules with:

udevadm control --reload

I’d always leave the detection to udev, so this is the rule for when the keyboard is added. To act on removal add another rule line with:

  • Link to facility9.com/2019/07/remapping-keys-in-linux: 404: Not Found :(.
    – pevik
    Oct 25, 2022 at 13:02
  • 1
    @pevik fixed it
    – dessert
    Oct 26, 2022 at 8:28
  • I think it is worth calling out in this answer that the DISPLAY and XAUTHORITY environment variables are important. The linked article does a good job.
    – guysherman
    May 11 at 0:59

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .