Monday, July 6, 2020

User-specific XKB configuration - part 2

This is the continuation from this post.

Several moons have bypassed us [1] in the time since the first post, and Things Have Happened! If you recall (and of course you did because you just re-read the article I so conveniently linked above), libxkbcommon supports an include directive for the rules files and it will load a rules file from $XDG_CONFIG_HOME/xkb/rules/ which is the framework for custom per-user keyboard layouts. Alas, those files are just sitting there, useful but undiscoverable.

To give you a very approximate analogy, the KcCGST format I described last time are the ingredients to a meal (pasta, mince, tomato). The rules file is the machine-readable instruction set to assemble your meal but it relies a lot on wildcards. Feed it "spaghetti, variant bolognese" and the actual keymap ends up being the various components put together: "pasta(spaghetti)+sauce(tomato)+mince". But for this to work you need to know that spag bol is available in the first place, i.e you need the menu. This applies to keyboard layouts too - the keyboard configuration panel needs to present a list so the users can clickedy click-click on whatever layout they think is best for them.

This menu of possible layouts is provided by the xkeyboard-config project but for historical reasons [2], it is stored as an XML file named after the ruleset: usually /usr/share/X11/xkb/rules/evdev.xml [3]. Configuration utilities parse that file directly which is a bit of an issue when your actual keymap compiler starts supporting other include paths. Your fancy new layout won't show up because everything insists on loading the system layout menu only. This bit is part 2, i.e. this post here.

If there's one thing that the world doesn't have enough of yet, it's low-level C libraries. So I hereby present to you: libxkbregistry. This library has now been merged into the libxkbcommon repository and provides a simple C interface to list all available models, layouts and options for a given ruleset. It sits in the same repository as libxkbcommon - long term this will allow us to better synchronise any changes to XKB handling or data formats as we can guarantee that the behaviour of both components is the same.

Speaking of data formats, we haven't actually changed any of those which means they're about as easy to understand as your local COVID19 restrictions. In the previous post I outlined the example for the KcCGST and rules file, what you need now with libxkbregistry is an XKB-compatible XML file named after your ruleset. Something like this:

$ cat $HOME/.config/xkb/rules/evdev.xml
<?xml version="1.0" encoding="UTF-8"?>                                           
<!DOCTYPE xkbConfigRegistry SYSTEM "xkb.dtd">                                    
<xkbConfigRegistry version="1.1">                                                
  <layoutList>                                                                   
    <layout>                                                                     
      <configItem>                                                               
        <name>us</name>                                                      
      </configItem>                                                              
      <variantList>                                                              
        <variant>                                                                
          <configItem>                                                           
            <name>banana</name>                                                  
            <shortDescription>banana</shortDescription>                              
            <description>US (Banana)</description>                           
          </variant>                                                             
      </variantList>                                                             
  </layoutList>                                                                  
  <optionList>                                                                   
    <group allowMultipleSelection="true">                                        
      <configItem>                                                               
        <name>custom</name>                                                      
        <description>Custom options</description>                                
      </configItem>                                                              
      <option>                                                                   
      <configItem>                                                               
        <name>custom:foo</name>                                                  
        <description>Map Tilde to nothing</description>                          
      </configItem>                                                              
      </option>                                                                  
      <option>                                                                   
      <configItem>                                                               
        <name>custom:baz</name>                                                  
        <description>Map Z to K</description>                                    
      </configItem>                                                              
      </option>                                                                  
    </group>                                                                     
  </optionList>                                                                  
</xkbConfigRegistry>   
This looks more complicated than it is: we have models (not shown here), layouts which can have multiple variants and options which are grouped together in option group (to make options mutually exclusive). libxkbregistry will merge this with the system layouts in what is supposed to be the most obvious merge algorithm. The simple summary of that is that you can add to existing system layouts but you can't modify those - the above example will add a "banana" variant to the US keyboard layout without modifying "us" itself or any of its other variants. The second part adds two new options based on my previous post.

Now, all that is needed is to change every user of evdev.xml to use libxkbregistry. The gnome-desktop merge request is here for a start.

[1] technically something that goes around something else doesn't bypass it but the earth is flat, the moon is made of cheese, facts don't matter anymore and stop being so pedantic about things already!
[2] it's amazing what you can handwave away with "for historical reasons". Life would be better if there was less history to choose reasons from.
[3] there's also evdev.extras.xml for very niche layouts which is a separate file for historical reasons [2], despite there being a "popularity" XML attribute