Xorg: Switching Keyboard Layouts Consistenly and Reliably from Userspace

For some time now, the XkbLayout option (set to “us, fr, de”) in my /etc/X11/xorg.conf.d/10-keyboard.conf has been broken for me. This probably has something to do with my recent xmodmap hacks in ~/.xinitrc, and possibly the interaction with the /etc/X11/xorg.conf.d/10-evdev.conf, which includes an entry to catch all keyboards. I decided to fix this problem today, and with a healthy dose of serendipity, got everything working smoothly again. Sources for my custom scripts are included, as usual.

By accident, I discovered the setxkbmap userland program. It does everything you could possibly do with keyboard layouts. It is maintained by the Xorg project, so it’s pretty much guaranteed to work. Here’s a sample run (using Arch’s xorg-setxkbmap 1.2.0-2 and xorg-server 1.10.1-1):

$ setxkbmap -help
Usage: setxkbmap [args] [<layout> [<variant> [<option> ... ]]]
Where legal args are:
-?,-help            Print this message
-compat <name>      Specifies compatibility map component name
-config <file>      Specifies configuration file to use
-device <deviceid>  Specifies the device ID to use
-display <dpy>      Specifies display to use
-geometry <name>    Specifies geometry component name
-I[<dir>]           Add <dir> to list of directories to be used
-keycodes <name>    Specifies keycodes component name
-keymap <name>      Specifies name of keymap to load
-layout <name>      Specifies layout used to choose component names
-model <name>       Specifies model used to choose component names
-option <name>      Adds an option used to choose component names
-print              Print a complete xkb_keymap description and exit
-query              Print the current layout settings and exit
-rules <name>       Name of rules file to use
-symbols <name>     Specifies symbols component name
-synch              Synchronize request w/X server
-types <name>       Specifies types component name
-v[erbose] [<lvl>]  Sets verbosity (1..10).  Higher values yield
                    more messages
-variant <name>     Specifies layout variant used to choose component names

And here’s a sample query on my machine:

$ setxkbmap -query
rules:      base
model:      pc105
layout:     us

And here’s how to change layouts.

To switch to the “us” keyboard layout:

$ setxkbmap us

To switch to the “fr” layout:

$ setxkbmap fr

To switch to the “de” layout:

$ setxkbmap de

And so on. Very useful, indeed. Here are some things to keep in mind:

  1. The layout change is X server-wide. This means that your layout change will take effect on all windows. Of course, you can limit your layout change with the various options like -display or -geometry, but to me that’s impractical.
  2. You can run this command from within a script, in a sub-shell (backgrounded), and it will still work! This is a nice feature which allows one to script/automate the whole thing (which is what I did).
  3. Unfortunately, running this command clears any xmodmap settings that were present before. For Xmonad/vim/etc. users who like to change up their Caps Lock key, for example, you need to re-run the xmodmap commands to get them back. I’m in this boat.

So, here’s my new setup, taking into account the xmodmap issue noted above:

  • layout_switch.sh: This script simply changes up the layout. It’s a dead-simple zsh script that you can easily port to Bash.
  • xmodmap.sh: This script simply does all the xmodmap-editing business that I need to do to get my Xmonad setup to work.
  • ~/.xmonad/xmonad.hs: Here, I have set up a hotkey sequence to call the layout_switch.sh script. I have to be careful to choose a hotkey that will remain consistent even in different layouts (e.g., I first tried the “\” backslash key, but this key’s location is different in the French layout). This means that I have to depend on keys like Escape, or the F1-F12 function keys. Since I already have F1-F12 mapped to additional workspaces, I chose the Escape key.
  • ~/.xinitrc: Here, I call the layout_switch.sh script to set my keyboard layout explicitly on startup. You could do fancy things, for example, of selecting a different layout on a different day (maybe on Sundays you want to start typing in French only, so you could just script that in here).

And here are the relevant source files for these scripts:

layout_switch.sh:

#!/bin/zsh
# LICENSE: PUBLIC DOMAIN
# switch between us, fr, and de layouts

# If an explicit layout is provided as an argument, use it. Otherwise, select the next layout from
# the set [us, fr, de].
if [[ -n "$1" ]]; then
    setxkbmap $1
else
    layout=$(setxkbmap -query | awk 'END{print $2}')
    case $layout in
        us)
            setxkbmap fr
            ;;
        fr)
            setxkbmap de
            ;;
        *)
            setxkbmap us
            ;;
    esac
fi

# remap Caps_Lock key to Xmonad's exclusive 'mod' key
~/syscfg/script/sys/xmodmap.sh

xmodmap.sh:

#!/bin/zsh
# LICENSE: PUBLIC DOMAIN
# remap Caps_Lock key to Xmonad's exclusive 'mod' key

xmodmap -e "remove Lock = Caps_Lock"
xmodmap -e "add mod3 = Caps_Lock"
xmodmap ~/.xmodmap

~/.xmodmap:

remove Lock = Caps_Lock
remove mod3 = Super_L
keysym Caps_Lock = Super_L
add mod3 = Super_L

~/.xmonad/xmonad.hs:

myKeys :: String -> XConfig Layout -> M.Map (KeyMask, KeySym) (X ())
myKeys hostname conf@(XConfig {XMonad.modMask = modm}) = M.fromList $
...
    -- change keyboard layouts
    , ((modm              , xK_Escape), spawn "/home/shinobu/syscfg/script/sys/layout_switch.sh")
...

~/.xinitrc:

...
# explicitly choose "us" (English) keyboard layout (and set Caps Lock key to
# xmonad's modmap key)
/home/shinobu/syscfg/script/sys/layout_switch.sh us &
...

There are a couple big advantages to this setup:

  • Because everything is handled in userspace (we didn’t touch any Xorg file!), we no longer have to restart the X server to diagnose any problem we encounter with changing the keyboard layout. Gone are the days of fiddling with a “XkbLayout” option (or any other Xorg option) and restarting X just to see if the changes worked.
  • Because everything is handled in userspace, we can use any number of custom shell scripts, programs, etc. to customize how the layout switch is made. For me, since I use Xmonad, I can use Xmonad’s extensive, consistent hotkey definitions to choose which hotkeys to do the layout switch. To be honest, I disliked the options available in the base file (/usr/share/X11/xkb/rules/base) when choosing which hotkeys to do my keyboard layout switching. And, my choice of modm + Escape (i.e., (dead) Caps Lock key + Escape) was impossible to realize with the base file.

Finally, a caveat: the only flaw with the setup above is that if I execute the xmonad.hs layout-switching hotkey too quickly too many times, there’s a chance of two independent processes of layout_switch.sh being spawned at nearly the same time. This means that the call to xmodmap.sh within each of those processes might get interleaved! For me, this has resulted in my Caps lock key becoming permanently turned on, for instance. However, you can always execute the xmodmap.sh script a second or two afterwards to clear this up.

I strongly recommend everyone to do their keyboard switching this way — not only is everything made explicit, but everything is transparent, too: keyboard layout switching isn’t a mysterious process anymore.

UPDATE August 18, 2011: Cleaned up some typos.

UPDATE August 24, 2011: See this post — I no longer use the fr and de layouts, but rather, just the us layout with variant altgr-intl. It’s much, much simpler this way.

Advertisements

7 thoughts on “Xorg: Switching Keyboard Layouts Consistenly and Reliably from Userspace

  1. This is an awesome post. Not because I need to do any of this usermap switiching but because I have installed a new distro in order to learn more about Linux. This xmodmap and xsetkb bollocks have caused me a headache in trying to understand the super technical language of the man page. Though this quick post has cleared up quite a bit. Thanks for the info.

  2. Hi,
    Nice explanatory post.
    I’ve had a little problem with setxkbmap and have set it to “gb” only to find that when I reboot it goes back to “us”. I just cannot seem to get it to stick.
    Is there a simple setting that can be used? I’m using Arch.
    Thanx in advance

  3. Hi Marcus,

    I don’t understand why you’re asking a question that is already covered by my post, but whatever. I’d just put setxkbmap in a script, and then call the script from ~/.xinitrc (or call setxkbmap from ~/.xinitrc directly).

  4. Have you tried using -option to specify CapsLock position? For example
    setxbmap -option ctrl:swapcaps us

  5. @Andrei: I guess you could do that if you just want to switch the position of Ctrl and Capslock. But my setup *kills* Capslock and changes it to a modkey for use in xmonad. To each his own…

Comments are closed.