Unable to set custom xkb layout

TLDR: Some of the keys in my keyboard doesn’t work so i want to change it.

Today i decided to tune my keyboard a bit since some keys like the at symbol and the exclam symbol doesn’t work properly

The problem occurs when i try to follow the nixos.wiki about how to change the layout of your keyboard using xmodmap

  cat /etc/nixos/configuration.nix
  ...
  let
    myCustomLayout = pkgs.writeText "xkb-layout" ''
      keysym e = e E exclam
      keysym a = a A at
    '';
  in
  ...
  services.xserver.displayManager.sessionCommands = "${pkgs.xorg.xmodmap}/bin/xmodmap ${myCustomLayout}";

After a $ nixos rebuild-switch and reboot the changes doesn’t appear to take effect :slight_smile:

This may be because your keyboard sends completely different events, which is probably also why you want to change it in the first place.

Have you tried looking at what it does with xev?

Yep, my keyboard works as spected. When i mean “doesn’t work” i mean the i lost the keycap so i cannot type that particular key

Ah, right. In that case, I assume you’re not using Wayland? What WM/DE are you using? Do these settings work in your DM? If they don’t, you can probably at least see what’s wrong with journalctl -xe --unit display-manager.

As the wiki itself says, that article is outdated: the proper way now is to use xserver.extraLayouts. See this chapter of the NixOS manual.

I’m currently using bspwm under xorg, the ouput of journalctl -xe --unit display-manager does not show anything related to xkb or xmodmap, just some lines about the X server being restarted or stopped

I also tried that way, and it work, the thing with that approach is that i would like to keep everything in my configuration.nix file without the need of external ones

If you mean the symbols file, just use pkgs.writeText.

using it this way doesn’t work:

{ config, pkgs, ...}:

let
    myCustomLayout = pkgs.writeText "xkb-layout" ''
      xkb_symbols "us-custom"
      {
        include "us(basic)"
        include "level3(ralt_switch)"

        key <LatA> { [ a, A, at ] };
        key <LatE> { [ e, E, exclam ] };
      }
    '';
in

{
...

extraLayouts.us-custom = {
    description = "My custom US layout";
    languages = [ "eng" ];
    symbolsFile = myCustomLayout;
};

Have you tried following the bits after the configuration in the manual? The following is given for testing, and should give a bit more info than “not work”:

$ nix-shell -p xorg.xkbcomp
$ setxkbmap -I/yourpath us-greek -print | xkbcomp -I/yourpath - $DISPLAY

Have you tried following the bits after the configuration in the manual?

Yep as i say before it work using the external file, but i would like to use only my configuration.nix

By that I mean, have you tested running it against the version of the file in your nix store, that should have been created by pkgs.mkText? It shouldn’t be hard to find with find, but you can also use pkgs.lib.debug.traceVal to print the path.

So i find the file you mention is inside /nix/store/some-hash-us-custom running

$ nix-shell -p xorg.xkbcomp
$ setxkbmap -I/nix/store us-custom -print | xkbcomp -I/nix/store - $DISPLAY

will fail complaining about not finding the file, as a side note i encounter that my previous example was wrong so i change it to:

But the issue continues :slightly_smiling_face:

Hm, interesting! It turns out setxkbmap demands a full directory structure as input (and there’s no separate flag for symbols, unlike rules). You could create a fake directory structure, but that’d be besides the point of a minimal test of what NixOS actually does.

So let’s look at the implementation.

The config is created in a custom package here, through a rather horrible-looking wrapper derivation.

While the derivation there is fairly complex, it looks like it should set up your configuration correctly, so looking at how this is used we can see an environment variable set by the module a bit further down, which points at the directory that NixOS creates for you - this means we can actually test this rather conveniently like so:

setxkbmap -I"${XKB_CONFIG_ROOT}" us-custom -print | xkbcomp -I"${XKB_CONFIG_ROOT}" - $DISPLAY

Sleuthing through that path and trying out the manual commands should help finding whatever is fishy here, and is probably the quickest way to figure out why this doesn’t work. It’ll likely be something silly like a mis-named file.

If not, it seems that the way this is applied is through the services.xserver.xkbDir setting, which is apparently passed directly to the X binary as a CLI argument - since you’re launching X with a display manager, it in turn will not be managed by systemd, and we indeed don’t expect any useful output from the display manager unit (except listing the arguments of the spawned sub-processes, which is another way of figuring out where the config ended up).

Instead, any errors with this will be reported by Xorg itself - Xorg will create a bunch of log files in /var/log/X.*.log, where there may be more info.

The fishy thing is basically this here, which will cp a store path during the system build. If I recall correctly, that will generally fail on something like ofborg, or anything a bit stricter, but this should work at least locally, so your problem is probably still something else entirely.

The best practice here would probably be to fix the upstream module, and allow passing raw text as well as file paths, which the derivation could then turn into files by piping them into the build tree. It may well be worth writing a patch for that and opening a PR.

since you’re launching X with a display manager…

Actually i’m using displayManager.startx.enable = true once i logged in my session i run startx $(which bspwm) Excuse me if that generate a bit of confusion, i was not aware that displayManager.sessionCommands runs only when you deliver the start of the xserver to the display manager, that’s why i change it to extraLayout.

the above example seems to work

Ah, no, false assumption by me. Nonetheless, with that setting, you should have the xkbdir set correctly.

Can you confirm that that is passed to X correctly by startx (I forgot exactly what logging startx permits, but in the worst case the args should be visible through ps), and check the /var/log/X.*.log files for output regarding the keyboard layout? If that command works properly we just need to figure out why your session isn’t applying it in practice :slight_smile:

it finally works. Turns out i’ve to set the new layout like this:

extraLayouts.us-custom = {
    description = "My custom US layout";
    languages = [ "eng" ];
    symbolsFile = myCustomLayout;
};
layout = "us-custom";

Thanks for your help :stuck_out_tongue_winking_eye:

1 Like

What?! That is state-of-the-art ed hacking :wink:
Unfortunately xkb is not really meant to be configured by end-users, so editing the source and rebuilding is really the only way* to install a custom layout.

* Actually they recently added this, but it has a lot of limitations.

Unless you’re using startx, the X server log should be redirected to the journal (check out displayManager.job.logToJournal), but it’s independent of the display-manager unit. You should be able to read it with journalctl -t xsession.

You probably mean IFD (import from derivation): that is indeed forbidden in the restricted mode used in ofBorg and Hydra, but it’s not related to this. IFD happens when Nix evaluation (“compile time”) depends on the output of a derivation (“runtime”). This is bad because it forces Nix to start building stuff before evaluating. Basically you would have to do something like builtins.readFile or import on a store path. Here the cp line is run during the build of xkeyboardconfig_custom, not at evaluation.

Do you mean with something like heredocs? Those tends to break in unexpected ways because the string may contain the escape sequence (eg. EOF). I rather use pkgs.writeText with an intermediate file because it’s safer.

I’m not sure what @carlos was doing wrong, but this seems to work for me:

{ lib, pkgs, ...}:

{

  console.useXkbConfig = true;

  services.xserver = {
    enable = true;
    displayManager.startx.enable = true;
    windowManager.twm.enable = true;

    layout = "us-custom";
    extraLayouts.us-custom = {
      description = "My custom US layout";
      languages = [ "eng" ];
      symbolsFile = pkgs.writeText "xkb-layout" ''
        xkb_symbols "us-custom"
        {
          include "us(basic)"
          include "level3(ralt_switch)" 

          key <LatA> { [ a, A, at ] };
          key <LatE> { [ e, E, exclam ] };
        };
      '';
    };
  };

}

No offense intended to the author :slight_smile: I understand that nixpkgs often works in spite of upstream not really supporting the use case, so horrible derivations are appreciated. And honestly I thought the user-facing API here is rather nice, given the constraints.

Hmm, I meant something like what home-manager usually does for things like xdg.configFile, though reading this I take it home-manager mostly does it to advertise to people that it’s possible, rather than requiring them to know about pkgs.writeText? I should look at the implementation there more closely. Anyway, I assumed we’re falling into what you call IFD here, but since we’re not clearly none of this is actually a problem.

Oddly enough, my Xorg logs still end up in /var/log, at least using lightdm. I should figure out why, because I actually find it annoying (why not log to systemd if it’s right there, Xorg?).

Anyway, that’s all homework for me. Thanks for clearing up my IFD misconception, and also presumably maintaining the X modules :slight_smile:

1 Like