udev rule to auto load keyboard layout when usb keyboard plugged in

24

15

im trying to load a new keyboard layout when I plug in a usb keyboard but my udev rule isnt working.

SUBSYSTEM=="input", ATTR{idVendor}=="062a", ATTR{idProduct}=="0201", GOTO="usb_xmodmap_auto"

LABEL="usb_xmodmap_auto"
ACTION=="add", RUN+="/usr/bin/xmodmap ~/.usbXmodmap"
ACTION=="remove", RUN+="/usr/bin/xmodmap ~/.pndXmodmap"

I have reloaded the rules using:

>sudo udevadm control --reload-rules

and by restarting the system but when I plug in the usb keyboard the orginal xmodmap is still loaded and thus the keyboard layout is wrong, but if i run the command in the terminal

>/usr/bin/xmodmap ~/.usbXmodmap
or
>/usr/bin/xmodmap ~/.pndXmodmap

they work just fine.

hope soneone can help.

Edit:

just to help more I ran some udevadm tests:

>udevadm test --action=add /devices/platform/ehci-omap.0/usb1/1-2/1-2.3/1-2.3:1.1/input/input10

outputs:

run_command: calling: test
udevadm_test: version 151
This program is for debugging only, it does not run any program,
specified by a RUN key. It may show incorrect results, because
some values may be different, or not available at a simulation run.

[...]
parse_file: reading '/etc/udev/rules.d/usb-keyboard.rules' as rules file
udev_rules_new: rules use 100572 bytes tokens (8381 * 12 bytes), 21523 bytes buffer
udev_rules_new: temporary index used 35380 bytes (1769 * 20 bytes)
udev_device_new_from_syspath: device 0x3b4d8 has devpath '/devices/platform/ehci-omap.0/usb1/1-2/1-2.3/1-2.3:1.1/input/input10'
udev_rules_apply_to_event: RUN '/sbin/modprobe -b $env{MODALIAS}' /etc/udev/rules.d/80-drivers.rules:5
udev_rules_apply_to_event: RUN 'socket:@/org/freedesktop/hal/udev_event' /etc/udev/rules.d/90-hal.rules:2
udev_rules_apply_to_event: RUN '/sbin/modprobe $env{MODALIAS}' /etc/udev/rules.d/local.rules:31
udev_rules_apply_to_event: RUN 'socket:/org/kernel/udev/monitor' /etc/udev/rules.d/run.rules:2
udev_rules_apply_to_event: RUN '/usr/bin/xmodmap ~/.usbXmodmap' /etc/udev/rules.d/usb-keyboard.rules:4
udevadm_test: UDEV_LOG=6
udevadm_test: DEVPATH=/devices/platform/ehci-omap.0/usb1/1-2/1-2.3/1-2.3:1.1/input/input10
udevadm_test: PRODUCT=3/62a/201/110
udevadm_test: NAME="USB-compliant keyboard"
udevadm_test: PHYS="usb-ehci-omap.0-2.3/input1"
udevadm_test: UNIQ=""
udevadm_test: EV==1f
udevadm_test: KEY==837fff 2c3027 bf004444 0 0 1fe3 c04 a27c000 267bfa d941dfed 9e0000 0 0 0
udevadm_test: REL==143
udevadm_test: ABS==1 0
udevadm_test: MSC==10
udevadm_test: MODALIAS=input:b0003v062Ap0201e0110-e0,1,2,3,4,k71,72,73,74,77,80,82,83,85,86,87,88,89,8A,8B,8C,8E,8F,90,96,98,9B,9C,9E,9F,A1,A3,A4,A5,A6,A7,A8,A9,AB,AC,AD,AE,B1,B2,B5,CE,CF,D0,D1,D2,D5,D9,DB,E2,EA,EB,100,101,105,106,107,108,109,10A,10B,10C,162,166,16A,16E,178,179,17A,17B,17C,17D,17F,180,181,182,185,18C,18D,192,193,195,1A0,1A1,1A2,1A3,1A4,1A5,1A6,1A7,1A8,1A9,1AA,1AB,1AC,1AD,1AE,1B0,1B1,1B7,r0,1,6,8,a20,m4,lsfw
udevadm_test: ACTION=add
udevadm_test: SUBSYSTEM=input
udevadm_test: run: '/sbin/modprobe -b input:b0003v062Ap0201e0110-e0,1,2,3,4,k71,72,73,74,77,80,82,83,85,86,87,88,89,8A,8B,8C,8E,8F,90,96,98,9B,9C,9E,9F,A1,A3,A4,A5,A6,A7,A8,A9,AB,AC,AD,AE,B1,B2,B5,CE,CF,D0,D1,D2,D5,D9,DB,E2,EA,EB,100,101,105,106,107,108,109,10A,10B,10C,162,166,16A,16E,178,179,17A,17B,17C,17D,17F,180,181,182,185,18C,18D,192,193,195,1A0,1A1,1A2,1A3,1A4,1A5,1A6,1A7,1A8,1A9,1AA,1AB,1AC,1AD,1AE,1B0,1B1,1B7,r0,1,6,8,a20,m4,lsfw'
udevadm_test: run: 'socket:@/org/freedesktop/hal/udev_event'
udevadm_test: run: '/sbin/modprobe input:b0003v062Ap0201e0110-e0,1,2,3,4,k71,72,73,74,77,80,82,83,85,86,87,88,89,8A,8B,8C,8E,8F,90,96,98,9B,9C,9E,9F,A1,A3,A4,A5,A6,A7,A8,A9,AB,AC,AD,AE,B1,B2,B5,CE,CF,D0,D1,D2,D5,D9,DB,E2,EA,EB,100,101,105,106,107,108,109,10A,10B,10C,162,166,16A,16E,178,179,17A,17B,17C,17D,17F,180,181,182,185,18C,18D,192,193,195,1A0,1A1,1A2,1A3,1A4,1A5,1A6,1A7,1A8,1A9,1AA,1AB,1AC,1AD,1AE,1B0,1B1,1B7,r0,1,6,8,a20,m4,lsfw'
udevadm_test: run: 'socket:/org/kernel/udev/monitor'
udevadm_test: run: '/usr/bin/xmodmap ~/.usbXmodmap'

and

>udevadm test --action=remove /devices/platform/ehci-omap.0/usb1/1-2/1-2.3/1-2.3:1.1/input/input10

outputs:

run_command: calling: test
udevadm_test: version 151
This program is for debugging only, it does not run any program,
specified by a RUN key. It may show incorrect results, because
some values may be different, or not available at a simulation run.

[...]
parse_file: reading '/etc/udev/rules.d/usb-keyboard.rules' as rules file
udev_rules_new: rules use 100572 bytes tokens (8381 * 12 bytes), 21523 bytes buffer
udev_rules_new: temporary index used 35380 bytes (1769 * 20 bytes)
udev_device_new_from_syspath: device 0x3b4d8 has devpath '/devices/platform/ehci-omap.0/usb1/1-2/1-2.3/1-2.3:1.1/input/input10'
udev_rules_apply_to_event: RUN 'socket:@/org/freedesktop/hal/udev_event' /etc/udev/rules.d/90-hal.rules:2
udev_rules_apply_to_event: RUN 'socket:/org/kernel/udev/monitor' /etc/udev/rules.d/run.rules:2
udev_rules_apply_to_event: RUN '/usr/bin/xmodmap ~/.pndXmodmap' /etc/udev/rules.d/usb-keyboard.rules:5
udevadm_test: UDEV_LOG=6
udevadm_test: DEVPATH=/devices/platform/ehci-omap.0/usb1/1-2/1-2.3/1-2.3:1.1/input/input10
udevadm_test: PRODUCT=3/62a/201/110
udevadm_test: NAME="USB-compliant keyboard"
udevadm_test: PHYS="usb-ehci-omap.0-2.3/input1"
udevadm_test: UNIQ=""
udevadm_test: EV==1f
udevadm_test: KEY==837fff 2c3027 bf004444 0 0 1fe3 c04 a27c000 267bfa d941dfed 9e0000 0 0 0
udevadm_test: REL==143
udevadm_test: ABS==1 0
udevadm_test: MSC==10
udevadm_test: MODALIAS=input:b0003v062Ap0201e0110-e0,1,2,3,4,k71,72,73,74,77,80,82,83,85,86,87,88,89,8A,8B,8C,8E,8F,90,96,98,9B,9C,9E,9F,A1,A3,A4,A5,A6,A7,A8,A9,AB,AC,AD,AE,B1,B2,B5,CE,CF,D0,D1,D2,D5,D9,DB,E2,EA,EB,100,101,105,106,107,108,109,10A,10B,10C,162,166,16A,16E,178,179,17A,17B,17C,17D,17F,180,181,182,185,18C,18D,192,193,195,1A0,1A1,1A2,1A3,1A4,1A5,1A6,1A7,1A8,1A9,1AA,1AB,1AC,1AD,1AE,1B0,1B1,1B7,r0,1,6,8,a20,m4,lsfw
udevadm_test: ACTION=remove
udevadm_test: SUBSYSTEM=input
udevadm_test: run: 'socket:@/org/freedesktop/hal/udev_event'
udevadm_test: run: 'socket:/org/kernel/udev/monitor'
udevadm_test: run: '/usr/bin/xmodmap ~/.pndXmodmap'

which seems to show it should work, but it doesnt hope this helps get an answer.

Jake Aitchison

Posted 2011-02-22T20:57:35.423

Reputation: 343

1related. – None – 2013-10-05T15:16:01.403

Answers

16

I found a way to work around this, though its a little hacky.

I got to the same exact point today in trying to set up two keyboards with udev, setxkbmap, and xinput --list and for them to work with usb hotplugging. I am swapping keys around, not changing the layout, but its all the same, once you've identified your keyboard on a hotplug and can conditonally call setxkbmap, then you should be able to set the language of only the keyboard you've specified. The list of keyboard layouts can be found here ls -l /usr/share/kbd/keymaps/i386/ and you can find your device name to gre on with xinput -list.

  1. You'll want to replace rizumu with your username, as I found it wasn't possible a way to do this without being explicit.
  2. Make sure you grep on the your keyboard name.
  3. Use lsusb to discover the Hardware ID that you need to set in the udev rule. My das keyboard looks like this Bus 002 Device 009: ID 04d9:2013 Holtek Semiconductor, Inc.

I first set up the udev rule to autodetct the keyboard is by creating a udev rule:

In the file /etc/udev/rules.d/00-usb-keyboards.rules:

ACTION=="add", ATTRS{idVendor}=="04d9", ATTRS{idProduct}=="2013", RUN+="/home/rizumu/bin/kbd_udev", OWNER="rizumu"

I have two files ~/bin/kbd and ~/bin/kbd_udev. Make sure they have the right permissions chmod 755 ~/bin/kbd*

The ~/bin/kbd_udev script contains:

#!/bin/bash
/home/rizumu/bin/kbd &

And you'll notice that all it does is call ~/bin/kbd in the background, so that udev can complete its process and activate the keyboard. Inside the ~/bin/kbd script we sleep for a second, because we need to wait until the keyboard is activated so we can get the device id using xinput. To achive this I've set some variables and exported them so xinput setxkbmap can do thier work: DISPLAY, XAUTHORITY, HOME, and one daskb_id for the id of my daskeyboard:

#!/bin/bash
sleep 1
DISPLAY=":0.0"
HOME=/home/rizumu/
XAUTHORITY=$HOME/.Xauthority
export DISPLAY XAUTHORITY HOME
daskb_id=`xinput -list | grep -i 'daskeyboard' | grep -o id=[0-9]. | grep -o [0-9]. | head -1`

xset r rate 200 30
setxkbmap -layout colemak
setxkbmap -option ctrl:nocaps
if [ "${daskb_id}" ]; then
    setxkbmap -device "${daskb_id}" -option altwin:swap_lalt_lwin
fi

Thomas Schreiber

Posted 2011-02-22T20:57:35.423

Reputation: 276

Why setting OWNER for this device? – Limbo Peng – 2014-06-27T04:01:28.373

1What does the xset r rate 200 30 line do? xset is not available on my Ubuntu 17.04 installation. – kleinfreund – 2017-07-19T08:30:42.140

for some reason this bash script doesn't work for me. My Keyboard is listed twice in the xinput -list. like ↳ Cherry USB keyboard id=17 [slave keyboard (3)] ↳ Cherry USB keyboard id=18 [slave keyboard (3)] maybe that's why. – Max N – 2017-08-19T21:27:49.440

1I'm not able to run xmodmap $HOME/.Xmodmap using a script analogous to your "/home/rizumu/bin/kbd". Why would that be? – Geremia – 2017-12-10T06:26:40.677

Whoa! setxkbmap has a -device property, meaning you can have multiple keyboards configured with different layout each, and every one works with their own layouts. A-mazing! thank you really much for this! – carpinchosaurio – 2018-09-04T18:29:49.960

Thanks a lot for helping me answer my own question is AskUbuntu: http://askubuntu.com/questions/337411/how-to-permanently-assign-a-different-keyboard-layout-to-a-usb-keyboard

– Sadi – 2013-09-22T16:34:52.247

And I wonder if you can also help me add a notification message at the end of this script (e.g. notify-send "USB Keyboard is plugged in and ready for use now." -i gtk-dialog-info -t 1000 -u normal). As I don't know much about scripting, I tried inserting it before or after "fi" but in both cases the notification message kept on appearing over and over again :-( – Sadi – 2013-09-22T16:42:12.163

5

Depending on your distro, you may already have a udev rule for keyboards in /lib/udev/rules.d/64-xorg-xkb.rules. On Ubuntu, this imports /etc/default/keyboard, which has options roughly like this:

XKBMODEL="pc105"
XKBLAYOUT="us"
XKBVARIANT=""
XKBOPTIONS=""

For my setup, I found that this built-in rule was executing after my custom udev rule, and was overriding my settings. Instead I changed XKBOPTIONS in /etc/default/keyboard to be:

XKBOPTIONS="-option ctrl:nocaps"

To get the "Caps Lock is Control" behavior I wanted on all keyboards.

jsha

Posted 2011-02-22T20:57:35.423

Reputation: 181

2Great idea! Mine works with just XBKOPTIONS="ctrl:nocaps" – RasmusWL – 2017-01-05T19:01:06.800

3

If you're running GNOME then you'll need to disable its keyboard management plugin so that it doesn't override your layout changes.

gconftool-2 --toggle /apps/gnome_settings_daemon/plugins/keyboard/active

Run the same command again to enable it as desired.

Ignacio Vazquez-Abrams

Posted 2011-02-22T20:57:35.423

Reputation: 100 516

im running angstrom. will this work? – Jake Aitchison – 2011-02-23T02:06:06.767

Are you using GNOME on Ångström? – Ignacio Vazquez-Abrams – 2011-02-23T02:18:16.160

nope im using xfce 4.6.1 – Jake Aitchison – 2011-02-23T02:40:45.600

1In my Ubuntu 13.04, this is in dconf under /org/gnome/settings-daemon/plugins/keyboard/active. – nh2 – 2013-08-16T14:06:33.533

1And the command for Ubuntu 13.04 is: gsettings set org.gnome.settings-daemon.plugins.keyboard active false – Sadi – 2013-09-22T16:33:31.727

3

It does not work because udev and xmodmap don't have access to your X11 display. In fact, udev doesn't even know if there are active X11 displays.

  • Note: displays, plural. It can't use "the" X11 display because there can be more than one. For example, if you use "fast user switching".

user1686

Posted 2011-02-22T20:57:35.423

Reputation: 283 655

so How could I make this work? – Jake Aitchison – 2011-02-23T13:27:59.683

anyone know how I can fix this? – Jake Aitchison – 2011-02-23T15:27:03.967

1I've gotten udev to call setxkbmap. The udev rule calls a script which backgrounds another script (so udev can complete). The second script pauses for a second, sets up the expected X11 variables, and triggers setxkbmap. See my answer to the main question for more details. – Thomas Schreiber – 2011-10-25T18:41:01.460

@rizumu: Ahh, good luck getting that to work with GDM though. – user1686 – 2011-10-25T19:41:12.157

3

What about X.Org configuration? From Gentoo Wiki: X.Org/Input_drivers - udev:

Example: If you have a Logitech Access keyboard for the French part of Switzerland, you can use the following:

File: /etc/X11/xorg.conf.d/10-keyboard.conf

Section "InputClass"
    Identifier             "evdev keyboard catchall"
    MatchIsKeyboard        "on"
    MatchDevicePath        "/dev/input/event*"
    Driver                 "evdev"
    Option                 "XkbModel" "logiaccess"
    Option                 "XkbLayout" "ch"
    Option                 "XkbVariant" "fr"
EndSection

For an in-depth explanation, read:

man xorg.conf

and:

man evdev

ArchWiki demonstrates using the same syntax in xorg.conf but notes that "nowadays you should create a separate config file, like /etc/X11/xorg.conf.d/90-keyboard-layouts.conf". I use Arch and configured my own USB keyboard in the existing /etc/X11/xorg.conf.d/vim 10-evdev.conf Worked for me.

@rizumu: Clever kludge, thanks for sharing.

Casey Jones

Posted 2011-02-22T20:57:35.423

Reputation: 106

1I don't have a x.org.conf.d directory on Linux Mint 18.2 – Max N – 2017-08-19T21:04:25.847

3

I think I found a much cleaner way of configuring this, which does not require special X11 hack.

The idea behind this is that udev will only detect for new keyboard input and create a symlink for each layout, then inotify will watch for new layout in userspace.

udev rules

#/etc/udev/rules.d/61-usb-keyboard-layout.rules

# will match my Logitech keyboard with US layout 
SUBSYSTEM=="input", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c31c", GROUP="plugdev", MODE="0660", SYMLINK+="input/by-layout/us"

# will match my Lenovo integrated keyboard with IT layout
SUBSYSTEM=="input", ENV{ID_PATH}=="platform-i8042-serio-0", SYMLINK+="input/by-layout/it"

# force the directory to be recreated, just in case you unplug all input
SUBSYSTEM=="input", RUN="/bin/mkdir -p /dev/input/by-layout"

With this rules, I have a directory under dev (/dev/input/by-layout) to watch for changes in userspace scripts.

Userspace script for KDE

For example, when using KDE I have this script (auto)running:

#!/bin/bash

# In case no link are found, switch to this layout
DEFAULT="it"

switch_layout () {
        [ ! -z "$1" ] || return 0
        /usr/bin/qdbus org.kde.keyboard /Layouts org.kde.KeyboardLayouts.setLayout $1
}

best_layout() {
        local LAYOUT=$(ls -1t /dev/input/by-layout/ | head -n 1)
        if [ -z "$LAYOUT" ] ; then
                LAYOUT=$DEFAULT
        fi
        echo $LAYOUT
}

switch_layout $(best_layout)

while true ; do
        EVENT=$(inotifywait -q -e create -e delete --exclude '.*tmp.*' /dev/input/by-layout/)

        if echo "$EVENT" | grep -qe CREATE ; then
                LAYOUT=${EVENT#?*CREATE }
        fi

        if echo "$EVENT" | grep -qe DELETE ; then
                LAYOUT=$(best_layout)
        fi

        switch_layout $LAYOUT
done

This works like a charm to me. To change system layout (which I don't need right now), a similar script which use loadkeys can be demonized using a system init script.

giosh94mhz

Posted 2011-02-22T20:57:35.423

Reputation: 131

Thanks, this made me realize I can just use inotifywait to run a setup script on any change in /dev/input, as the script itself is idempotent. – Charlie Gorichanaz – 2019-05-04T16:29:21.397

2

To answer your question about accessing the running display, you can export the appropriate DISPLAY variable in the script, assuming that permissions for the display are correctly set. (man xset for display permissions.)

In many usual cases you can simply export DISPLAY=:0 for the command, as that's the first display on a single user system. It's probably easiest to run a script rather than xmodmap directly, as this will allow you to have more control over environment variables and the rest. (So replace "/usr/bin/xmodmap ~/.usbXmodmap" in your rule with "/usr/local/bin/keyboard_plug.sh" and put the appropriate commands in that script along with the DISPLAY variable.)

As noted above, though, if you assume DISPLAY=:0 then you might run into problems later on if you have multiple users or displays. You can write scripts to detect the appropriate display, but in that case you're on your own (as far as this answer is concerned). :)

rust_and_moth

Posted 2011-02-22T20:57:35.423

Reputation: 21

1

Since I couldn't get the hacks to make udev rules work, I wrote a small Python script using pyudev to monitor for input events.

#! /usr/bin/env python3

import pyudev
import time
import subprocess

ctx = pyudev.Context()
monitor = pyudev.Monitor.from_netlink(ctx)
monitor.filter_by("input")

def defer_xmodmap():
    time.sleep(1) # not sure if there's a race here, but it feels like there could be.
    subprocess.run("xmodmap ~/dotfiles/.xmodmap", shell=True)


for device in iter(monitor.poll, None):
    # there might be a way to add the action condition to the filter, but I couldn't find it
    if device.action != "add":
        continue

    # ensure the KB is initialized -- not sure if this is actually a needed check
    if not device.is_initialized:
        continue

    # my keyboard, from the output of `lsusb`
    if not "045E:07A5" in device.device_path:
        continue

    # it's the keyboard being added.
    defer_xmodmap()

I then use this systemd user unit file to keep it running (systemctl --user enable name_of_service_file):

[Unit]
Description=udev xmodmap monitor

[Service]
ExecStart=/usr/bin/env python3 %h/local/bin/monitor_kb_udev
Restart=always
RestartSec=10

[Install]
WantedBy=default.target

The inotifywait solution from @giosh94mhz is a little simpler, and avoids the dependency on pyudev. However, for some reason I found the inotify event wasn't being triggered for 10-20 seconds after my keyboard was connected.

Ryan Marcus

Posted 2011-02-22T20:57:35.423

Reputation: 111