Add a custom keyboard layout in Ubuntu using Ansible
Tags: ansible, ubuntu, keyboardlayout
Published: 2021-11-11 09:54:10

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     ] };
};
```

### *.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)
```

#### `! variants`

```
  dvorak       frost: Swedish (Frost Dvorak)
```

### *.xml entry

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

It looks like this:

``` xml
<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>
```

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

``` yaml
    - 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
```

### Add the `! layout` entry to evdev.lst

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

### Add the `! variant` entry to evdev.lst

``` yaml
    - 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)"
```

### Add the `<layout>` entry to evdev.xml

``` yaml

    - 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>
```

### Reconfigure xkb-data

``` yaml

    - name: Reconfigure xkb-data
      become: yes
      shell: dpkg-reconfigure xkb-data
```

### Set the GNOME keyboard layout(s)

``` yaml

    - 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
```

### Restart GNOME Shell without exiting all programs

``` yaml

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

```


Håppü Häcking!