summary refs log tree commit diff
path: root/pkgs/tools
diff options
context:
space:
mode:
authoraszlig <aszlig@redmoonstudios.org>2017-07-28 12:36:48 +0200
committeraszlig <aszlig@redmoonstudios.org>2017-07-28 12:39:55 +0200
commit6e5d2f896365fa6737d0c336b7edbc6b6a15fcb9 (patch)
tree62521cd04d1462265a166bc1aa9e0c631f76de2d /pkgs/tools
parent805467bb5a03db607145dd7aae2ef08e1c1ee746 (diff)
downloadnixlib-6e5d2f896365fa6737d0c336b7edbc6b6a15fcb9.tar
nixlib-6e5d2f896365fa6737d0c336b7edbc6b6a15fcb9.tar.gz
nixlib-6e5d2f896365fa6737d0c336b7edbc6b6a15fcb9.tar.bz2
nixlib-6e5d2f896365fa6737d0c336b7edbc6b6a15fcb9.tar.lz
nixlib-6e5d2f896365fa6737d0c336b7edbc6b6a15fcb9.tar.xz
nixlib-6e5d2f896365fa6737d0c336b7edbc6b6a15fcb9.tar.zst
nixlib-6e5d2f896365fa6737d0c336b7edbc6b6a15fcb9.zip
nixos/xserver: Properly validate XKB options
Checking the keyboard layout has been a long set of hurdles so far, with
several attempts. Originally, the checking was introduced by @lheckemann
in #23709.

The initial implementation just was trying to check whether the symbols/
directory contained the layout name.

Unfortunately, that wasn't enough and keyboard variants weren't
recognized, so if you set layout to eg. "dvorak" it will fail with an
error (#25526).

So my improvement on that was to use sed to filter rules/base.lst and
match the layout against that. I fucked up twice with this, first
because layout can be a comma-separated list which I didn't account for
and second because I ran into a Nix issue (NixOS/nix#1426).

After fixing this, it still wasn't enough (and this is btw. what
localectl also does), because we were *only* matching rules but not
symbols, so using "eu" as a layout won't work either.

I decided now it's the time to actually use libxkbcommon to try
compiling the keyboard options and see whether it succeeds. This comes
in the form of a helper tool called xkbvalidate.

IMHO this approach is a lot less error-prone and we can be sure that we
don't forget about anything because that's what the X server itself uses
to compile the keymap.

Another advantage of this is that we now validate the full set of XKB
options rather than just the layout.

Tested this against a variety of wrong and correct keyboard
configurations and against the "keymap" NixOS VM tests.

Signed-off-by: aszlig <aszlig@redmoonstudios.org>
Cc: @lheckemann, @peti, @7c6f434c, @tohl, @vcunat, @lluchs
Fixes: #27597
Diffstat (limited to 'pkgs/tools')
-rw-r--r--pkgs/tools/X11/xkbvalidate/default.nix15
-rw-r--r--pkgs/tools/X11/xkbvalidate/xkbvalidate.c135
2 files changed, 150 insertions, 0 deletions
diff --git a/pkgs/tools/X11/xkbvalidate/default.nix b/pkgs/tools/X11/xkbvalidate/default.nix
new file mode 100644
index 000000000000..f5a264108359
--- /dev/null
+++ b/pkgs/tools/X11/xkbvalidate/default.nix
@@ -0,0 +1,15 @@
+{ lib, runCommandCC, libxkbcommon }:
+
+runCommandCC "xkbvalidate" {
+  buildInputs = [ libxkbcommon ];
+  meta = {
+    description = "NixOS tool to validate X keyboard configuration";
+    license = lib.licenses.mit;
+    platforms = lib.platforms.linux;
+    maintainers = [ lib.maintainers.aszlig ];
+  };
+} ''
+  mkdir -p "$out/bin"
+  gcc -std=gnu11 -Wall -pedantic -lxkbcommon ${./xkbvalidate.c} \
+    -o "$out/bin/validate"
+''
diff --git a/pkgs/tools/X11/xkbvalidate/xkbvalidate.c b/pkgs/tools/X11/xkbvalidate/xkbvalidate.c
new file mode 100644
index 000000000000..d9c9042467c0
--- /dev/null
+++ b/pkgs/tools/X11/xkbvalidate/xkbvalidate.c
@@ -0,0 +1,135 @@
+#define _GNU_SOURCE
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <xkbcommon/xkbcommon.h>
+
+static char **log_buffer = NULL;
+static int log_buffer_size = 0;
+static bool log_alloc_success = true;
+
+static void add_log(struct xkb_context *ctx, enum xkb_log_level level,
+                    const char *fmt, va_list args)
+{
+    log_buffer_size++;
+
+    if (log_buffer == NULL)
+        log_buffer = malloc(sizeof(char *));
+    else
+        log_buffer = realloc(log_buffer, sizeof(char *) * log_buffer_size);
+
+    if (log_buffer == NULL) {
+        perror("buffer alloc");
+        log_alloc_success = false;
+        log_buffer_size--;
+        return;
+    }
+
+    if (vasprintf(&log_buffer[log_buffer_size - 1], fmt, args) == -1) {
+        perror("log line alloc");
+        log_alloc_success = false;
+        return;
+    }
+}
+
+static void print_logs(void)
+{
+    for (int i = 0; i < log_buffer_size; ++i)
+        fprintf(stderr, "    %s", log_buffer[i]);
+}
+
+static void free_logs(void)
+{
+    if (log_buffer == NULL)
+        return;
+    for (int i = 0; i < log_buffer_size; ++i)
+        free(log_buffer[i]);
+    free(log_buffer);
+    log_buffer = NULL;
+    log_buffer_size = 0;
+}
+
+static bool try_keymap(struct xkb_context *ctx, struct xkb_rule_names *rdef)
+{
+    struct xkb_keymap *keymap;
+    bool result = true;
+
+    if ((keymap = xkb_keymap_new_from_names(ctx, rdef, 0)) == NULL)
+        result = false;
+    else
+        xkb_keymap_unref(keymap);
+
+    return result;
+}
+
+static void print_error(const char *name, const char *value,
+                        const char *nixos_option)
+{
+    fprintf(stderr, "\nThe value `%s' for keyboard %s is invalid.\n\n"
+                    "Please check the definition in `services.xserver.%s'.\n",
+            value, name, nixos_option);
+    fputs("\nDetailed XKB compiler errors:\n\n", stderr);
+    print_logs();
+    putc('\n', stderr);
+}
+
+#define TRY_KEYMAP(name, value, nixos_option) \
+    *rdef = (struct xkb_rule_names) {0}; \
+    free_logs(); \
+    rdef->name = value; \
+    result = try_keymap(ctx, rdef); \
+    if (!log_alloc_success) \
+        goto out; \
+    if (!result) { \
+        print_error(#name, value, nixos_option); \
+        exit_code = EXIT_FAILURE; \
+        goto out; \
+    }
+
+int main(int argc, char **argv)
+{
+    int exit_code = EXIT_SUCCESS;
+    bool result;
+    struct xkb_context *ctx;
+    struct xkb_rule_names *rdef;
+
+    if (argc != 5) {
+        fprintf(stderr, "Usage: %s model layout variant options\n", argv[0]);
+        return EXIT_FAILURE;
+    }
+
+    ctx = xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES);
+    xkb_context_set_log_fn(ctx, add_log);
+
+    rdef = malloc(sizeof(struct xkb_rule_names));
+
+    TRY_KEYMAP(model,   argv[1], "xkbModel");
+    TRY_KEYMAP(layout,  argv[2], "layout");
+    TRY_KEYMAP(variant, argv[3], "xkbVariant");
+    TRY_KEYMAP(options, argv[4], "xkbOptions");
+
+    free_logs();
+    rdef->model = argv[1];
+    rdef->layout = argv[2];
+    rdef->variant = argv[3];
+    rdef->options = argv[4];
+
+    result = try_keymap(ctx, rdef);
+    if (!log_alloc_success)
+        goto out;
+
+    if (!result) {
+        fputs("The XKB keyboard definition failed to compile:\n", stderr);
+        print_logs();
+        exit_code = EXIT_FAILURE;
+    }
+
+out:
+    free_logs();
+    free(rdef);
+    xkb_context_unref(ctx);
+    return exit_code;
+}