32

I often plugin a USB keyboard to my laptop (in addition to external monitor and mouse, which all virtually convert my laptop to a desktop computer) and I prefer using a different keyboard layout then.

I have to manually change the current keyboard layout each time I plugin this USB keyboard.

And I'd like to use an automated way for this, if possible.

Radu's answer to the question here gives some clues, but it seems I'll need a startup script for this task as the device ID for my USB keyboard changes every time the computer starts.

This startup script will probably first include the command xinput -list | grep "USB Keyboard", and another command to grab the first USB Keyboard ID number displayed and then use it in the final command to set my chosen layout for that USB keyboard as below:

setxkbmap -device <NUMBER> -layout <LAYOUT>

1

6 Answers 6

18

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
18
  • 2
    It would be great if another similar script could automatically run whenever a USB keyboard is plugged in, as asked (not answered yet) here: askubuntu.com/questions/284224/…
    – Sadi
    Aug 27, 2013 at 7:30
  • 2
    It can be helpful if you can post another answer out of this messy thread: superuser.com/questions/249064/… Aug 30, 2013 at 4:00
  • 1
    @MinimusHeximus Thanks a million!!! After going through the thread you've pointed out and making some trial-and-error, I've finally sorted it out and will add this new (almost) perfect hotplugging solution above!
    – Sadi
    Sep 20, 2013 at 12:58
  • 1
    My Keyboard for some reason has two IDs, so instead of using an IF I had to use a FOR. It works for me now, thank you! gist.github.com/zvictor/193b567c14b5b6a679fe
    – zVictor
    Jul 9, 2014 at 8:26
  • 1
    @Sadi I’m a mere average user myself. I’ll post my solution here. Maybe you can take some ideas off of that. Jul 23, 2017 at 13:58
12

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.
6
  • Unplug and replug your keyboard and look in /var/log/Xorg.0.log for possible issues. You can also use udevadm info to check that the settings are applied correctly.
    – pavel
    Aug 9, 2016 at 22:28
  • Eureka! Your smart solution began working only after I entered this command (once for all) to override the current gconf setting: gsettings set org.gnome.settings-daemon.plugins.keyboard active false. If you also add such a note, I'll try to mark yours as the answer (instead of that clumsy, long and winding road ;-)
    – Sadi
    Aug 13, 2016 at 8:13
  • This is exceptional. On Ubuntu 14.04 the possible models/layouts/options/etc. are listed at /usr/share/X11/xkb/rules/evdev.lst
    – Jon
    Sep 21, 2016 at 10:20
  • 2
    Unfortunately this nice and easy solution stopped working for me after upgrading to Ubuntu Gnome 17.04 :-(
    – Sadi
    Apr 14, 2017 at 9:04
  • Tried Linux Mint 18.2, which is based on Ubuntu 16.04 LTS, with udev version 229-4ubuntu19, no effect again :-( Any ideas about this odd udev behavior???
    – Sadi
    Jul 25, 2017 at 8:55
2

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
1

After fiddling around a lot, this is what I have running for now. Maybe I’ll write a complete article of sorts and publish the code within a repository, if that would be of interest.


Set up a new rule set for udev like that:

 sudo gedit /etc/udev/rules.d/80-external-keyboard.rules

The rule is supposed to call a shell script whenever some action is triggered by a device with the given combination of vendor and product ID.

ATTRS{idVendor}=="04b4", ATTRS{idProduct}=="4042", RUN+="/home/phil/.bin/switch-kb-layout-wrapper.sh"

After adding the new rule set, restart the udev service:

sudo service udev restart

Note: I was not able to achieve reliable results by providing more specific matching rules in that file. Most significantly, adding a ACTION matching rule did not work. As far as I can tell, the script was triggered anyway. When adding ACTION=="add", the script would still be called upon removing the device. Very strange and confusing.

However the action that triggered the udev rule will be available to the called script as shown below.


Next, the script itself. Well, not quite. Note the wrapper suffix in the file name. This indicates that this is not the actual script but a wrapper that calls the script and executes it in the background so that udev can finish its process.

~/.bin/switch-kb-layout-wrapper.sh:

#!/bin/sh
/home/phil/.bin/switch-kb-layout.sh "${ACTION}" &

The variable ACTION contains the udev action that was triggered by the device. It yields values like add (device was plugged in) and remove (device was removed). We’ll use these later on.

~/.bin/switch-kb-layout.sh:

#!/bin/sh

sleep 1

# Some environment variables that need to be set in order to run `setxkbmap`
DISPLAY=":0.0"
HOME=/home/phil
XAUTHORITY=$HOME/.Xauthority
export DISPLAY XAUTHORITY HOME

udev_action=$1
log_file="$HOME/switch-kb-layout.log"

if [ "${udev_action}" != "add" ] && [ "${udev_action}" != "remove" ]; then
    echo "Other action. Aborting." >> $log_file
    exit 1
fi

internal_kb_layout="de"
internal_kb_variant=""

external_kb_layout="us"
external_kb_variant="altgr-intl"

kb_layout=""
kb_variant=""

if [ "${udev_action}" = "add" ]; then
    kb_layout=$external_kb_layout
    kb_variant=$external_kb_variant
elif [ "${udev_action}" = "remove" ]; then
    kb_layout=$internal_kb_layout
    kb_variant=$internal_kb_variant
fi

setxkbmap -layout "${kb_layout}"
echo "set layout:" "$kb_layout" >> $log_file
if [ ! -z "${kb_variant}" ]; then
    setxkbmap -variant "${kb_variant}"
    echo "set variant:" "$kb_variant" >> $log_file
fi

Replace my username with yours when setting the HOME variable ($(whoami) won’t work here, since this will not be called by your user but by root).

sed -i "s/phil/YOUR_USERNAME/g" ~/.bin/switch-kb-layout.sh

For testing purpose, I added some lines that log certain events to a file in my home directory to see whether everything works. You can savely remove these.


Finally, these scripts need to have execution permissions. Also it might be important to note that these scripts will be called by the root user, so be careful what you do in there.

chmod +x ~/.bin/switch-kb-layout-wrapper.sh ~/.bin/switch-kb-layout.sh 
7
  • 1
    Thanks, great work! I've got it working as well. However, why I see 14 times "Add action" and 3 times "Other action" when I plugin my external command is a mystery, which I'll try to solve later. But I've found that it's better to add also device ID to setxkbmap command so that internal keyboard is still usable as well as the external keyboard, as in my latest autostartup script. I'll give this a try for a couple of days, and come back and see if yours can be regarded as THE answer, perhaps with a little modification here and there ;-)
    – Sadi
    Jul 23, 2017 at 20:43
  • Also getting the same logged lines multiple times. Good catch on the device ID on the command. Jul 23, 2017 at 22:17
  • @Sadi Also I think the line gsettings set org.gnome.settings-daemon.plugins.keyboard active false doesn’t work as intended, as the script is run by root. In my tests, the line didn’t have an effect on that setting. Jul 24, 2017 at 6:18
  • I think in such a scenario, it might be a good idea to execute the command gsettings set org.gnome.settings-daemon.plugins.keyboard active false as user once and for all, and then use this script without the gsettings set commands...
    – Sadi
    Jul 24, 2017 at 9:20
  • So far I've seen that we still cannot get this thing working smoothly; from time to time the keyboard layout is not being implemented by udev, and you have to plug off and plug in again. I think I'll go back to my (updated) startup script at the top of my answer again for some time, which serves me more smoothly - only I have to run it manually if I plug in my external keyboard after startup. It seems udev is a bit buggy as can also be seen in so many action repetitions (it is running the wrapper script many times, and in strange ways)...
    – Sadi
    Jul 24, 2017 at 19:03
1

I had some permission problem with the script executed by udev. I resolved with sudo as follow:

# Estract id of MX3 keyboard devices that present themself as "123 COM Smart Control"
    IDLIST=$(sudo -u max /usr/bin/xinput -list | grep "123 COM Smart Control" | grep keyboard | sed -e 's/^.*id=\([0-9]\+\).*/\1/g')

Set the keyboard map for each device

    for ID in $IDLIST; do
            sudo -u max /usr/bin/setxkbmap -device $ID -layout "${kb_layout}" -display :0
    done
1
  • I think it can be more useful if you can write it in full. For example, where have you put the lines above ?
    – Sadi
    Jun 7, 2018 at 11:34
0

You can also define it in a Xorg config file.

It is layed out in this stackexchange answer: https://superuser.com/a/946575/437492

3
  • That would be great, if I could get it work. But using the latest LTS release, Ubuntu 18.04, I can only see /usr/share/X11/xorg.conf.d/ directory which contains a file named 40-libinput.conf. So I simply added a section for the external keyboard in that file. Using the driver libinput or evdev and a longer and shorter vendor name, I couldn't get any result. Any ideas?
    – Sadi
    Jul 26, 2019 at 10:33
  • @Sadi: If you read a bit about Xorg configuration, you will figure out that you can simply add new files to /etc/X11/xorg.conf.d/ (that's the place to go). Jul 28, 2019 at 10:07
  • Thanks. I surely read quite a bit and tried first using "/etc/X11/xorg.conf.d/30-keyboard.conf". I'll try my luck under the stackexchange answer mentioned above then. ;-)
    – Sadi
    Jul 29, 2019 at 11:55

You must log in to answer this question.

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