DEV Community

Martin Frost
Martin Frost

Posted on

Add a custom keyboard layout in Ubuntu using Ansible

I have been using a slightly custom-modded version of the US Dvorak layout for years, and I like automating my computer setups.

It has always seemed like a hassle trying to automate the keyboard layout setup, so I haven't gotten myself to do it until now.

How to add a custom keyboard layout in Ubuntu?

You need:

  • A functional xkb keyboard layout, placed under /usr/share/X11/xkb/symbols/
  • An entry in one of the *.lst files under /usr/share/X11/xkb/rules/
  • An entry in the corresponding *.xml file under the same rules directory

When all this is done, you need to run the sudo dpkg-reconfigure xkeyboard-data command, to make your installation reload the configured keyboard layouts. This will enable your desktop environment or window manager (Gnome, KDE, i3, XMonad, etc.) to pick up on the changes and let you choose your custom keyboard layout so that it's available even at the login screen.

XKB Layout

My layout is basically that I modded the standard US Dvorak layout by adding some characters with umlauts and other stuff that's used in the Swedish language (å, ö, ä, é, and the very infrequently used ü). I access these by holding the altgr key and pressing a, o, e, u, and y keys respectively (i.e., for the people who don't know the Dvorak layout, that's the keys that usually have the letters a, s, d, f, and t on them).

It looks like this

// This is basically a US Dvorak with some (mostly) Swedish accented characters added
// å -> alt-gr a
// ä -> alt-gr e
// ö -> alt-gr o
// é -> alt-gr u
// ü -> alt-gr y

default partial alphanumeric_keys
xkb_symbols "dvorak" {
    include "us(dvorak)"
    include "level3(caps_switch)"
    include "level3(ralt_switch)"

    name[Group1] = "Swedish (Frost Dvorak)";

    //             Unmodified       Shift           AltGr            Shift+AltGr

    // // symbols row, left side
    key <AD05> { [ y,               Y,              udiaeresis,      Udiaeresis ] };

    // home row, left side
    key <AC01> { [ a,               A,              aring,           Aring      ] };
    key <AC02> { [ o,               O,              odiaeresis,      Odiaeresis ] };
    key <AC03> { [ e,               E,              adiaeresis,      Adiaeresis ] };
    key <AC04> { [ u,               U,              eacute,          Eacute     ] };
};
Enter fullscreen mode Exit fullscreen mode

*.lst entries

Normally, there should be an evdev.extras.lst file, and it should be possible to add your entry there, but due to some bug, that doesn't work, and we have to add this to the evdev.lst file.

You need to add two different lines in this file, one under the ! layouts section, and one under the ! variants section.

! layouts

  frost           Swedish (Frost Dvorak)
Enter fullscreen mode Exit fullscreen mode

! variants

  dvorak       frost: Swedish (Frost Dvorak)
Enter fullscreen mode Exit fullscreen mode

*.xml entry

The XML entry gives pretty much the same information as the lst entry, but in a different format.

It looks like this:

<layout>
  <configItem>
    <name>frost</name>
    <shortDescription>frost dvorak</shortDescription>
    <description>Swedish</description>
    <languageList>
      <iso639Id>swe</iso639Id>
    </languageList>
  </configItem>
  <variantList>
    <variant>
      <configItem>
        <name>dvorak</name>
        <description>Swedish (Frost Dvorak)</description>
      </configItem>
    </variant>
  </variantList>
</layout>
Enter fullscreen mode Exit fullscreen mode

The same bug-related thing goes here, so this needs to be added to the evdev.xml file.

Automating all of this using Ansible

I was going through my setup scripts the other day and realized that I could probably be able to automate this entire thing as well, to remove a few more of the manual steps in setting up my computer. I've had some bad luck with work computers the past year and a half and so I've had to do this multiple times.

After a little digging around and testing things, I found out that we can use the lineinfile module for the *.lst entries, and the xml module for the XML entry.

Not only that, but we can also tell Ansible to automatically run the dpkg-reconfigure command, AND tell it to configure my keyboard layouts for me, so I don't have to poke around in the menus at all!

Here's the Playbook definition I use for this, task by task.

Copy the keyboard layout file

    - name: Copy keyboard layout file
      become: yes
      file:
        src: frost.xkb
        dest: /usr/share/X11/xkb/symbols/frost
        owner: root
        group: root
        mode: '0644'
        state: file
Enter fullscreen mode Exit fullscreen mode

Add the ! layout entry to evdev.lst

    - name: Add layout to evdev.lst
      become: yes
      lineinfile:
        path: /usr/share/X11/xkb/rules/evdev.lst
        insertafter: "! layout"
        line: "  frost           Swedish (Frost Dvorak)"
Enter fullscreen mode Exit fullscreen mode

Add the ! variant entry to evdev.lst

    - name: Add variant to evdev.lst
      become: yes
      lineinfile:
        path: /usr/share/X11/xkb/rules/evdev.lst
        insertafter: "! variant"
        line: "  dvorak       frost: Swedish (Frost Dvorak)"
Enter fullscreen mode Exit fullscreen mode

Add the <layout> entry to evdev.xml


    - name: Add layout to evdev.xml
      become: yes
      xml:
        path: /usr/share/X11/xkb/rules/evdev.xml
        xpath: /xkbConfigRegistry/layoutList
        backup: yes
        input_type: xml
        add_children:
          - |
            <layout>
              <configItem>
                <name>frost</name>
                <shortDescription>frost dvorak</shortDescription>
                <description>Swedish</description>
                <languageList>
                  <iso639Id>swe</iso639Id>
                </languageList>
              </configItem>
              <variantList>
                <variant>
                  <configItem>
                    <name>dvorak</name>
                    <description>Swedish (Frost Dvorak)</description>
                  </configItem>
                </variant>
              </variantList>
            </layout>
Enter fullscreen mode Exit fullscreen mode

Reconfigure xkb-data


    - name: Reconfigure xkb-data
      become: yes
      shell: dpkg-reconfigure xkb-data
Enter fullscreen mode Exit fullscreen mode

Set the GNOME keyboard layout(s)


    - name: Set keyboard layout
      become: yes
      become_user: frost
      tags: dconf
      dconf:
        key: "/org/gnome/desktop/input-sources/sources"
        value: "[('xkb', 'frost+dvorak'), ('xkb', 'dvorak')]"
        state: present
Enter fullscreen mode Exit fullscreen mode

Restart GNOME Shell without exiting all programs


    - name: Restart GNOME Shell
      shell: busctl --user call org.gnome.Shell /org/gnome/Shell org.gnome.Shell Eval s 'Meta.restart("Restarting…")'

Enter fullscreen mode Exit fullscreen mode

Håppü Häcking!

Top comments (0)