How to permanently assign a different keyboard layout to a USB keyboard?

After a little research, I've found a solution, although I'm still open to other (probably better) answers.

Here's a startup script (which can be added to Startup Applications) which will set the maually entered usbkbd_layout variable to the usbkbd device ids found in the xinput -list:

#!/bin/bash
usbkbd=`xinput -list | grep -c "USB Keyboard"`
if [[ "$usbkbd" -gt 0 ]]
then
    usbkbd_ids=`xinput -list | grep "USB Keyboard" | awk -F'=' '{print $2}' | cut -c 1-2`
    usbkbd_layout="tr(f)"
    for ID in $usbkbd_ids
    do
      setxkbmap -device "${ID}" -layout "${usbkbd_layout}"
    done
fi
exit 0

This script is quite useful (and more stable) for scenarios where user starts using the laptop on a desktop setup (with external keyboard, mouse and monitor, etc.), and it can also be run manually whenever the external USB keyboard is plugged in...

==========================================================================

THE BETTER (almost perfect) SOLUTION - found thanks to MinimusHeximus and the respective contributors to the thread he mentioned in his comment below:

I can now just plugin my USB keyboard and automatically have its different (TR-F) keyboard layout applied while still keeping the default keyboard layout (TR-Q) on my laptop!

Here are the files and their contents that make this possible:

/etc/udev/rules.d/00-usb-keyboard.rules

ATTRS{idVendor}=="09da", ATTRS{idProduct}=="0260", OWNER="sadi"
ACTION=="add", RUN+="/home/sadi/.bin/usb-keyboard-in_udev"
ACTION=="remove", RUN+="/home/sadi/.bin/usb-keyboard-out_udev"

/home/sadi/.bin/usb-keyboard-in_udev

#!/bin/bash
/home/sadi/.bin/usb-keyboard-in &

/home/sadi/.bin/usb-keyboard-in

#!/bin/bash
sleep 1
DISPLAY=":0.0"
HOME=/home/sadi/
XAUTHORITY=$HOME/.Xauthority
export DISPLAY XAUTHORITY HOME
usbkbd_id=`xinput -list | grep "USB Keyboard" | awk -F'=' '{print $2}' | cut -c 1-2 | head -1`
usbkbd_layout="tr(f)"
if [ "${usbkbd_id}" ]; then
    gsettings set org.gnome.settings-daemon.plugins.keyboard active false
    sleep 2
    setxkbmap -device "${usbkbd_id}" -layout "${usbkbd_layout}"
fi

/home/sadi/.bin/usb-keyboard-out_udev

#!/bin/bash
/home/sadi/.bin/usb-keyboard-out &

/home/sadi/.bin/usb-keyboard-out

#!/bin/bash
sleep 1
DISPLAY=":0.0"
HOME=/home/sadi/
XAUTHORITY=$HOME/.Xauthority
export DISPLAY XAUTHORITY HOME
gsettings set org.gnome.settings-daemon.plugins.keyboard active true

Notes:

  1. Of course all of the four files in your ."bin" folder should have necessary permissions (readable and executable) which maybe implemented for example with a Terminal command like chmod - 755 /home/sadi/.bin/usb-keyboard-*
  2. Sometimes after the USB keyboard is plugged in it still uses the same (default) keyboard layout, and switches to the specified layout upon the second try (perhaps requiring a little more sleep time somewhere?)
  3. The USB keyboard specific layout is not effective in the login screen (when you Log Out).
  4. If you use a separate partition for /home, then it might be a better idea to put the four scripts somewhere in the root partition, e.g. /usr/local/bin and modify the contents of all respective files accordingly as sometimes udev may look for those files before your /home partition is mounted and cause problems.

IN ORDER TO ADAPT THIS SETUP TO DIFFERENT REQUIREMENTS:

  1. USB keyboard vendor and product ids should be changed as per the output of the command lsusb (For example, my lsusb output have this for my USB Keyboard: Bus 001 Device 006: ID 09da:0260 A4 Tech Co., Ltd)
  2. OWNER and all user directory names should be changed from "sadi" to another name
  3. The usbkbd_id may require a little adjustment to grab the correct device id (For example, output of the commands xinput -list | grep "USB Keyboard" gives me two lines; ↳ USB Keyboard id=14 [slave keyboard (3)] and ↳ USB Keyboard id=16 [slave keyboard (3)]; which are then filtered by awk using "=" as field delimiter and capturing the second part; then cutting only the first two digits, and then using only the value in the first line)
  4. The value for usbkbd_layout may be any other valid choice

One can specify X11 driver options inside the udev rule, no custom scripts are needed. As an example, here are the contents of my /etc/udev/rules.d/99-usb-kbd.rules

ACTION=="add", ATTRS{idVendor}=="04d9", ATTRS{idProduct}=="2323", ENV{XKBMODEL}="pc104", ENV{XKBLAYOUT}="us", ENV{XKBVARIANT}="euro", ENV{XKBOPTIONS}="compose:caps"

This rule ensures that a particular USB keyboard uses US layout in Xorg (my laptop's internal keyboard is German, and this is also my primary layout). Important points:

  1. You can find out idVendor and idProduct of your device using lsusb or evtest
  2. You can use any layout from /usr/share/X11/xkb/symbols. Pay attention to specify both a valid layout and a valid variant.
  3. The file name must start with a number >64 in order for the settings to overwrite the system wide settings specified in /lib/udev/rules.d/64-xorg-xkb.rules
  4. Make sure that Gnome/KDE layout management does not overwrite your settings.

I've just improved this solution for a bépo Typematrix keyboard (french version of optimized excellent dvorak) and in a wide system context (it supposes that you have a root access to the machine). It needs only 3 files to work. You can consult a logfile in case of failure to figure out what is failing.

/etc/udev/96-usb-keyboard.rules

ATTRS{idVendor}=="1e54", ATTRS{idProduct}=="2030", SUBSYSTEMS=="usb", ACTION=="add", RUN+="/etc/udev/bepo-typematrix-kbd.sh in"
ATTRS{idVendor}=="1e54", ATTRS{idProduct}=="2030", SUBSYSTEMS=="usb", ACTION=="remove", RUN+="/etc/udev/bepo-typematrix-kbd.sh out"

/etc/udev/bepo-typematrix-kbd.sh (absolutely necessary to use an intermediate backgrounding script)

#!/bin/bash

dir=$(dirname $0)
command=$(basename $0)
command=$dir/${command%\.sh}
arg=$1 # must be "in" or "out"
LOG=/var/log/bepo-typematrix-kbd.log

[ -x "$command" ] && $command $arg >$LOG 2>&1 &

/etc/udev/bepo-typematrix-kbd

#!/bin/bash
# jp dot ayanides at free.fr

MODEL="tm2030USB-102" # keyboard model
DISPLAY=':0.0'
GSETTING=/usr/bin/gsettings
XSET=/usr/bin/xset
SETXKBMAP=/usr/bin/setxkbmap
XINPUT=/usr/bin/xinput

USER=$(/usr/bin/who | /usr/bin/awk -v DIS=':0' '{if ($2==DIS) print $1}')
eval HOME=~$USER
XAUTHORITY=$HOME/.Xauthority
export DISPLAY XAUTHORITY HOME

case $1 in
        'in')
                BEPO=$($XINPUT list --short | grep "TypeMatrix.com USB Keyboard" | grep keyboard | sed -e 's/^.*id=\([0-9]\+\).*/\1/g')
                if [ -n "$BEPO" ]; then
                        [ -x $GSETTING ] && $GSETTING set org.gnome.settings-daemon.plugins.keyboard active false
                        # apparently nothing to do with TDE (trinity KDE)
                        for ID in $BEPO; do # case of multiple bepo keyboard is taken into account
                                [ -x $SETXKBMAP ] && $SETXKBMAP -device $ID -model $MODEL -layout fr -variant bepo
                        done
                fi
                echo "bépo keyboard id(s) is (are) $BEPO"
                [ -x $XSET ] && $XSET -display $DISPLAY r rate 250 40
        ;;
        'out')
                # apparently nothing to do with TDE (trinity KDE)
                [ -x $GSETTING ] && $GSETTING set org.gnome.settings-daemon.plugins.keyboard active true
        ;;
        *)
                printf "wrong parameter: $1\n"
                exit 1
        ;;
esac