test: add "how to type" demo program
authorRan Benita <ran@unusedvar.com>
Fri, 20 Mar 2020 16:29:03 +0000 (18:29 +0200)
committerRan Benita <ran234@gmail.com>
Fri, 20 Mar 2020 17:40:14 +0000 (19:40 +0200)
The program takes a unicode codepoint and an RMLVO and prints out all
key + modifier combinations that would result in that codepoint.

The program was written to exercise the new
xkb_keymap_key_get_mods_for_level() function. It's handy and can be
extended in several ways, but enough for now.

Example:

    $ ./build/how-to-type -l us,il,ru 0x41 | column -ts $'\t'
    keysym: A (0x41)
    KEYCODE  KEY NAME  LAYOUT#  LAYOUT NAME   LEVEL#  MODIFIERS
    38       AC01      1        English (US)  2       [ Shift ]
    38       AC01      1        English (US)  2       [ Lock ]
    38       AC01      2        Hebrew        2       [ Shift ]
    38       AC01      2        Hebrew        2       [ Lock ]

    $ ./build/how-to-type -l de -v neo 0x3b6 | column -ts $'\t'
    keysym: Greek_zeta (0x7e6)
    KEYCODE  KEY NAME  LAYOUT#  LAYOUT NAME     LEVEL#  MODIFIERS
    56       AB05      1        German (Neo 2)  4       [ Shift Mod5 ]
    56       AB05      1        German (Neo 2)  4       [ Shift Mod2 Mod3 Mod5 ]
    56       AB05      1        German (Neo 2)  4       [ Shift Lock Mod5 ]
    56       AB05      1        German (Neo 2)  4       [ Lock Mod2 Mod3 Mod5 ]

Signed-off-by: Ran Benita <ran@unusedvar.com>
meson.build
test/how-to-type.c [new file with mode: 0644]

index 6f48c65..465ee48 100644 (file)
@@ -447,6 +447,7 @@ if cc.has_header_symbol('getopt.h', 'getopt_long', prefix: '#define _GNU_SOURCE'
     executable('rmlvo-to-kccgst', 'test/rmlvo-to-kccgst.c', dependencies: test_dep)
     executable('rmlvo-to-keymap', 'test/rmlvo-to-keymap.c', dependencies: test_dep)
     executable('print-compiled-keymap', 'test/print-compiled-keymap.c', dependencies: test_dep)
+    executable('how-to-type', 'test/how-to-type.c', dependencies: test_dep)
 endif
 if cc.has_header('linux/input.h')
     executable('interactive-evdev', 'test/interactive-evdev.c', dependencies: test_dep)
diff --git a/test/how-to-type.c b/test/how-to-type.c
new file mode 100644 (file)
index 0000000..8692033
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * Copyright © 2020 Ran Benita <ran@unusedvar.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <xkbcommon/xkbcommon.h>
+
+static void
+usage(const char *argv0)
+{
+    fprintf(stderr, "Usage: %s [-r <rules>] [-m <model>] "
+            "[-l <layout>] [-v <variant>] [-o <options>] <unicode codepoint>\n",
+            argv0);
+    fprintf(stderr, "Pipe into `column -ts $'\\t'` for nice aligned output.\n");
+    exit(2);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int opt;
+    const char *rules = NULL;
+    const char *model = NULL;
+    const char *layout_ = NULL;
+    const char *variant = NULL;
+    const char *options = NULL;
+    int exit = EXIT_FAILURE;
+    struct xkb_context *ctx;
+    char *endp;
+    long val;
+    uint32_t codepoint;
+    xkb_keysym_t keysym;
+    int ret;
+    char name[200];
+    struct xkb_keymap *keymap;
+    xkb_keycode_t min_keycode, max_keycode;
+    xkb_mod_index_t num_mods;
+
+    while ((opt = getopt(argc, argv, "r:m:l:v:o:")) != -1) {
+        switch (opt) {
+        case 'r':
+            rules = optarg;
+            break;
+        case 'm':
+            model = optarg;
+            break;
+        case 'l':
+            layout_ = optarg;
+            break;
+        case 'v':
+            variant = optarg;
+            break;
+        case 'o':
+            options = optarg;
+            break;
+        default:
+            usage(argv[0]);
+        }
+    }
+    if (argc - optind != 1) {
+        usage(argv[0]);
+    }
+
+    errno = 0;
+    val = strtol(argv[optind], &endp, 0);
+    if (errno != 0 || endp == argv[optind] || val < 0 || val > 0x10FFFF) {
+        fprintf(stderr, "usage: %s <unicode codepoint>\n", argv[0]);
+        goto err_exit;
+    }
+    codepoint = (uint32_t) val;
+
+    keysym = xkb_utf32_to_keysym(codepoint);
+    if (keysym == XKB_KEY_NoSymbol) {
+        fprintf(stderr, "Failed to convert codepoint to keysym");
+        goto err_exit;
+    }
+
+    ret = xkb_keysym_get_name(keysym, name, sizeof(name));
+    if (ret < 0 || (size_t) ret >= sizeof(name)) {
+        fprintf(stderr, "Failed to get name of keysym");
+        goto err_exit;
+    }
+
+    ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+    if (!ctx) {
+        fprintf(stderr, "Failed to create XKB context\n");
+        goto err_exit;
+    }
+
+    struct xkb_rule_names names = {
+        .rules = rules,
+        .model = model,
+        .layout = layout_,
+        .variant = variant,
+        .options = options,
+    };
+    keymap = xkb_keymap_new_from_names(ctx, &names,
+                                       XKB_KEYMAP_COMPILE_NO_FLAGS);
+    if (!keymap) {
+        fprintf(stderr, "Failed to create XKB keymap\n");
+        goto err_ctx;
+    }
+
+    printf("keysym: %s (%#x)\n", name, keysym);
+    printf("KEYCODE\tKEY NAME\tLAYOUT#\tLAYOUT NAME\tLEVEL#\tMODIFIERS\n");
+
+    min_keycode = xkb_keymap_min_keycode(keymap);
+    max_keycode = xkb_keymap_max_keycode(keymap);
+    num_mods = xkb_keymap_num_mods(keymap);
+    for (xkb_keycode_t keycode = min_keycode; keycode <= max_keycode; keycode++) {
+        const char *key_name;
+        xkb_layout_index_t num_layouts;
+
+        key_name = xkb_keymap_key_get_name(keymap, keycode);
+        if (!key_name) {
+            continue;
+        }
+
+        num_layouts = xkb_keymap_num_layouts_for_key(keymap, keycode);
+        for (xkb_layout_index_t layout = 0; layout < num_layouts; layout++) {
+            const char *layout_name;
+            xkb_level_index_t num_levels;
+
+            layout_name = xkb_keymap_layout_get_name(keymap, layout);
+            if (!layout_name) {
+                layout_name = "?";
+            }
+
+            num_levels = xkb_keymap_num_levels_for_key(keymap, keycode, layout);
+            for (xkb_level_index_t level = 0; level < num_levels; level++) {
+                int num_syms;
+                const xkb_keysym_t *syms;
+                size_t num_masks;
+                xkb_mod_mask_t masks[100];
+
+                num_syms = xkb_keymap_key_get_syms_by_level(
+                    keymap, keycode, layout, level, &syms
+                );
+                if (num_syms != 1) {
+                    continue;
+                }
+                if (syms[0] != keysym) {
+                    continue;
+                }
+
+                num_masks = xkb_keymap_key_get_mods_for_level(
+                    keymap, keycode, layout, level, masks, 100
+                );
+                for (size_t i = 0; i < num_masks; i++) {
+                    xkb_mod_mask_t mask = masks[i];
+
+                    printf("%u\t%s\t%u\t%s\t%u\t[ ",
+                           keycode, key_name, layout + 1, layout_name, level + 1);
+                    for (xkb_mod_index_t mod = 0; mod < num_mods; mod++) {
+                        if ((mask & (1 << mod)) == 0) {
+                            continue;
+                        }
+                        printf("%s ", xkb_keymap_mod_get_name(keymap, mod));
+                    }
+                    printf("]\n");
+                }
+            }
+        }
+    }
+
+    exit = EXIT_SUCCESS;
+    xkb_keymap_unref(keymap);
+err_ctx:
+    xkb_context_unref(ctx);
+err_exit:
+    return exit;
+}