aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorHaibo Huang <hhb@google.com>2020-09-08 17:10:03 -0700
committerHaibo Huang <hhb@google.com>2020-09-10 22:20:42 +0000
commitbffa8499cb8ce3cc4366055be8fe62d501d6a8e5 (patch)
tree648dfaada5799a6227dd5f1af43d89ed8d71d96d /tools
parente4e474780d90ed6166f7113a7464371baa275007 (diff)
downloadlibxkbcommon-bffa8499cb8ce3cc4366055be8fe62d501d6a8e5.tar.gz
Upgrade libxkbcommon to xkbcommon-1.0.0
1. Run meson build locally: meson config -Denable-x11=false -Denable-wayland=false -Denable-docs=false 2. Copy over generated parser.h / parser.c 3. Copy over config.h, and remove defines for not supported functions. Change-Id: Id7f3c822c1d958fa541685344961507bcfa03b17
Diffstat (limited to 'tools')
-rw-r--r--tools/compile-keymap.c375
-rw-r--r--tools/how-to-type.c224
-rw-r--r--tools/interactive-evdev.c567
-rw-r--r--tools/interactive-wayland.c725
-rw-r--r--tools/interactive-x11.c406
-rw-r--r--tools/registry-list.c225
-rw-r--r--tools/tools-common.c242
-rw-r--r--tools/tools-common.h60
-rw-r--r--tools/xkbcli-compile-keymap.162
-rw-r--r--tools/xkbcli-how-to-type.141
-rw-r--r--tools/xkbcli-interactive-evdev.177
-rw-r--r--tools/xkbcli-interactive-wayland.137
-rw-r--r--tools/xkbcli-interactive-x11.137
-rw-r--r--tools/xkbcli-list.139
-rw-r--r--tools/xkbcli.165
-rw-r--r--tools/xkbcli.c119
16 files changed, 3301 insertions, 0 deletions
diff --git a/tools/compile-keymap.c b/tools/compile-keymap.c
new file mode 100644
index 0000000..d0877a6
--- /dev/null
+++ b/tools/compile-keymap.c
@@ -0,0 +1,375 @@
+/*
+ * Copyright © 2018 Red Hat, Inc.
+ *
+ * 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 <assert.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "xkbcommon/xkbcommon.h"
+#if ENABLE_PRIVATE_APIS
+#include "xkbcomp/xkbcomp-priv.h"
+#include "xkbcomp/rules.h"
+#endif
+#include "tools-common.h"
+
+#define DEFAULT_INCLUDE_PATH_PLACEHOLDER "__defaults__"
+
+static bool verbose = false;
+static enum output_format {
+ FORMAT_RMLVO,
+ FORMAT_KEYMAP,
+ FORMAT_KCCGST,
+ FORMAT_KEYMAP_FROM_XKB,
+} output_format = FORMAT_KEYMAP;
+static const char *includes[64];
+static size_t num_includes = 0;
+
+static void
+usage(char **argv)
+{
+ printf("Usage: %s [OPTIONS]\n"
+ "\n"
+ "Compile the given RMLVO to a keymap and print it\n"
+ "\n"
+ "Options:\n"
+ " --verbose\n"
+ " Enable verbose debugging output\n"
+#if ENABLE_PRIVATE_APIS
+ " --kccgst\n"
+ " Print a keymap which only includes the KcCGST component names instead of the full keymap\n"
+#endif
+ " --rmlvo\n"
+ " Print the full RMLVO with the defaults filled in for missing elements\n"
+ " --from-xkb\n"
+ " Load the XKB file from stdin, ignore RMLVO options.\n"
+#if ENABLE_PRIVATE_APIS
+ " This option must not be used with --kccgst.\n"
+#endif
+ " --include\n"
+ " Add the given path to the include path list. This option is\n"
+ " order-dependent, include paths given first are searched first.\n"
+ " If an include path is given, the default include path list is\n"
+ " not used. Use --include-defaults to add the default include\n"
+ " paths\n"
+ " --include-defaults\n"
+ " Add the default set of include directories.\n"
+ " This option is order-dependent, include paths given first\n"
+ " are searched first.\n"
+ "\n"
+ "XKB-specific options:\n"
+ " --rules <rules>\n"
+ " The XKB ruleset (default: '%s')\n"
+ " --model <model>\n"
+ " The XKB model (default: '%s')\n"
+ " --layout <layout>\n"
+ " The XKB layout (default: '%s')\n"
+ " --variant <variant>\n"
+ " The XKB layout variant (default: '%s')\n"
+ " --options <options>\n"
+ " The XKB options (default: '%s')\n"
+ "\n",
+ argv[0], DEFAULT_XKB_RULES,
+ DEFAULT_XKB_MODEL, DEFAULT_XKB_LAYOUT,
+ DEFAULT_XKB_VARIANT ? DEFAULT_XKB_VARIANT : "<none>",
+ DEFAULT_XKB_OPTIONS ? DEFAULT_XKB_OPTIONS : "<none>");
+}
+
+static bool
+parse_options(int argc, char **argv, struct xkb_rule_names *names)
+{
+ enum options {
+ OPT_VERBOSE,
+ OPT_KCCGST,
+ OPT_RMLVO,
+ OPT_FROM_XKB,
+ OPT_INCLUDE,
+ OPT_INCLUDE_DEFAULTS,
+ OPT_RULES,
+ OPT_MODEL,
+ OPT_LAYOUT,
+ OPT_VARIANT,
+ OPT_OPTION,
+ };
+ static struct option opts[] = {
+ {"help", no_argument, 0, 'h'},
+ {"verbose", no_argument, 0, OPT_VERBOSE},
+#if ENABLE_PRIVATE_APIS
+ {"kccgst", no_argument, 0, OPT_KCCGST},
+#endif
+ {"rmlvo", no_argument, 0, OPT_RMLVO},
+ {"from-xkb", no_argument, 0, OPT_FROM_XKB},
+ {"include", required_argument, 0, OPT_INCLUDE},
+ {"include-defaults", no_argument, 0, OPT_INCLUDE_DEFAULTS},
+ {"rules", required_argument, 0, OPT_RULES},
+ {"model", required_argument, 0, OPT_MODEL},
+ {"layout", required_argument, 0, OPT_LAYOUT},
+ {"variant", required_argument, 0, OPT_VARIANT},
+ {"options", required_argument, 0, OPT_OPTION},
+ {0, 0, 0, 0},
+ };
+
+ while (1) {
+ int c;
+ int option_index = 0;
+ c = getopt_long(argc, argv, "h", opts, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ usage(argv);
+ exit(0);
+ case OPT_VERBOSE:
+ verbose = true;
+ break;
+ case OPT_KCCGST:
+ output_format = FORMAT_KCCGST;
+ break;
+ case OPT_RMLVO:
+ output_format = FORMAT_RMLVO;
+ break;
+ case OPT_FROM_XKB:
+ output_format = FORMAT_KEYMAP_FROM_XKB;
+ break;
+ case OPT_INCLUDE:
+ if (num_includes >= ARRAY_SIZE(includes)) {
+ fprintf(stderr, "error: too many includes\n");
+ exit(EXIT_INVALID_USAGE);
+ }
+ includes[num_includes++] = optarg;
+ break;
+ case OPT_INCLUDE_DEFAULTS:
+ if (num_includes >= ARRAY_SIZE(includes)) {
+ fprintf(stderr, "error: too many includes\n");
+ exit(EXIT_INVALID_USAGE);
+ }
+ includes[num_includes++] = DEFAULT_INCLUDE_PATH_PLACEHOLDER;
+ break;
+ case OPT_RULES:
+ names->rules = optarg;
+ break;
+ case OPT_MODEL:
+ names->model = optarg;
+ break;
+ case OPT_LAYOUT:
+ names->layout = optarg;
+ break;
+ case OPT_VARIANT:
+ names->variant = optarg;
+ break;
+ case OPT_OPTION:
+ names->options = optarg;
+ break;
+ default:
+ usage(argv);
+ exit(EXIT_INVALID_USAGE);
+ }
+
+ }
+
+ return true;
+}
+
+static bool
+print_rmlvo(struct xkb_context *ctx, const struct xkb_rule_names *rmlvo)
+{
+ printf("rules: \"%s\"\nmodel: \"%s\"\nlayout: \"%s\"\nvariant: \"%s\"\noptions: \"%s\"\n",
+ rmlvo->rules, rmlvo->model, rmlvo->layout,
+ rmlvo->variant ? rmlvo->variant : "",
+ rmlvo->options ? rmlvo->options : "");
+ return true;
+}
+
+static bool
+print_kccgst(struct xkb_context *ctx, const struct xkb_rule_names *rmlvo)
+{
+#if ENABLE_PRIVATE_APIS
+ struct xkb_component_names kccgst;
+
+ if (!xkb_components_from_rules(ctx, rmlvo, &kccgst))
+ return false;
+
+ printf("xkb_keymap {\n"
+ " xkb_keycodes { include \"%s\" };\n"
+ " xkb_types { include \"%s\" };\n"
+ " xkb_compat { include \"%s\" };\n"
+ " xkb_symbols { include \"%s\" };\n"
+ "};\n",
+ kccgst.keycodes, kccgst.types, kccgst.compat, kccgst.symbols);
+ free(kccgst.keycodes);
+ free(kccgst.types);
+ free(kccgst.compat);
+ free(kccgst.symbols);
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+static bool
+print_keymap(struct xkb_context *ctx, const struct xkb_rule_names *rmlvo)
+{
+ struct xkb_keymap *keymap;
+
+ keymap = xkb_keymap_new_from_names(ctx, rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS);
+ if (keymap == NULL)
+ return false;
+
+ printf("%s\n", xkb_keymap_get_as_string(keymap,
+ XKB_KEYMAP_FORMAT_TEXT_V1));
+ xkb_keymap_unref(keymap);
+ return true;
+}
+
+static bool
+print_keymap_from_file(struct xkb_context *ctx)
+{
+ struct xkb_keymap *keymap = NULL;
+ char *keymap_string = NULL;
+ FILE *file = NULL;
+ bool success = false;
+
+ file = tmpfile();
+ if (!file) {
+ fprintf(stderr, "Failed to create tmpfile\n");
+ goto out;
+ }
+
+ while (true) {
+ char buf[4096];
+ size_t len;
+
+ len = fread(buf, 1, sizeof(buf), stdin);
+ if (ferror(stdin)) {
+ fprintf(stderr, "Failed to read from stdin\n");
+ goto out;
+ }
+ if (len > 0) {
+ size_t wlen = fwrite(buf, 1, len, file);
+ if (wlen != len) {
+ fprintf(stderr, "Failed to write to tmpfile\n");
+ goto out;
+ }
+ }
+ if (feof(stdin))
+ break;
+ }
+ fseek(file, 0, SEEK_SET);
+ keymap = xkb_keymap_new_from_file(ctx, file,
+ XKB_KEYMAP_FORMAT_TEXT_V1, 0);
+ if (!keymap) {
+ fprintf(stderr, "Couldn't create xkb keymap\n");
+ goto out;
+ }
+
+ keymap_string = xkb_keymap_get_as_string(keymap, XKB_KEYMAP_FORMAT_TEXT_V1);
+ if (!keymap_string) {
+ fprintf(stderr, "Couldn't get the keymap string\n");
+ goto out;
+ }
+
+ fputs(keymap_string, stdout);
+ success = true;
+
+out:
+ if (file)
+ fclose(file);
+ xkb_keymap_unref(keymap);
+ free(keymap_string);
+
+ return success;
+}
+
+int
+main(int argc, char **argv)
+{
+ struct xkb_context *ctx;
+ struct xkb_rule_names names = {
+ .rules = DEFAULT_XKB_RULES,
+ .model = DEFAULT_XKB_MODEL,
+ /* layout and variant are tied together, so we either get user-supplied for
+ * both or default for both, see below */
+ .layout = NULL,
+ .variant = NULL,
+ .options = DEFAULT_XKB_OPTIONS,
+ };
+ int rc = 1;
+
+ if (argc <= 1) {
+ usage(argv);
+ return EXIT_INVALID_USAGE;
+ }
+
+ if (!parse_options(argc, argv, &names))
+ return EXIT_INVALID_USAGE;
+
+ /* Now fill in the layout */
+ if (!names.layout || !*names.layout) {
+ if (names.variant && *names.variant) {
+ fprintf(stderr, "Error: a variant requires a layout\n");
+ return EXIT_INVALID_USAGE;
+ }
+ names.layout = DEFAULT_XKB_LAYOUT;
+ names.variant = DEFAULT_XKB_VARIANT;
+ }
+
+ ctx = xkb_context_new(XKB_CONTEXT_NO_DEFAULT_INCLUDES);
+ assert(ctx);
+
+ if (verbose) {
+ xkb_context_set_log_level(ctx, XKB_LOG_LEVEL_DEBUG);
+ xkb_context_set_log_verbosity(ctx, 10);
+ }
+
+ if (num_includes == 0)
+ includes[num_includes++] = DEFAULT_INCLUDE_PATH_PLACEHOLDER;
+
+ for (size_t i = 0; i < num_includes; i++) {
+ const char *include = includes[i];
+ if (strcmp(include, DEFAULT_INCLUDE_PATH_PLACEHOLDER) == 0)
+ xkb_context_include_path_append_default(ctx);
+ else
+ xkb_context_include_path_append(ctx, include);
+ }
+
+ if (output_format == FORMAT_RMLVO) {
+ rc = print_rmlvo(ctx, &names) ? EXIT_SUCCESS : EXIT_FAILURE;
+ } else if (output_format == FORMAT_KEYMAP) {
+ rc = print_keymap(ctx, &names) ? EXIT_SUCCESS : EXIT_FAILURE;
+ } else if (output_format == FORMAT_KCCGST) {
+ rc = print_kccgst(ctx, &names) ? EXIT_SUCCESS : EXIT_FAILURE;
+ } else if (output_format == FORMAT_KEYMAP_FROM_XKB) {
+ rc = print_keymap_from_file(ctx);
+ }
+
+ xkb_context_unref(ctx);
+
+ return rc;
+}
diff --git a/tools/how-to-type.c b/tools/how-to-type.c
new file mode 100644
index 0000000..903a3ed
--- /dev/null
+++ b/tools/how-to-type.c
@@ -0,0 +1,224 @@
+/*
+ * 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 <getopt.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "xkbcommon/xkbcommon.h"
+
+#define ARRAY_SIZE(arr) ((sizeof(arr) / sizeof(*(arr))))
+
+static void
+usage(const char *argv0, FILE *fp)
+{
+ fprintf(fp, "Usage: %s [--rules <rules>] [--model <model>] "
+ "[--layout <layout>] [--variant <variant>] [--options <options>]"
+ " <unicode codepoint>\n", argv0);
+ fprintf(fp, "Pipe into `column -ts $'\\t'` for nicely aligned output.\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ const char *rules = NULL;
+ const char *model = NULL;
+ const char *layout_ = NULL;
+ const char *variant = NULL;
+ const char *options = NULL;
+ int err = EXIT_FAILURE;
+ struct xkb_context *ctx = NULL;
+ char *endp;
+ long val;
+ uint32_t codepoint;
+ xkb_keysym_t keysym;
+ int ret;
+ char name[200];
+ struct xkb_keymap *keymap = NULL;
+ xkb_keycode_t min_keycode, max_keycode;
+ xkb_mod_index_t num_mods;
+ enum options {
+ OPT_RULES,
+ OPT_MODEL,
+ OPT_LAYOUT,
+ OPT_VARIANT,
+ OPT_OPTIONS,
+ };
+ static struct option opts[] = {
+ {"help", no_argument, 0, 'h'},
+ {"rules", required_argument, 0, OPT_RULES},
+ {"model", required_argument, 0, OPT_MODEL},
+ {"layout", required_argument, 0, OPT_LAYOUT},
+ {"variant", required_argument, 0, OPT_VARIANT},
+ {"options", required_argument, 0, OPT_OPTIONS},
+ {0, 0, 0, 0},
+ };
+
+ while (1) {
+ int opt;
+ int option_index = 0;
+
+ opt = getopt_long(argc, argv, "h", opts, &option_index);
+ if (opt == -1)
+ break;
+
+ switch (opt) {
+ case OPT_RULES:
+ rules = optarg;
+ break;
+ case OPT_MODEL:
+ model = optarg;
+ break;
+ case OPT_LAYOUT:
+ layout_ = optarg;
+ break;
+ case OPT_VARIANT:
+ variant = optarg;
+ break;
+ case OPT_OPTIONS:
+ options = optarg;
+ break;
+ case 'h':
+ usage(argv[0], stdout);
+ exit(EXIT_SUCCESS);
+ default:
+ usage(argv[0], stderr);
+ exit(EXIT_INVALID_USAGE);
+ }
+ }
+ if (argc - optind != 1) {
+ usage(argv[0], stderr);
+ exit(EXIT_INVALID_USAGE);
+ }
+
+ errno = 0;
+ val = strtol(argv[optind], &endp, 0);
+ if (errno != 0 || endp == argv[optind] || val < 0 || val > 0x10FFFF) {
+ usage(argv[0], stderr);
+ exit(EXIT_INVALID_USAGE);
+ }
+ codepoint = (uint32_t) val;
+
+ keysym = xkb_utf32_to_keysym(codepoint);
+ if (keysym == XKB_KEY_NoSymbol) {
+ fprintf(stderr, "Failed to convert codepoint to keysym\n");
+ goto err;
+ }
+
+ 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\n");
+ goto err;
+ }
+
+ ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+ if (!ctx) {
+ fprintf(stderr, "Failed to create XKB context\n");
+ goto err;
+ }
+
+ 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;
+ }
+
+ 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, ARRAY_SIZE(masks)
+ );
+ 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");
+ }
+ }
+ }
+ }
+
+ err = EXIT_SUCCESS;
+err:
+ xkb_keymap_unref(keymap);
+ xkb_context_unref(ctx);
+ return err;
+}
diff --git a/tools/interactive-evdev.c b/tools/interactive-evdev.c
new file mode 100644
index 0000000..9c8c94e
--- /dev/null
+++ b/tools/interactive-evdev.c
@@ -0,0 +1,567 @@
+/*
+ * Copyright © 2012 Ran Benita <ran234@gmail.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 <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <getopt.h>
+#include <limits.h>
+#include <locale.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/epoll.h>
+#include <linux/input.h>
+
+#include "xkbcommon/xkbcommon.h"
+
+#include "tools-common.h"
+
+struct keyboard {
+ char *path;
+ int fd;
+ struct xkb_state *state;
+ struct xkb_compose_state *compose_state;
+ struct keyboard *next;
+};
+
+static bool terminate;
+static int evdev_offset = 8;
+static bool report_state_changes;
+static bool with_compose;
+static enum xkb_consumed_mode consumed_mode = XKB_CONSUMED_MODE_XKB;
+
+#define NLONGS(n) (((n) + LONG_BIT - 1) / LONG_BIT)
+
+static bool
+evdev_bit_is_set(const unsigned long *array, int bit)
+{
+ return array[bit / LONG_BIT] & (1LL << (bit % LONG_BIT));
+}
+
+/* Some heuristics to see if the device is a keyboard. */
+static bool
+is_keyboard(int fd)
+{
+ int i;
+ unsigned long evbits[NLONGS(EV_CNT)] = { 0 };
+ unsigned long keybits[NLONGS(KEY_CNT)] = { 0 };
+
+ errno = 0;
+ ioctl(fd, EVIOCGBIT(0, sizeof(evbits)), evbits);
+ if (errno)
+ return false;
+
+ if (!evdev_bit_is_set(evbits, EV_KEY))
+ return false;
+
+ errno = 0;
+ ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybits)), keybits);
+ if (errno)
+ return false;
+
+ for (i = KEY_RESERVED; i <= KEY_MIN_INTERESTING; i++)
+ if (evdev_bit_is_set(keybits, i))
+ return true;
+
+ return false;
+}
+
+static int
+keyboard_new(struct dirent *ent, struct xkb_keymap *keymap,
+ struct xkb_compose_table *compose_table, struct keyboard **out)
+{
+ int ret;
+ char *path;
+ int fd;
+ struct xkb_state *state;
+ struct xkb_compose_state *compose_state = NULL;
+ struct keyboard *kbd;
+
+ ret = asprintf(&path, "/dev/input/%s", ent->d_name);
+ if (ret < 0)
+ return -ENOMEM;
+
+ fd = open(path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);
+ if (fd < 0) {
+ ret = -errno;
+ goto err_path;
+ }
+
+ if (!is_keyboard(fd)) {
+ /* Dummy "skip this device" value. */
+ ret = -ENOTSUP;
+ goto err_fd;
+ }
+
+ state = xkb_state_new(keymap);
+ if (!state) {
+ fprintf(stderr, "Couldn't create xkb state for %s\n", path);
+ ret = -EFAULT;
+ goto err_fd;
+ }
+
+ if (with_compose) {
+ compose_state = xkb_compose_state_new(compose_table,
+ XKB_COMPOSE_STATE_NO_FLAGS);
+ if (!compose_state) {
+ fprintf(stderr, "Couldn't create compose state for %s\n", path);
+ ret = -EFAULT;
+ goto err_state;
+ }
+ }
+
+ kbd = calloc(1, sizeof(*kbd));
+ if (!kbd) {
+ ret = -ENOMEM;
+ goto err_compose_state;
+ }
+
+ kbd->path = path;
+ kbd->fd = fd;
+ kbd->state = state;
+ kbd->compose_state = compose_state;
+ *out = kbd;
+ return 0;
+
+err_compose_state:
+ xkb_compose_state_unref(compose_state);
+err_state:
+ xkb_state_unref(state);
+err_fd:
+ close(fd);
+err_path:
+ free(path);
+ return ret;
+}
+
+static void
+keyboard_free(struct keyboard *kbd)
+{
+ if (!kbd)
+ return;
+ if (kbd->fd >= 0)
+ close(kbd->fd);
+ free(kbd->path);
+ xkb_state_unref(kbd->state);
+ xkb_compose_state_unref(kbd->compose_state);
+ free(kbd);
+}
+
+static int
+filter_device_name(const struct dirent *ent)
+{
+ return !fnmatch("event*", ent->d_name, 0);
+}
+
+static struct keyboard *
+get_keyboards(struct xkb_keymap *keymap,
+ struct xkb_compose_table *compose_table)
+{
+ int ret, i, nents;
+ struct dirent **ents;
+ struct keyboard *kbds = NULL, *kbd = NULL;
+
+ nents = scandir("/dev/input", &ents, filter_device_name, alphasort);
+ if (nents < 0) {
+ fprintf(stderr, "Couldn't scan /dev/input: %s\n", strerror(errno));
+ return NULL;
+ }
+
+ for (i = 0; i < nents; i++) {
+ ret = keyboard_new(ents[i], keymap, compose_table, &kbd);
+ if (ret) {
+ if (ret == -EACCES) {
+ fprintf(stderr, "Couldn't open /dev/input/%s: %s. "
+ "You probably need root to run this.\n",
+ ents[i]->d_name, strerror(-ret));
+ break;
+ }
+ if (ret != -ENOTSUP) {
+ fprintf(stderr, "Couldn't open /dev/input/%s: %s. Skipping.\n",
+ ents[i]->d_name, strerror(-ret));
+ }
+ continue;
+ }
+
+ assert(kbd != NULL);
+ kbd->next = kbds;
+ kbds = kbd;
+ }
+
+ if (!kbds) {
+ fprintf(stderr, "Couldn't find any keyboards I can use! Quitting.\n");
+ goto err;
+ }
+
+err:
+ for (i = 0; i < nents; i++)
+ free(ents[i]);
+ free(ents);
+ return kbds;
+}
+
+static void
+free_keyboards(struct keyboard *kbds)
+{
+ struct keyboard *next;
+
+ while (kbds) {
+ next = kbds->next;
+ keyboard_free(kbds);
+ kbds = next;
+ }
+}
+
+/* The meaning of the input_event 'value' field. */
+enum {
+ KEY_STATE_RELEASE = 0,
+ KEY_STATE_PRESS = 1,
+ KEY_STATE_REPEAT = 2,
+};
+
+static void
+process_event(struct keyboard *kbd, uint16_t type, uint16_t code, int32_t value)
+{
+ xkb_keycode_t keycode;
+ struct xkb_keymap *keymap;
+ enum xkb_state_component changed;
+ enum xkb_compose_status status;
+
+ if (type != EV_KEY)
+ return;
+
+ keycode = evdev_offset + code;
+ keymap = xkb_state_get_keymap(kbd->state);
+
+ if (value == KEY_STATE_REPEAT && !xkb_keymap_key_repeats(keymap, keycode))
+ return;
+
+ if (with_compose && value != KEY_STATE_RELEASE) {
+ xkb_keysym_t keysym = xkb_state_key_get_one_sym(kbd->state, keycode);
+ xkb_compose_state_feed(kbd->compose_state, keysym);
+ }
+
+ if (value != KEY_STATE_RELEASE)
+ tools_print_keycode_state(kbd->state, kbd->compose_state, keycode,
+ consumed_mode);
+
+ if (with_compose) {
+ status = xkb_compose_state_get_status(kbd->compose_state);
+ if (status == XKB_COMPOSE_CANCELLED || status == XKB_COMPOSE_COMPOSED)
+ xkb_compose_state_reset(kbd->compose_state);
+ }
+
+ if (value == KEY_STATE_RELEASE)
+ changed = xkb_state_update_key(kbd->state, keycode, XKB_KEY_UP);
+ else
+ changed = xkb_state_update_key(kbd->state, keycode, XKB_KEY_DOWN);
+
+ if (report_state_changes)
+ tools_print_state_changes(changed);
+}
+
+static int
+read_keyboard(struct keyboard *kbd)
+{
+ ssize_t len;
+ struct input_event evs[16];
+
+ /* No fancy error checking here. */
+ while ((len = read(kbd->fd, &evs, sizeof(evs))) > 0) {
+ const size_t nevs = len / sizeof(struct input_event);
+ for (size_t i = 0; i < nevs; i++)
+ process_event(kbd, evs[i].type, evs[i].code, evs[i].value);
+ }
+
+ if (len < 0 && errno != EWOULDBLOCK) {
+ fprintf(stderr, "Couldn't read %s: %s\n", kbd->path, strerror(errno));
+ return 1;
+ }
+
+ return 0;
+}
+
+static int
+loop(struct keyboard *kbds)
+{
+ int i, ret = 1;
+ int epfd = -1;
+ struct keyboard *kbd;
+ struct epoll_event ev;
+ struct epoll_event evs[16];
+
+ epfd = epoll_create1(0);
+ if (epfd < 0) {
+ fprintf(stderr, "Couldn't create epoll instance: %s\n",
+ strerror(errno));
+ goto out;
+ }
+
+ for (kbd = kbds; kbd; kbd = kbd->next) {
+ memset(&ev, 0, sizeof(ev));
+ ev.events = EPOLLIN;
+ ev.data.ptr = kbd;
+ ret = epoll_ctl(epfd, EPOLL_CTL_ADD, kbd->fd, &ev);
+ if (ret) {
+ fprintf(stderr, "Couldn't add %s to epoll: %s\n",
+ kbd->path, strerror(errno));
+ goto out;
+ }
+ }
+
+ while (!terminate) {
+ ret = epoll_wait(epfd, evs, 16, -1);
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ fprintf(stderr, "Couldn't poll for events: %s\n",
+ strerror(errno));
+ goto out;
+ }
+
+ for (i = 0; i < ret; i++) {
+ kbd = evs[i].data.ptr;
+ ret = read_keyboard(kbd);
+ if (ret) {
+ goto out;
+ }
+ }
+ }
+
+ ret = 0;
+out:
+ close(epfd);
+ return ret;
+}
+
+static void
+sigintr_handler(int signum)
+{
+ terminate = true;
+}
+
+static void
+usage(FILE *fp, char *progname)
+{
+ fprintf(fp, "Usage: %s [--rules=<rules>] [--model=<model>] "
+ "[--layout=<layout>] [--variant=<variant>] [--options=<options>]\n",
+ progname);
+ fprintf(fp, " or: %s --keymap <path to keymap file>\n",
+ progname);
+ fprintf(fp, "For both:\n"
+ " --report-state-changes (report changes to the state)\n"
+ " --enable-compose (enable Compose)\n"
+ " --consumed-mode={xkb|gtk} (select the consumed modifiers mode, default: xkb)\n"
+ " --without-x11-offset (don't add X11 keycode offset)\n"
+ );
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ret = EXIT_FAILURE;
+ struct keyboard *kbds;
+ struct xkb_context *ctx = NULL;
+ struct xkb_keymap *keymap = NULL;
+ struct xkb_compose_table *compose_table = NULL;
+ const char *rules = NULL;
+ const char *model = NULL;
+ const char *layout = NULL;
+ const char *variant = NULL;
+ const char *options = NULL;
+ const char *keymap_path = NULL;
+ const char *locale;
+ struct sigaction act;
+ enum options {
+ OPT_RULES,
+ OPT_MODEL,
+ OPT_LAYOUT,
+ OPT_VARIANT,
+ OPT_OPTION,
+ OPT_KEYMAP,
+ OPT_WITHOUT_X11_OFFSET,
+ OPT_CONSUMED_MODE,
+ OPT_COMPOSE,
+ OPT_REPORT_STATE,
+ };
+ static struct option opts[] = {
+ {"help", no_argument, 0, 'h'},
+ {"rules", required_argument, 0, OPT_RULES},
+ {"model", required_argument, 0, OPT_MODEL},
+ {"layout", required_argument, 0, OPT_LAYOUT},
+ {"variant", required_argument, 0, OPT_VARIANT},
+ {"options", required_argument, 0, OPT_OPTION},
+ {"keymap", required_argument, 0, OPT_KEYMAP},
+ {"consumed-mode", required_argument, 0, OPT_CONSUMED_MODE},
+ {"enable-compose", no_argument, 0, OPT_COMPOSE},
+ {"report-state-changes", no_argument, 0, OPT_REPORT_STATE},
+ {"without-x11-offset", no_argument, 0, OPT_WITHOUT_X11_OFFSET},
+ {0, 0, 0, 0},
+ };
+
+ setlocale(LC_ALL, "");
+
+ while (1) {
+ int opt;
+ int option_index = 0;
+
+ opt = getopt_long(argc, argv, "h", opts, &option_index);
+ if (opt == -1)
+ break;
+
+ switch (opt) {
+ case OPT_RULES:
+ rules = optarg;
+ break;
+ case OPT_MODEL:
+ model = optarg;
+ break;
+ case OPT_LAYOUT:
+ layout = optarg;
+ break;
+ case OPT_VARIANT:
+ variant = optarg;
+ break;
+ case OPT_OPTION:
+ options = optarg;
+ break;
+ case OPT_KEYMAP:
+ keymap_path = optarg;
+ break;
+ case OPT_WITHOUT_X11_OFFSET:
+ evdev_offset = 0;
+ break;
+ case OPT_REPORT_STATE:
+ report_state_changes = true;
+ break;
+ case OPT_COMPOSE:
+ with_compose = true;
+ break;
+ case OPT_CONSUMED_MODE:
+ if (strcmp(optarg, "gtk") == 0) {
+ consumed_mode = XKB_CONSUMED_MODE_GTK;
+ } else if (strcmp(optarg, "xkb") == 0) {
+ consumed_mode = XKB_CONSUMED_MODE_XKB;
+ } else {
+ fprintf(stderr, "error: invalid --consumed-mode \"%s\"\n", optarg);
+ usage(stderr, argv[0]);
+ return EXIT_INVALID_USAGE;
+ }
+ break;
+ case 'h':
+ usage(stdout, argv[0]);
+ return EXIT_SUCCESS;
+ case '?':
+ usage(stderr, argv[0]);
+ return EXIT_INVALID_USAGE;
+ }
+ }
+
+ ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+ if (!ctx) {
+ fprintf(stderr, "Couldn't create xkb context\n");
+ goto out;
+ }
+
+ if (keymap_path) {
+ FILE *file = fopen(keymap_path, "rb");
+ if (!file) {
+ fprintf(stderr, "Couldn't open '%s': %s\n",
+ keymap_path, strerror(errno));
+ goto out;
+ }
+ keymap = xkb_keymap_new_from_file(ctx, file,
+ XKB_KEYMAP_FORMAT_TEXT_V1,
+ XKB_KEYMAP_COMPILE_NO_FLAGS);
+ fclose(file);
+ }
+ else {
+ struct xkb_rule_names rmlvo = {
+ .rules = (rules == NULL || rules[0] == '\0') ? NULL : rules,
+ .model = (model == NULL || model[0] == '\0') ? NULL : model,
+ .layout = (layout == NULL || layout[0] == '\0') ? NULL : layout,
+ .variant = (variant == NULL || variant[0] == '\0') ? NULL : variant,
+ .options = (options == NULL || options[0] == '\0') ? NULL : options
+ };
+
+ if (!rules && !model && !layout && !variant && !options)
+ keymap = xkb_keymap_new_from_names(ctx, NULL, 0);
+ else
+ keymap = xkb_keymap_new_from_names(ctx, &rmlvo, 0);
+
+ if (!keymap) {
+ fprintf(stderr,
+ "Failed to compile RMLVO: '%s', '%s', '%s', '%s', '%s'\n",
+ rules, model, layout, variant, options);
+ goto out;
+ }
+ }
+
+ if (!keymap) {
+ fprintf(stderr, "Couldn't create xkb keymap\n");
+ goto out;
+ }
+
+ if (with_compose) {
+ locale = setlocale(LC_CTYPE, NULL);
+ compose_table =
+ xkb_compose_table_new_from_locale(ctx, locale,
+ XKB_COMPOSE_COMPILE_NO_FLAGS);
+ if (!compose_table) {
+ fprintf(stderr, "Couldn't create compose from locale\n");
+ goto out;
+ }
+ }
+
+ kbds = get_keyboards(keymap, compose_table);
+ if (!kbds) {
+ goto out;
+ }
+
+ act.sa_handler = sigintr_handler;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ sigaction(SIGINT, &act, NULL);
+ sigaction(SIGTERM, &act, NULL);
+
+ tools_disable_stdin_echo();
+ ret = loop(kbds);
+ tools_enable_stdin_echo();
+
+ free_keyboards(kbds);
+out:
+ xkb_compose_table_unref(compose_table);
+ xkb_keymap_unref(keymap);
+ xkb_context_unref(ctx);
+
+ return ret;
+}
diff --git a/tools/interactive-wayland.c b/tools/interactive-wayland.c
new file mode 100644
index 0000000..3ac7ad7
--- /dev/null
+++ b/tools/interactive-wayland.c
@@ -0,0 +1,725 @@
+/*
+ * Copyright © 2012 Collabora, Ltd.
+ * Copyright © 2013 Ran Benita <ran234@gmail.com>
+ * Copyright © 2016 Daniel Stone <daniel@fooishbar.org>
+ *
+ * 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 <errno.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "xkbcommon/xkbcommon.h"
+#include "tools-common.h"
+
+#include <wayland-client.h>
+#include "xdg-shell-client-protocol.h"
+#include <wayland-util.h>
+
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+struct interactive_dpy {
+ struct wl_display *dpy;
+ struct wl_compositor *compositor;
+ struct xdg_wm_base *shell;
+ struct wl_shm *shm;
+ uint32_t shm_format;
+
+ struct xkb_context *ctx;
+
+ struct wl_surface *wl_surf;
+ struct xdg_surface *xdg_surf;
+ struct xdg_toplevel *xdg_top;
+
+ struct wl_list seats;
+};
+
+struct interactive_seat {
+ struct interactive_dpy *inter;
+
+ struct wl_seat *wl_seat;
+ struct wl_keyboard *wl_kbd;
+ struct wl_pointer *wl_pointer;
+ uint32_t version; /* ... of wl_seat */
+ uint32_t global_name; /* an ID of sorts */
+ char *name_str; /* a descriptor */
+
+ struct xkb_keymap *keymap;
+ struct xkb_state *state;
+
+ struct wl_list link;
+};
+
+static bool terminate;
+
+#ifdef HAVE_MKOSTEMP
+static int
+create_tmpfile_cloexec(char *tmpname)
+{
+ int fd = mkostemp(tmpname, O_CLOEXEC);
+ if (fd >= 0)
+ unlink(tmpname);
+ return fd;
+}
+#else
+/* The following utility functions are taken from Weston's
+ * shared/os-compatibility.c. */
+static int
+os_fd_set_cloexec(int fd)
+{
+ long flags;
+
+ if (fd == -1)
+ return -1;
+
+ flags = fcntl(fd, F_GETFD);
+ if (flags == -1)
+ return -1;
+
+ if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
+ return -1;
+
+ return 0;
+}
+
+static int
+set_cloexec_or_close(int fd)
+{
+ if (os_fd_set_cloexec(fd) != 0) {
+ close(fd);
+ return -1;
+ }
+ return fd;
+}
+
+static int
+create_tmpfile_cloexec(char *tmpname)
+{
+ int fd = mkstemp(tmpname);
+ if (fd >= 0) {
+ fd = set_cloexec_or_close(fd);
+ unlink(tmpname);
+ }
+ return fd;
+}
+#endif
+
+/*
+ * Create a new, unique, anonymous file of the given size, and
+ * return the file descriptor for it. The file descriptor is set
+ * CLOEXEC. The file is immediately suitable for mmap()'ing
+ * the given size at offset zero.
+ *
+ * The file should not have a permanent backing store like a disk,
+ * but may have if XDG_RUNTIME_DIR is not properly implemented in OS.
+ *
+ * The file name is deleted from the file system.
+ *
+ * The file is suitable for buffer sharing between processes by
+ * transmitting the file descriptor over Unix sockets using the
+ * SCM_RIGHTS methods.
+ *
+ * If the C library implements posix_fallocate(), it is used to
+ * guarantee that disk space is available for the file at the
+ * given size. If disk space is insufficent, errno is set to ENOSPC.
+ * If posix_fallocate() is not supported, program may receive
+ * SIGBUS on accessing mmap()'ed file contents instead.
+ */
+static int
+os_create_anonymous_file(off_t size)
+{
+ static const char template[] = "/weston-shared-XXXXXX";
+ const char *path;
+ char *name;
+ int fd;
+ int ret;
+
+ path = getenv("XDG_RUNTIME_DIR");
+ if (!path) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ name = malloc(strlen(path) + sizeof(template));
+ if (!name)
+ return -1;
+
+ strcpy(name, path);
+ strcat(name, template);
+
+ fd = create_tmpfile_cloexec(name);
+
+ free(name);
+
+ if (fd < 0)
+ return -1;
+
+#ifdef HAVE_POSIX_FALLOCATE
+ ret = posix_fallocate(fd, 0, size);
+ if (ret != 0) {
+ close(fd);
+ errno = ret;
+ return -1;
+ }
+#else
+ ret = ftruncate(fd, size);
+ if (ret < 0) {
+ close(fd);
+ return -1;
+ }
+#endif
+
+ return fd;
+}
+
+static void
+buffer_release(void *data, struct wl_buffer *buffer)
+{
+ wl_buffer_destroy(buffer);
+}
+
+static const struct wl_buffer_listener buffer_listener = {
+ buffer_release
+};
+
+static void
+buffer_create(struct interactive_dpy *inter, uint32_t width, uint32_t height)
+{
+ struct wl_shm_pool *pool;
+ struct wl_buffer *buf;
+ struct wl_region *opaque;
+ uint32_t stride;
+ size_t size;
+ void *map;
+ int fd;
+
+ switch (inter->shm_format) {
+ case WL_SHM_FORMAT_ARGB8888:
+ case WL_SHM_FORMAT_XRGB8888:
+ case WL_SHM_FORMAT_ABGR8888:
+ case WL_SHM_FORMAT_XBGR8888:
+ stride = width * 4;
+ break;
+ case WL_SHM_FORMAT_RGB565:
+ case WL_SHM_FORMAT_BGR565:
+ stride = width * 2;
+ break;
+ default:
+ fprintf(stderr, "Unsupported SHM format %d\n", inter->shm_format);
+ exit(1);
+ }
+
+ size = stride * height;
+ fd = os_create_anonymous_file(size);
+ if (fd < 0) {
+ fprintf(stderr, "Couldn't create surface buffer\n");
+ exit(1);
+ }
+
+ map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (map == MAP_FAILED) {
+ fprintf(stderr, "Couldn't mmap surface buffer\n");
+ exit(1);
+ }
+ memset(map, 0xff, size);
+ munmap(map, size);
+
+ pool = wl_shm_create_pool(inter->shm, fd, size);
+ buf = wl_shm_pool_create_buffer(pool, 0, width, height, stride,
+ inter->shm_format);
+ wl_buffer_add_listener(buf, &buffer_listener, inter);
+
+ wl_surface_attach(inter->wl_surf, buf, 0, 0);
+ wl_surface_damage(inter->wl_surf, 0, 0, width, height);
+
+ opaque = wl_compositor_create_region(inter->compositor);
+ wl_region_add(opaque, 0, 0, width, height);
+ wl_surface_set_opaque_region(inter->wl_surf, opaque);
+ wl_region_destroy(opaque);
+
+ wl_shm_pool_destroy(pool);
+ close(fd);
+}
+
+static void
+surface_configure(void *data, struct xdg_surface *surface,
+ uint32_t serial)
+{
+ struct interactive_dpy *inter = data;
+
+ xdg_surface_ack_configure(inter->xdg_surf, serial);
+ wl_surface_commit(inter->wl_surf);
+}
+
+static const struct xdg_surface_listener surface_listener = {
+ surface_configure,
+};
+
+static void
+toplevel_configure(void *data, struct xdg_toplevel *toplevel,
+ int32_t width, int32_t height, struct wl_array *states)
+{
+ struct interactive_dpy *inter = data;
+
+ if (width == 0)
+ width = 200;
+ if (height == 0)
+ height = 200;
+
+ buffer_create(inter, width, height);
+}
+
+static void
+toplevel_close(void *data, struct xdg_toplevel *toplevel)
+{
+ terminate = true;
+}
+
+static const struct xdg_toplevel_listener toplevel_listener = {
+ toplevel_configure,
+ toplevel_close
+};
+
+static void surface_create(struct interactive_dpy *inter)
+{
+ inter->wl_surf = wl_compositor_create_surface(inter->compositor);
+ inter->xdg_surf = xdg_wm_base_get_xdg_surface(inter->shell, inter->wl_surf);
+ xdg_surface_add_listener(inter->xdg_surf, &surface_listener, inter);
+ inter->xdg_top = xdg_surface_get_toplevel(inter->xdg_surf);
+ xdg_toplevel_add_listener(inter->xdg_top, &toplevel_listener, inter);
+ xdg_toplevel_set_title(inter->xdg_top, "xkbcommon event tester");
+ xdg_toplevel_set_app_id(inter->xdg_top,
+ "org.xkbcommon.test.interactive-wayland");
+ wl_surface_commit(inter->wl_surf);
+}
+
+static void
+shell_ping(void *data, struct xdg_wm_base *shell, uint32_t serial)
+{
+ xdg_wm_base_pong(shell, serial);
+}
+
+static const struct xdg_wm_base_listener shell_listener = {
+ shell_ping
+};
+
+static void
+kbd_keymap(void *data, struct wl_keyboard *wl_kbd, uint32_t format,
+ int fd, uint32_t size)
+{
+ struct interactive_seat *seat = data;
+ void *buf;
+
+ buf = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
+ if (buf == MAP_FAILED) {
+ fprintf(stderr, "Failed to mmap keymap: %d\n", errno);
+ close(fd);
+ return;
+ }
+
+ seat->keymap = xkb_keymap_new_from_buffer(seat->inter->ctx, buf, size - 1,
+ XKB_KEYMAP_FORMAT_TEXT_V1,
+ XKB_KEYMAP_COMPILE_NO_FLAGS);
+ munmap(buf, size);
+ close(fd);
+ if (!seat->keymap) {
+ fprintf(stderr, "Failed to compile keymap!\n");
+ return;
+ }
+
+ seat->state = xkb_state_new(seat->keymap);
+ if (!seat->state) {
+ fprintf(stderr, "Failed to create XKB state!\n");
+ return;
+ }
+}
+
+static void
+kbd_enter(void *data, struct wl_keyboard *wl_kbd, uint32_t serial,
+ struct wl_surface *surf, struct wl_array *keys)
+{
+}
+
+static void
+kbd_leave(void *data, struct wl_keyboard *wl_kbd, uint32_t serial,
+ struct wl_surface *surf)
+{
+}
+
+static void
+kbd_key(void *data, struct wl_keyboard *wl_kbd, uint32_t serial, uint32_t time,
+ uint32_t key, uint32_t state)
+{
+ struct interactive_seat *seat = data;
+
+ if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
+ return;
+
+ printf("%s: ", seat->name_str);
+ tools_print_keycode_state(seat->state, NULL, key + 8,
+ XKB_CONSUMED_MODE_XKB);
+
+ /* Exit on ESC. */
+ if (xkb_state_key_get_one_sym(seat->state, key + 8) == XKB_KEY_Escape)
+ terminate = true;
+}
+
+static void
+kbd_modifiers(void *data, struct wl_keyboard *wl_kbd, uint32_t serial,
+ uint32_t mods_depressed, uint32_t mods_latched,
+ uint32_t mods_locked, uint32_t group)
+{
+ struct interactive_seat *seat = data;
+
+ xkb_state_update_mask(seat->state, mods_depressed, mods_latched,
+ mods_locked, 0, 0, group);
+}
+
+static void
+kbd_repeat_info(void *data, struct wl_keyboard *wl_kbd, int32_t rate,
+ int32_t delay)
+{
+}
+
+static const struct wl_keyboard_listener kbd_listener = {
+ kbd_keymap,
+ kbd_enter,
+ kbd_leave,
+ kbd_key,
+ kbd_modifiers,
+ kbd_repeat_info
+};
+
+static void
+pointer_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial,
+ struct wl_surface *surf, wl_fixed_t fsx, wl_fixed_t fsy)
+{
+}
+
+static void
+pointer_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial,
+ struct wl_surface *surf)
+{
+}
+
+static void
+pointer_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time,
+ wl_fixed_t fsx, wl_fixed_t fsy)
+{
+}
+
+static void
+pointer_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial,
+ uint32_t time, uint32_t button, uint32_t state)
+{
+ struct interactive_seat *seat = data;
+
+ xdg_toplevel_move(seat->inter->xdg_top, seat->wl_seat, serial);
+}
+
+static void
+pointer_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time,
+ uint32_t axis, wl_fixed_t value)
+{
+}
+
+static void
+pointer_frame(void *data, struct wl_pointer *wl_pointer)
+{
+}
+
+static void
+pointer_axis_source(void *data, struct wl_pointer *wl_pointer, uint32_t source)
+{
+}
+
+static void
+pointer_axis_stop(void *data, struct wl_pointer *wl_pointer, uint32_t time,
+ uint32_t axis)
+{
+}
+
+static void
+pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer, uint32_t time,
+ int32_t discrete)
+{
+}
+
+static const struct wl_pointer_listener pointer_listener = {
+ pointer_enter,
+ pointer_leave,
+ pointer_motion,
+ pointer_button,
+ pointer_axis,
+ pointer_frame,
+ pointer_axis_source,
+ pointer_axis_stop,
+ pointer_axis_discrete
+};
+
+static void
+seat_capabilities(void *data, struct wl_seat *wl_seat, uint32_t caps)
+{
+ struct interactive_seat *seat = data;
+
+ if (!seat->wl_kbd && (caps & WL_SEAT_CAPABILITY_KEYBOARD)) {
+ seat->wl_kbd = wl_seat_get_keyboard(seat->wl_seat);
+ wl_keyboard_add_listener(seat->wl_kbd, &kbd_listener, seat);
+ }
+ else if (seat->wl_kbd && !(caps & WL_SEAT_CAPABILITY_KEYBOARD)) {
+ if (seat->version >= WL_SEAT_RELEASE_SINCE_VERSION)
+ wl_keyboard_release(seat->wl_kbd);
+ else
+ wl_keyboard_destroy(seat->wl_kbd);
+
+ xkb_state_unref(seat->state);
+ xkb_keymap_unref(seat->keymap);
+
+ seat->state = NULL;
+ seat->keymap = NULL;
+ seat->wl_kbd = NULL;
+ }
+
+ if (!seat->wl_pointer && (caps & WL_SEAT_CAPABILITY_POINTER)) {
+ seat->wl_pointer = wl_seat_get_pointer(seat->wl_seat);
+ wl_pointer_add_listener(seat->wl_pointer, &pointer_listener,
+ seat);
+ }
+ else if (seat->wl_pointer && !(caps & WL_SEAT_CAPABILITY_POINTER)) {
+ if (seat->version >= WL_SEAT_RELEASE_SINCE_VERSION)
+ wl_pointer_release(seat->wl_pointer);
+ else
+ wl_pointer_destroy(seat->wl_pointer);
+ seat->wl_pointer = NULL;
+ }
+}
+
+static void
+seat_name(void *data, struct wl_seat *wl_seat, const char *name)
+{
+ struct interactive_seat *seat = data;
+
+ free(seat->name_str);
+ seat->name_str = strdup(name);
+}
+
+static const struct wl_seat_listener seat_listener = {
+ seat_capabilities,
+ seat_name
+};
+
+static void
+seat_create(struct interactive_dpy *inter, struct wl_registry *registry,
+ uint32_t name, uint32_t version)
+{
+ int ret;
+ struct interactive_seat *seat = calloc(1, sizeof(*seat));
+
+ seat->global_name = name;
+ seat->inter = inter;
+ seat->wl_seat = wl_registry_bind(registry, name, &wl_seat_interface,
+ MAX(version, 5));
+ wl_seat_add_listener(seat->wl_seat, &seat_listener, seat);
+ ret = asprintf(&seat->name_str, "seat:%d",
+ wl_proxy_get_id((struct wl_proxy *) seat->wl_seat));
+ assert(ret >= 0);
+ wl_list_insert(&inter->seats, &seat->link);
+}
+
+static void
+seat_destroy(struct interactive_seat *seat)
+{
+ if (seat->wl_kbd) {
+ if (seat->version >= WL_SEAT_RELEASE_SINCE_VERSION)
+ wl_keyboard_release(seat->wl_kbd);
+ else
+ wl_keyboard_destroy(seat->wl_kbd);
+
+ xkb_state_unref(seat->state);
+ xkb_keymap_unref(seat->keymap);
+ }
+
+ if (seat->wl_pointer) {
+ if (seat->version >= WL_SEAT_RELEASE_SINCE_VERSION)
+ wl_pointer_release(seat->wl_pointer);
+ else
+ wl_pointer_destroy(seat->wl_pointer);
+ }
+
+ if (seat->version >= WL_SEAT_RELEASE_SINCE_VERSION)
+ wl_seat_release(seat->wl_seat);
+ else
+ wl_seat_destroy(seat->wl_seat);
+
+ free(seat->name_str);
+ wl_list_remove(&seat->link);
+ free(seat);
+}
+
+static void
+registry_global(void *data, struct wl_registry *registry, uint32_t name,
+ const char *interface, uint32_t version)
+{
+ struct interactive_dpy *inter = data;
+
+ if (strcmp(interface, "wl_seat") == 0) {
+ seat_create(inter, registry, name, version);
+ }
+ else if (strcmp(interface, "xdg_wm_base") == 0) {
+ inter->shell = wl_registry_bind(registry, name,
+ &xdg_wm_base_interface,
+ MAX(version, 2));
+ xdg_wm_base_add_listener(inter->shell, &shell_listener, inter);
+ }
+ else if (strcmp(interface, "wl_compositor") == 0) {
+ inter->compositor = wl_registry_bind(registry, name,
+ &wl_compositor_interface,
+ MAX(version, 1));
+ }
+ else if (strcmp(interface, "wl_shm") == 0) {
+ inter->shm = wl_registry_bind(registry, name, &wl_shm_interface,
+ MAX(version, 1));
+ }
+}
+
+static void
+registry_delete(void *data, struct wl_registry *registry, uint32_t name)
+{
+ struct interactive_dpy *inter = data;
+ struct interactive_seat *seat, *tmp;
+
+ wl_list_for_each_safe(seat, tmp, &inter->seats, link) {
+ if (seat->global_name != name)
+ continue;
+
+ seat_destroy(seat);
+ }
+}
+
+static const struct wl_registry_listener registry_listener = {
+ registry_global,
+ registry_delete
+};
+
+static void
+dpy_disconnect(struct interactive_dpy *inter)
+{
+ struct interactive_seat *seat, *tmp;
+
+ wl_list_for_each_safe(seat, tmp, &inter->seats, link)
+ seat_destroy(seat);
+
+ if (inter->xdg_surf)
+ xdg_surface_destroy(inter->xdg_surf);
+ if (inter->xdg_top)
+ xdg_toplevel_destroy(inter->xdg_top);
+ if (inter->wl_surf)
+ wl_surface_destroy(inter->wl_surf);
+ if (inter->shell)
+ xdg_wm_base_destroy(inter->shell);
+ if (inter->compositor)
+ wl_compositor_destroy(inter->compositor);
+ if (inter->shm)
+ wl_shm_destroy(inter->shm);
+
+ /* Do one last roundtrip to try to destroy our wl_buffer. */
+ wl_display_roundtrip(inter->dpy);
+
+ xkb_context_unref(inter->ctx);
+ wl_display_disconnect(inter->dpy);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ret;
+ struct interactive_dpy inter;
+ struct wl_registry *registry;
+
+ if (argc != 1) {
+ ret = strcmp(argv[1], "--help");
+ fprintf(ret ? stderr : stdout, "Usage: %s [--help]\n", argv[0]);
+ if (ret)
+ fprintf(stderr, "unrecognized option: %s\n", argv[1]);
+ return ret ? EXIT_INVALID_USAGE : EXIT_SUCCESS;
+ }
+
+ setlocale(LC_ALL, "");
+
+ memset(&inter, 0, sizeof(inter));
+ wl_list_init(&inter.seats);
+
+ inter.dpy = wl_display_connect(NULL);
+ if (!inter.dpy) {
+ fprintf(stderr, "Couldn't connect to Wayland server\n");
+ ret = -1;
+ goto err_out;
+ }
+
+ inter.ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+ if (!inter.ctx) {
+ ret = -1;
+ fprintf(stderr, "Couldn't create xkb context\n");
+ goto err_out;
+ }
+
+ registry = wl_display_get_registry(inter.dpy);
+ wl_registry_add_listener(registry, &registry_listener, &inter);
+
+ /* The first roundtrip gets the list of advertised globals. */
+ wl_display_roundtrip(inter.dpy);
+
+ /* The second roundtrip dispatches the events sent after binding, e.g.
+ * after binding to wl_seat globals in the first roundtrip, we will get
+ * the wl_seat::capabilities event in this roundtrip. */
+ wl_display_roundtrip(inter.dpy);
+
+ if (!inter.shell || !inter.shm || !inter.compositor) {
+ fprintf(stderr, "Required Wayland interfaces %s%s%s unsupported\n",
+ (inter.shell) ? "" : "xdg_shell ",
+ (inter.shm) ? "" : "wl_shm",
+ (inter.compositor) ? "" : "wl_compositor");
+ ret = -1;
+ goto err_conn;
+ }
+
+ surface_create(&inter);
+
+ tools_disable_stdin_echo();
+ do {
+ ret = wl_display_dispatch(inter.dpy);
+ } while (ret >= 0 && !terminate);
+ tools_enable_stdin_echo();
+
+ wl_registry_destroy(registry);
+err_conn:
+ dpy_disconnect(&inter);
+err_out:
+ exit(ret >= 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
diff --git a/tools/interactive-x11.c b/tools/interactive-x11.c
new file mode 100644
index 0000000..c7ca9aa
--- /dev/null
+++ b/tools/interactive-x11.c
@@ -0,0 +1,406 @@
+/*
+ * Copyright © 2013 Ran Benita <ran234@gmail.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 <locale.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <xcb/xkb.h>
+
+#include "xkbcommon/xkbcommon-x11.h"
+#include "tools-common.h"
+
+/*
+ * Note: This program only handles the core keyboard device for now.
+ * It should be straigtforward to change struct keyboard to a list of
+ * keyboards with device IDs, as in tools/interactive-evdev.c. This would
+ * require:
+ *
+ * - Initially listing the keyboard devices.
+ * - Listening to device changes.
+ * - Matching events to their devices.
+ *
+ * XKB itself knows about xinput1 devices, and most requests and events are
+ * device-specific.
+ *
+ * In order to list the devices and react to changes, you need xinput1/2.
+ * You also need xinput for the key press/release event, since the core
+ * protocol key press event does not carry a device ID to match on.
+ */
+
+struct keyboard {
+ xcb_connection_t *conn;
+ uint8_t first_xkb_event;
+ struct xkb_context *ctx;
+
+ struct xkb_keymap *keymap;
+ struct xkb_state *state;
+ int32_t device_id;
+};
+
+static bool terminate;
+
+static int
+select_xkb_events_for_device(xcb_connection_t *conn, int32_t device_id)
+{
+ enum {
+ required_events =
+ (XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY |
+ XCB_XKB_EVENT_TYPE_MAP_NOTIFY |
+ XCB_XKB_EVENT_TYPE_STATE_NOTIFY),
+
+ required_nkn_details =
+ (XCB_XKB_NKN_DETAIL_KEYCODES),
+
+ required_map_parts =
+ (XCB_XKB_MAP_PART_KEY_TYPES |
+ XCB_XKB_MAP_PART_KEY_SYMS |
+ XCB_XKB_MAP_PART_MODIFIER_MAP |
+ XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS |
+ XCB_XKB_MAP_PART_KEY_ACTIONS |
+ XCB_XKB_MAP_PART_VIRTUAL_MODS |
+ XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP),
+
+ required_state_details =
+ (XCB_XKB_STATE_PART_MODIFIER_BASE |
+ XCB_XKB_STATE_PART_MODIFIER_LATCH |
+ XCB_XKB_STATE_PART_MODIFIER_LOCK |
+ XCB_XKB_STATE_PART_GROUP_BASE |
+ XCB_XKB_STATE_PART_GROUP_LATCH |
+ XCB_XKB_STATE_PART_GROUP_LOCK),
+ };
+
+ static const xcb_xkb_select_events_details_t details = {
+ .affectNewKeyboard = required_nkn_details,
+ .newKeyboardDetails = required_nkn_details,
+ .affectState = required_state_details,
+ .stateDetails = required_state_details,
+ };
+
+ xcb_void_cookie_t cookie =
+ xcb_xkb_select_events_aux_checked(conn,
+ device_id,
+ required_events, /* affectWhich */
+ 0, /* clear */
+ 0, /* selectAll */
+ required_map_parts, /* affectMap */
+ required_map_parts, /* map */
+ &details); /* details */
+
+ xcb_generic_error_t *error = xcb_request_check(conn, cookie);
+ if (error) {
+ free(error);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+update_keymap(struct keyboard *kbd)
+{
+ struct xkb_keymap *new_keymap;
+ struct xkb_state *new_state;
+
+ new_keymap = xkb_x11_keymap_new_from_device(kbd->ctx, kbd->conn,
+ kbd->device_id,
+ XKB_KEYMAP_COMPILE_NO_FLAGS);
+ if (!new_keymap)
+ goto err_out;
+
+ new_state = xkb_x11_state_new_from_device(new_keymap, kbd->conn,
+ kbd->device_id);
+ if (!new_state)
+ goto err_keymap;
+
+ if (kbd->keymap)
+ printf("Keymap updated!\n");
+
+ xkb_state_unref(kbd->state);
+ xkb_keymap_unref(kbd->keymap);
+ kbd->keymap = new_keymap;
+ kbd->state = new_state;
+ return 0;
+
+err_keymap:
+ xkb_keymap_unref(new_keymap);
+err_out:
+ return -1;
+}
+
+static int
+init_kbd(struct keyboard *kbd, xcb_connection_t *conn, uint8_t first_xkb_event,
+ int32_t device_id, struct xkb_context *ctx)
+{
+ int ret;
+
+ kbd->conn = conn;
+ kbd->first_xkb_event = first_xkb_event;
+ kbd->ctx = ctx;
+ kbd->keymap = NULL;
+ kbd->state = NULL;
+ kbd->device_id = device_id;
+
+ ret = update_keymap(kbd);
+ if (ret)
+ goto err_out;
+
+ ret = select_xkb_events_for_device(conn, device_id);
+ if (ret)
+ goto err_state;
+
+ return 0;
+
+err_state:
+ xkb_state_unref(kbd->state);
+ xkb_keymap_unref(kbd->keymap);
+err_out:
+ return -1;
+}
+
+static void
+deinit_kbd(struct keyboard *kbd)
+{
+ xkb_state_unref(kbd->state);
+ xkb_keymap_unref(kbd->keymap);
+}
+
+static void
+process_xkb_event(xcb_generic_event_t *gevent, struct keyboard *kbd)
+{
+ union xkb_event {
+ struct {
+ uint8_t response_type;
+ uint8_t xkbType;
+ uint16_t sequence;
+ xcb_timestamp_t time;
+ uint8_t deviceID;
+ } any;
+ xcb_xkb_new_keyboard_notify_event_t new_keyboard_notify;
+ xcb_xkb_map_notify_event_t map_notify;
+ xcb_xkb_state_notify_event_t state_notify;
+ } *event = (union xkb_event *) gevent;
+
+ if (event->any.deviceID != kbd->device_id)
+ return;
+
+ /*
+ * XkbNewKkdNotify and XkbMapNotify together capture all sorts of keymap
+ * updates (e.g. xmodmap, xkbcomp, setxkbmap), with minimal redundent
+ * recompilations.
+ */
+ switch (event->any.xkbType) {
+ case XCB_XKB_NEW_KEYBOARD_NOTIFY:
+ if (event->new_keyboard_notify.changed & XCB_XKB_NKN_DETAIL_KEYCODES)
+ update_keymap(kbd);
+ break;
+
+ case XCB_XKB_MAP_NOTIFY:
+ update_keymap(kbd);
+ break;
+
+ case XCB_XKB_STATE_NOTIFY:
+ xkb_state_update_mask(kbd->state,
+ event->state_notify.baseMods,
+ event->state_notify.latchedMods,
+ event->state_notify.lockedMods,
+ event->state_notify.baseGroup,
+ event->state_notify.latchedGroup,
+ event->state_notify.lockedGroup);
+ break;
+ }
+}
+
+static void
+process_event(xcb_generic_event_t *gevent, struct keyboard *kbd)
+{
+ switch (gevent->response_type) {
+ case XCB_KEY_PRESS: {
+ xcb_key_press_event_t *event = (xcb_key_press_event_t *) gevent;
+ xkb_keycode_t keycode = event->detail;
+
+ tools_print_keycode_state(kbd->state, NULL, keycode,
+ XKB_CONSUMED_MODE_XKB);
+
+ /* Exit on ESC. */
+ if (keycode == 9)
+ terminate = true;
+ break;
+ }
+ default:
+ if (gevent->response_type == kbd->first_xkb_event)
+ process_xkb_event(gevent, kbd);
+ break;
+ }
+}
+
+static int
+loop(xcb_connection_t *conn, struct keyboard *kbd)
+{
+ while (!terminate) {
+ xcb_generic_event_t *event;
+
+ switch (xcb_connection_has_error(conn)) {
+ case 0:
+ break;
+ case XCB_CONN_ERROR:
+ fprintf(stderr,
+ "Closed connection to X server: connection error\n");
+ return -1;
+ case XCB_CONN_CLOSED_EXT_NOTSUPPORTED:
+ fprintf(stderr,
+ "Closed connection to X server: extension not supported\n");
+ return -1;
+ default:
+ fprintf(stderr,
+ "Closed connection to X server: error code %d\n",
+ xcb_connection_has_error(conn));
+ return -1;
+ }
+
+ event = xcb_wait_for_event(conn);
+ if (!event) {
+ continue;
+ }
+
+ process_event(event, kbd);
+ free(event);
+ }
+
+ return 0;
+}
+
+static int
+create_capture_window(xcb_connection_t *conn)
+{
+ xcb_generic_error_t *error;
+ xcb_void_cookie_t cookie;
+ xcb_screen_t *screen =
+ xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
+ xcb_window_t window = xcb_generate_id(conn);
+ uint32_t values[2] = {
+ screen->white_pixel,
+ XCB_EVENT_MASK_KEY_PRESS,
+ };
+
+ cookie = xcb_create_window_checked(conn, XCB_COPY_FROM_PARENT,
+ window, screen->root,
+ 10, 10, 100, 100, 1,
+ XCB_WINDOW_CLASS_INPUT_OUTPUT,
+ screen->root_visual,
+ XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
+ values);
+ if ((error = xcb_request_check(conn, cookie)) != NULL) {
+ free(error);
+ return -1;
+ }
+
+ cookie = xcb_map_window_checked(conn, window);
+ if ((error = xcb_request_check(conn, cookie)) != NULL) {
+ free(error);
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ret;
+ xcb_connection_t *conn;
+ uint8_t first_xkb_event;
+ int32_t core_kbd_device_id;
+ struct xkb_context *ctx;
+ struct keyboard core_kbd;
+
+ if (argc != 1) {
+ ret = strcmp(argv[1], "--help");
+ fprintf(ret ? stderr : stdout, "Usage: %s [--help]\n", argv[0]);
+ if (ret)
+ fprintf(stderr, "unrecognized option: %s\n", argv[1]);
+ return ret ? EXIT_INVALID_USAGE : EXIT_SUCCESS;
+ }
+
+ setlocale(LC_ALL, "");
+
+ conn = xcb_connect(NULL, NULL);
+ if (!conn || xcb_connection_has_error(conn)) {
+ fprintf(stderr, "Couldn't connect to X server: error code %d\n",
+ conn ? xcb_connection_has_error(conn) : -1);
+ ret = -1;
+ goto err_out;
+ }
+
+ ret = xkb_x11_setup_xkb_extension(conn,
+ XKB_X11_MIN_MAJOR_XKB_VERSION,
+ XKB_X11_MIN_MINOR_XKB_VERSION,
+ XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
+ NULL, NULL, &first_xkb_event, NULL);
+ if (!ret) {
+ fprintf(stderr, "Couldn't setup XKB extension\n");
+ goto err_conn;
+ }
+
+ ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+ if (!ctx) {
+ ret = -1;
+ fprintf(stderr, "Couldn't create xkb context\n");
+ goto err_conn;
+ }
+
+ core_kbd_device_id = xkb_x11_get_core_keyboard_device_id(conn);
+ if (core_kbd_device_id == -1) {
+ ret = -1;
+ fprintf(stderr, "Couldn't find core keyboard device\n");
+ goto err_ctx;
+ }
+
+ ret = init_kbd(&core_kbd, conn, first_xkb_event, core_kbd_device_id, ctx);
+ if (ret) {
+ fprintf(stderr, "Couldn't initialize core keyboard device\n");
+ goto err_ctx;
+ }
+
+ ret = create_capture_window(conn);
+ if (ret) {
+ fprintf(stderr, "Couldn't create a capture window\n");
+ goto err_core_kbd;
+ }
+
+ tools_disable_stdin_echo();
+ ret = loop(conn, &core_kbd);
+ tools_enable_stdin_echo();
+
+err_core_kbd:
+ deinit_kbd(&core_kbd);
+err_ctx:
+ xkb_context_unref(ctx);
+err_conn:
+ xcb_disconnect(conn);
+err_out:
+ exit(ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
diff --git a/tools/registry-list.c b/tools/registry-list.c
new file mode 100644
index 0000000..0dc71d1
--- /dev/null
+++ b/tools/registry-list.c
@@ -0,0 +1,225 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * 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 <assert.h>
+#include <stdio.h>
+#include <getopt.h>
+
+#include "xkbcommon/xkbregistry.h"
+
+static void
+usage(const char *progname, FILE *fp)
+{
+ fprintf(fp,
+ "Usage: %s [OPTIONS] [/path/to/xkb_base_directory [/path2]...]\n"
+ "\n"
+ "Options:\n"
+ " --verbose, -v .......... Increase verbosity, use multiple times for debugging output\n"
+ " --ruleset=foo .......... Load the 'foo' ruleset\n"
+ " --skip-default-paths ... Do not load the default XKB paths\n"
+ " --load-exotic .......... Load the exotic (extra) rulesets\n"
+ "\n"
+ "Trailing arguments are treated as XKB base directory installations.\n",
+ progname);
+}
+
+int
+main(int argc, char **argv)
+{
+ int rc = 1;
+ struct rxkb_context *ctx = NULL;
+ struct rxkb_model *m;
+ struct rxkb_layout *l;
+ struct rxkb_option_group *g;
+ enum rxkb_context_flags flags = RXKB_CONTEXT_NO_FLAGS;
+ bool load_defaults = true;
+ int verbosity = 0;
+ const char *ruleset = DEFAULT_XKB_RULES;
+
+ static const struct option opts[] = {
+ {"help", no_argument, 0, 'h'},
+ {"verbose", no_argument, 0, 'v'},
+ {"load-exotic", no_argument, 0, 'e'},
+ {"skip-default-paths", no_argument, 0, 'd'},
+ {"ruleset", required_argument, 0, 'r'},
+ {0, 0, 0, 0},
+ };
+
+ while (1) {
+ int c;
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "hev", opts, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ usage(argv[0], stdout);
+ return 0;
+ case '?':
+ usage(argv[0], stderr);
+ return EXIT_INVALID_USAGE;
+ case 'd':
+ load_defaults = false;
+ break;
+ case 'e':
+ flags |= RXKB_CONTEXT_LOAD_EXOTIC_RULES;
+ break;
+ case 'r':
+ ruleset = optarg;
+ break;
+ case 'v':
+ verbosity++;
+ break;
+ }
+ }
+
+ if (optind < argc)
+ flags |= RXKB_CONTEXT_NO_DEFAULT_INCLUDES;
+
+ ctx = rxkb_context_new(flags);
+ assert(ctx);
+
+ switch (verbosity) {
+ case 0:
+ rxkb_context_set_log_level(ctx, RXKB_LOG_LEVEL_ERROR);
+ break;
+ case 1:
+ rxkb_context_set_log_level(ctx, RXKB_LOG_LEVEL_INFO);
+ break;
+ default:
+ rxkb_context_set_log_level(ctx, RXKB_LOG_LEVEL_DEBUG);
+ break;
+ }
+
+ if (optind < argc) {
+ for (int i = optind; i < argc; i++) {
+ if (!rxkb_context_include_path_append(ctx, argv[i])) {
+ fprintf(stderr, "Failed to append include path '%s'\n",
+ argv[i]);
+ goto err;
+ }
+ }
+
+ if (load_defaults) {
+ if (!rxkb_context_include_path_append_default(ctx)) {
+ fprintf(stderr, "Failed to include default paths.\n");
+ goto err;
+ }
+ }
+ }
+ if (!rxkb_context_parse(ctx, ruleset)) {
+ fprintf(stderr, "Failed to parse XKB descriptions.\n");
+ goto err;
+ }
+
+ printf("Models:\n");
+ m = rxkb_model_first(ctx);
+ assert(m); /* Empty model list is usually a bug or a bad xml file */
+ while (m) {
+ printf("- %s:%s:%s\n",
+ rxkb_model_get_name(m),
+ rxkb_model_get_vendor(m),
+ rxkb_model_get_description(m));
+ m = rxkb_model_next(m);
+ }
+
+ printf("\n");
+ printf("Layouts:\n");
+ l = rxkb_layout_first(ctx);
+ assert(l); /* Empty layout list is usually a bug or a bad xml file */
+ while (l) {
+ struct rxkb_iso639_code *iso639;
+ struct rxkb_iso3166_code *iso3166;
+ const char *variant = rxkb_layout_get_variant(l);
+ const char *brief = rxkb_layout_get_brief(l);
+ bool first;
+
+ printf("- %s%s%s%s:%s:%s",
+ rxkb_layout_get_name(l),
+ variant ? "(" : "",
+ variant ? variant : "",
+ variant ? ")" : "",
+ brief ? brief : "",
+ rxkb_layout_get_description(l));
+
+ iso639 = rxkb_layout_get_iso639_first(l);
+ if (iso639)
+ printf(":iso639-");
+ first = true;
+ while (iso639) {
+ printf("%s%s", first ? "" : ",", rxkb_iso639_code_get_code(iso639));
+ iso639 = rxkb_iso639_code_next(iso639);
+ first = false;
+ }
+ iso3166 = rxkb_layout_get_iso3166_first(l);
+ if (iso3166)
+ printf(":iso3166-");
+ first = true;
+ while (iso3166) {
+ printf("%s%s", first ? "" : ",", rxkb_iso3166_code_get_code(iso3166));
+ iso3166 = rxkb_iso3166_code_next(iso3166);
+ first = false;
+ }
+
+ printf("\n");
+ l = rxkb_layout_next(l);
+ }
+ printf("\n");
+ printf("Options:\n");
+ g = rxkb_option_group_first(ctx);
+ assert(g); /* Empty option goups list is usually a bug or a bad xml file */
+ while (g) {
+ struct rxkb_option *o;
+
+ printf("- %s:%s (%s)\n",
+ rxkb_option_group_get_name(g),
+ rxkb_option_group_get_description(g),
+ rxkb_option_group_allows_multiple(g) ? "multiple" : "single");
+
+ o = rxkb_option_first(g);
+ assert(o); /* Empty option list is usually a bug or a bad xml file */
+ while (o) {
+ const char *brief = rxkb_option_get_brief(o);
+
+ printf(" - %s:%s:%s\n",
+ rxkb_option_get_name(o),
+ brief ? brief : "",
+ rxkb_option_get_description(o));
+ o = rxkb_option_next(o);
+ }
+
+ g = rxkb_option_group_next(g);
+ }
+
+ rc = 0;
+
+err:
+ if (ctx)
+ rxkb_context_unref(ctx);
+
+ return rc;
+}
diff --git a/tools/tools-common.c b/tools/tools-common.c
new file mode 100644
index 0000000..254499d
--- /dev/null
+++ b/tools/tools-common.c
@@ -0,0 +1,242 @@
+/*
+ * Copyright © 2009 Dan Nicholson <dbn.lists@gmail.com>
+ * Copyright © 2012 Intel Corporation
+ * Copyright © 2012 Ran Benita <ran234@gmail.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 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 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.
+ *
+ * Except as contained in this notice, the names of the authors or their
+ * institutions shall not be used in advertising or otherwise to promote the
+ * sale, use or other dealings in this Software without prior written
+ * authorization from the authors.
+ *
+ * Author: Dan Nicholson <dbn.lists@gmail.com>
+ * Daniel Stone <daniel@fooishbar.org>
+ * Ran Benita <ran234@gmail.com>
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef _MSC_VER
+#include <io.h>
+#include <windows.h>
+#else
+#include <unistd.h>
+#include <termios.h>
+#endif
+
+#include "tools-common.h"
+
+void
+tools_print_keycode_state(struct xkb_state *state,
+ struct xkb_compose_state *compose_state,
+ xkb_keycode_t keycode,
+ enum xkb_consumed_mode consumed_mode)
+{
+ struct xkb_keymap *keymap;
+
+ xkb_keysym_t sym;
+ const xkb_keysym_t *syms;
+ int nsyms;
+ char s[16];
+ xkb_layout_index_t layout;
+ enum xkb_compose_status status;
+
+ keymap = xkb_state_get_keymap(state);
+
+ nsyms = xkb_state_key_get_syms(state, keycode, &syms);
+
+ if (nsyms <= 0)
+ return;
+
+ status = XKB_COMPOSE_NOTHING;
+ if (compose_state)
+ status = xkb_compose_state_get_status(compose_state);
+
+ if (status == XKB_COMPOSE_COMPOSING || status == XKB_COMPOSE_CANCELLED)
+ return;
+
+ if (status == XKB_COMPOSE_COMPOSED) {
+ sym = xkb_compose_state_get_one_sym(compose_state);
+ syms = &sym;
+ nsyms = 1;
+ }
+ else if (nsyms == 1) {
+ sym = xkb_state_key_get_one_sym(state, keycode);
+ syms = &sym;
+ }
+
+ printf("keysyms [ ");
+ for (int i = 0; i < nsyms; i++) {
+ xkb_keysym_get_name(syms[i], s, sizeof(s));
+ printf("%-*s ", (int) sizeof(s), s);
+ }
+ printf("] ");
+
+ if (status == XKB_COMPOSE_COMPOSED)
+ xkb_compose_state_get_utf8(compose_state, s, sizeof(s));
+ else
+ xkb_state_key_get_utf8(state, keycode, s, sizeof(s));
+ printf("unicode [ %s ] ", s);
+
+ layout = xkb_state_key_get_layout(state, keycode);
+ printf("layout [ %s (%d) ] ",
+ xkb_keymap_layout_get_name(keymap, layout), layout);
+
+ printf("level [ %d ] ",
+ xkb_state_key_get_level(state, keycode, layout));
+
+ printf("mods [ ");
+ for (xkb_mod_index_t mod = 0; mod < xkb_keymap_num_mods(keymap); mod++) {
+ if (xkb_state_mod_index_is_active(state, mod,
+ XKB_STATE_MODS_EFFECTIVE) <= 0)
+ continue;
+ if (xkb_state_mod_index_is_consumed2(state, keycode, mod,
+ consumed_mode))
+ printf("-%s ", xkb_keymap_mod_get_name(keymap, mod));
+ else
+ printf("%s ", xkb_keymap_mod_get_name(keymap, mod));
+ }
+ printf("] ");
+
+ printf("leds [ ");
+ for (xkb_led_index_t led = 0; led < xkb_keymap_num_leds(keymap); led++) {
+ if (xkb_state_led_index_is_active(state, led) <= 0)
+ continue;
+ printf("%s ", xkb_keymap_led_get_name(keymap, led));
+ }
+ printf("] ");
+
+ printf("\n");
+}
+
+void
+tools_print_state_changes(enum xkb_state_component changed)
+{
+ if (changed == 0)
+ return;
+
+ printf("changed [ ");
+ if (changed & XKB_STATE_LAYOUT_EFFECTIVE)
+ printf("effective-layout ");
+ if (changed & XKB_STATE_LAYOUT_DEPRESSED)
+ printf("depressed-layout ");
+ if (changed & XKB_STATE_LAYOUT_LATCHED)
+ printf("latched-layout ");
+ if (changed & XKB_STATE_LAYOUT_LOCKED)
+ printf("locked-layout ");
+ if (changed & XKB_STATE_MODS_EFFECTIVE)
+ printf("effective-mods ");
+ if (changed & XKB_STATE_MODS_DEPRESSED)
+ printf("depressed-mods ");
+ if (changed & XKB_STATE_MODS_LATCHED)
+ printf("latched-mods ");
+ if (changed & XKB_STATE_MODS_LOCKED)
+ printf("locked-mods ");
+ if (changed & XKB_STATE_LEDS)
+ printf("leds ");
+ printf("]\n");
+}
+
+#ifdef _MSC_VER
+void
+tools_disable_stdin_echo(void)
+{
+ HANDLE stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
+ DWORD mode = 0;
+ GetConsoleMode(stdin_handle, &mode);
+ SetConsoleMode(stdin_handle, mode & ~ENABLE_ECHO_INPUT);
+}
+
+void
+tools_enable_stdin_echo(void)
+{
+ HANDLE stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
+ DWORD mode = 0;
+ GetConsoleMode(stdin_handle, &mode);
+ SetConsoleMode(stdin_handle, mode | ENABLE_ECHO_INPUT);
+}
+#else
+void
+tools_disable_stdin_echo(void)
+{
+ /* Same as `stty -echo`. */
+ struct termios termios;
+ if (tcgetattr(STDIN_FILENO, &termios) == 0) {
+ termios.c_lflag &= ~ECHO;
+ (void) tcsetattr(STDIN_FILENO, TCSADRAIN, &termios);
+ }
+}
+
+void
+tools_enable_stdin_echo(void)
+{
+ /* Same as `stty echo`. */
+ struct termios termios;
+ if (tcgetattr(STDIN_FILENO, &termios) == 0) {
+ termios.c_lflag |= ECHO;
+ (void) tcsetattr(STDIN_FILENO, TCSADRAIN, &termios);
+ }
+}
+
+#endif
+
+int
+tools_exec_command(const char *prefix, int real_argc, char **real_argv)
+{
+ char *argv[64] = {NULL};
+ char executable[PATH_MAX];
+ const char *command;
+ int rc;
+
+ if (((size_t)real_argc >= ARRAY_SIZE(argv))) {
+ fprintf(stderr, "Too many arguments\n");
+ return EXIT_INVALID_USAGE;
+ }
+
+ command = real_argv[0];
+
+ rc = snprintf(executable, sizeof(executable),
+ "%s/%s-%s", LIBXKBCOMMON_TOOL_PATH, prefix, command);
+ if (rc < 0 || (size_t) rc >= sizeof(executable)) {
+ fprintf(stderr, "Failed to assemble command\n");
+ return EXIT_FAILURE;
+ }
+
+ argv[0] = executable;
+ for (int i = 1; i < real_argc; i++)
+ argv[i] = real_argv[i];
+
+ execv(executable, argv);
+ if (errno == ENOENT) {
+ fprintf(stderr, "Command '%s' is not available\n", command);
+ return EXIT_INVALID_USAGE;
+ } else {
+ fprintf(stderr, "Failed to execute '%s' (%s)\n",
+ command, strerror(errno));
+ }
+
+ return EXIT_FAILURE;
+}
diff --git a/tools/tools-common.h b/tools/tools-common.h
new file mode 100644
index 0000000..780720a
--- /dev/null
+++ b/tools/tools-common.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright © 2012 Intel Corporation
+ *
+ * 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.
+ *
+ * Author: Daniel Stone <daniel@fooishbar.org>
+ */
+
+#pragma once
+
+#include "config.h"
+
+#include <assert.h>
+
+/* Don't use compat names in internal code. */
+#define _XKBCOMMON_COMPAT_H
+#include "xkbcommon/xkbcommon.h"
+#include "xkbcommon/xkbcommon-compose.h"
+
+#define ARRAY_SIZE(arr) ((sizeof(arr) / sizeof(*(arr))))
+
+void
+tools_print_keycode_state(struct xkb_state *state,
+ struct xkb_compose_state *compose_state,
+ xkb_keycode_t keycode,
+ enum xkb_consumed_mode consumed_mode);
+
+void
+tools_print_state_changes(enum xkb_state_component changed);
+
+void
+tools_disable_stdin_echo(void);
+
+void
+tools_enable_stdin_echo(void);
+
+int
+tools_exec_command(const char *prefix, int argc, char **argv);
+
+#ifdef _MSC_VER
+#define setenv(varname, value, overwrite) _putenv_s((varname), (value))
+#define unsetenv(varname) _putenv_s(varname, "")
+#endif
diff --git a/tools/xkbcli-compile-keymap.1 b/tools/xkbcli-compile-keymap.1
new file mode 100644
index 0000000..4246512
--- /dev/null
+++ b/tools/xkbcli-compile-keymap.1
@@ -0,0 +1,62 @@
+.Dd July 27, 2020
+.Dt XKBCLI\-COMPILE\-KEYMAP 1
+.Os
+.
+.Sh NAME
+.Nm "xkbcli compile\-keymap"
+.Nd compile an XKB keymap
+.
+.Sh SYNOPSIS
+.Nm
+.Op Ar options
+.
+.Sh DESCRIPTION
+.Nm
+compiles and prints a keymap based on the given options.
+.
+.Bl -tag -width Ds
+.It Fl \-help
+Print help and exit
+.
+.It Fl \-verbose
+Enable verbose debugging output
+.
+.It Fl \-rmlvo
+Print the full RMLVO with the defaults filled in for missing elements
+.
+.It Fl \-from\-xkb
+Load the XKB file from stdin, ignore RMLVO options.
+This option must not be used with
+.Fl \-kccgst .
+.
+.It Fl \-include Ar PATH
+Add the given path to the include path list.
+This option is order\-dependent, include paths given first are searched first.
+If an include path is given, the default include path list is not used.
+Use
+.Fl -\-include\-defaults
+to add the default include paths.
+.
+.It Fl \-include\-defaults
+Add the default set of include directories.
+This option is order-dependent, include paths given first are searched first.
+.
+.It Fl \-rules Ar rules
+The XKB ruleset
+.
+.It Fl \-model Ar model
+The XKB model
+.
+.It Fl \-layout Ar layout
+The XKB layout
+.
+.It Fl \-variant Ar variant
+The XKB layout variant
+.
+.It Fl \-options Ar options
+The XKB options
+.El
+.
+.Sh SEE ALSO
+.Xr xkbcli 1 ,
+.Lk https://xkbcommon.org "The libxkbcommon online documentation"
diff --git a/tools/xkbcli-how-to-type.1 b/tools/xkbcli-how-to-type.1
new file mode 100644
index 0000000..a697e20
--- /dev/null
+++ b/tools/xkbcli-how-to-type.1
@@ -0,0 +1,41 @@
+.Dd July 27, 2020
+.Dt XKBCLI\-HOW\-TO\-TYPE 1
+.Os
+.
+.Sh NAME
+.Nm "xkbcli how\-to\-type"
+.Nd query how to type a given Unicode codepoint
+.
+.Sh SYNOPSIS
+.Nm
+.Op options
+.Ar codepoint
+.
+.Sh DESCRIPTION
+.Nm
+prints key sequences to type the given Unicode codepoint.
+.Pp
+Pipe into
+.Dq "column \-ts $\'\e\et\'"
+for nicely aligned output.
+.
+.Bl -tag -width Ds
+.It Fl \-rules Ar rules
+The XKB ruleset
+.
+.It Fl \-model Ar model
+The XKB model
+.
+.It Fl \-layout Ar layout
+The XKB layout
+.
+.It Fl \-variant Ar variant
+The XKB layout variant
+.
+.It Fl \-options Ar options
+The XKB options
+.El
+.
+.Sh SEE ALSO
+.Xr xkbcli 1 ,
+.Lk https://xkbcommon.org "The libxkbcommon online documentation"
diff --git a/tools/xkbcli-interactive-evdev.1 b/tools/xkbcli-interactive-evdev.1
new file mode 100644
index 0000000..be1b70f
--- /dev/null
+++ b/tools/xkbcli-interactive-evdev.1
@@ -0,0 +1,77 @@
+.Dd July 27, 2020
+.Dt XKBCLI\-INTERACTIVE\-EVDEV 1
+.Os
+.
+.Sh NAME
+.Nm "xkbcli interactive\-evdev"
+.Nd interactive debugger for XKB keymaps
+.
+.Sh SYNOPSIS
+.Nm
+.Op Ar options
+.
+.Sh DESCRIPTION
+.Nm
+is a commandline tool to interactively debug XKB keymaps by listening to
+.Pa /dev/input/eventX
+evdev devices (Linux).
+.
+.Pp
+.Nm
+requires permission to open the evdev device nodes.
+This usually requires being the
+.Dq root
+user or belonging to the
+.Dq input
+group.
+.
+.Pp
+Press the
+.Aq Escape
+key to exit.
+.
+.Pp
+This is a debugging tool, its behavior or output is not guaranteed to be stable.
+.
+.Bl -tag -width Ds
+.It Fl \-help
+Print help and exit
+.
+.It Fl \-rules Ar rules
+The XKB ruleset
+.
+.It Fl \-model Ar model
+The XKB model
+.
+.It Fl \-layout Ar layout
+The XKB layout
+.
+.It Fl \-variant Ar variant
+The XKB layout variant
+.
+.It Fl \-option Ar options
+The XKB options
+.
+.It Fl \-keymap Ar file
+Specify a keymap path.
+This option is mutually exclusive with the RMLVO options.
+.
+.It Fl \-report\-state\-changes
+Report changes to the keyboard state
+.
+.It Fl \-enable\-compose
+Enable Compose functionality
+.
+.It Fl \-consumed\-mode Brq xkb|gtk
+Set the consumed modifiers mode (default: xkb)
+.
+.It Fl \-without\-x11\-offset
+Don't add an offset of 8 when converting an evdev keycode to an XKB keycode.
+You probably don't want this option.
+.El
+.
+.Sh SEE ALSO
+.Xr xkbcli 1 ,
+.Xr xkbcli\-interactive\-wayland 1 ,
+.Xr xkbcli\-interactive\-x11 1 ,
+.Lk https://xkbcommon.org "The libxkbcommon online documentation"
diff --git a/tools/xkbcli-interactive-wayland.1 b/tools/xkbcli-interactive-wayland.1
new file mode 100644
index 0000000..d9ba1de
--- /dev/null
+++ b/tools/xkbcli-interactive-wayland.1
@@ -0,0 +1,37 @@
+.Dd July 27, 2020
+.Dt XKBCLI\-INTERACTIVE\-WAYLAND 1
+.Os
+.
+.Sh NAME
+.Nm "xkbcli interactive\-wayland"
+.Nd interactive debugger for XKB keymaps
+.
+.Sh SYNOPSIS
+.Nm
+.Op Ar options
+.
+.Sh DESCRIPTION
+.Nm
+is a commandline tool to interactively debug XKB keymaps by listening to Wayland events.
+.
+.Pp
+This requires a Wayland compositor to be running.
+.
+.Pp
+Press the
+.Aq Escape
+key to exit.
+.
+.Pp
+This is a debugging tool, its behavior or output is not guaranteed to be stable.
+.
+.Bl -tag -width Ds
+.It Fl \-help
+Print help and exit
+.El
+.
+.Sh SEE ALSO
+.Xr xkbcli 1 ,
+.Xr xkbcli\-interactive\-evdev 1 ,
+.Xr xkbcli\-interactive\-x11 1 ,
+.Lk https://xkbcommon.org "The libxkbcommon online documentation"
diff --git a/tools/xkbcli-interactive-x11.1 b/tools/xkbcli-interactive-x11.1
new file mode 100644
index 0000000..0f16f3f
--- /dev/null
+++ b/tools/xkbcli-interactive-x11.1
@@ -0,0 +1,37 @@
+.Dd July 27, 2020
+.Dt XKBCLI\-INTERACTIVE\-X11 1
+.Os
+.
+.Sh NAME
+.Nm "xkbcli interactive\-x11"
+.Nd interactive debugger for XKB keymaps
+.
+.Sh SYNOPSIS
+.Nm
+.Op Ar options
+.
+.Sh DESCRIPTION
+.Nm
+is a commandline tool to interactively debug XKB keymaps by listening to X11 events.
+.
+.Pp
+This requires an X server to be running.
+.
+.Pp
+Press the
+.Aq Escape
+key to exit.
+.
+.Pp
+This is a debugging tool, its behavior or output is not guaranteed to be stable.
+.
+.Bl -tag -width Ds
+.It Fl \-help
+Print help and exit
+.El
+.
+.Sh SEE ALSO
+.Xr xkbcli 1 ,
+.Xr xkbcli\-interactive\-evdev 1 ,
+.Xr xkbcli\-interactive\-wayland 1 ,
+.Lk https://xkbcommon.org "The libxkbcommon online documentation"
diff --git a/tools/xkbcli-list.1 b/tools/xkbcli-list.1
new file mode 100644
index 0000000..a72477b
--- /dev/null
+++ b/tools/xkbcli-list.1
@@ -0,0 +1,39 @@
+.Dd July 27, 2020
+.Dt XKBCLI\-LIST 1
+.Os
+.
+.Sh NAME
+.Nm "xkbcli list"
+.Nd list available XKB models, layouts, variants and options
+.
+.Sh SYNOPSIS
+.Nm
+.Op Pa /path/to/xkbbase Oo Pa /path/to/xkbbase Oc ...
+.
+.Sh DESCRIPTION
+.Nm
+is a commandline tool to list available model, layout, variant and option (MLVO) values from the XKB registry.
+.
+.Pp
+Positional arguments provided on the commandline are treated as XKB base directory installations.
+.
+.Bl -tag -width Ds
+.It Fl \-help
+Print help and exit
+.
+.It Fl \-verbose
+Increase verbosity, use multiple times for debugging output
+.
+.It Fl \-ruleset Ar name
+Load the ruleset with the given name
+.
+.It Fl \-skip\-default\-paths
+Do not load the default XKB include paths
+.
+.It Fl \-load\-exotic
+Load exotic (extra) layouts
+.El
+.
+.Sh SEE ALSO
+.Xr xkbcli 1 ,
+.Lk https://xkbcommon.org "The libxkbcommon online documentation"
diff --git a/tools/xkbcli.1 b/tools/xkbcli.1
new file mode 100644
index 0000000..9ea0416
--- /dev/null
+++ b/tools/xkbcli.1
@@ -0,0 +1,65 @@
+.Dd July 27, 2020
+.Dt XKBCLI 1
+.Os
+.
+.Sh NAME
+.Nm xkbcli
+.Nd tool to interact with XKB keymaps
+.
+.Sh SYNOPSIS
+.Nm
+.Ar command Bo arguments Bc
+.
+.Nm
+.Op Fl \-help | Fl \-version
+.
+.Sh DESCRIPTION
+.Nm
+is a commandline tool to query, compile and test XKB keymaps, layouts and other elements.
+.
+.Bl -tag -width Ds
+.It Fl \-help
+Print help and exit
+.
+.It Fl \-version
+Print the version and exit
+.El
+.
+.Ss COMMANDS
+.Bl -tag -width Ds
+.It Ic how\-to\-type
+Show how to type a given Unicode codepoint, see
+.Xr xkbcli\-how\-to\-type 1
+.
+.It Ic interactive\-x11
+Interactive debugger for XKB keymaps for X11, see
+.Xr xkbcli\-interactive\-x11 1
+.
+.It Ic interactive\-wayland
+Interactive debugger for XKB keymaps for Wayland, see
+.Xr xkbcli\-interactive\-wayland 1
+.
+.It Ic interactive\-evdev
+Interactive debugger for XKB keymaps for evdev (Linux), see
+.Xr xkbcli\-interactive\-evdev 1
+.
+.It Ic list
+List available layouts and more, see
+.Xr xkbcli\-list 1
+.El
+.
+.Pp
+Note that not all tools may be available on your system.
+.
+.Sh EXIT STATUS
+.Bl -tag -compact -width Ds
+.It 0
+exited successfully
+.It 1
+an error occured
+.It 2
+program was called with invalid arguments
+.El
+.
+.Sh SEE ALSO
+.Lk https://xkbcommon.org "The libxkbcommon online documentation"
diff --git a/tools/xkbcli.c b/tools/xkbcli.c
new file mode 100644
index 0000000..1f3ed3f
--- /dev/null
+++ b/tools/xkbcli.c
@@ -0,0 +1,119 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * 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 <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "tools-common.h"
+
+static void
+usage(void)
+{
+ printf("Usage: xkbcli [--help|-h] [--version|-V] <command> [<args>]\n"
+ "\n"
+ "Global options:\n"
+ " -h, --help ...... show this help and exit\n"
+ " -V, --version ... show version information and exit\n"
+ "Commands:\n"
+#if HAVE_XKBCLI_LIST
+ " list\n"
+ " List available rules, models, layouts, variants and options\n"
+ "\n"
+#endif
+#if HAVE_XKBCLI_INTERACTIVE_WAYLAND
+ " interactive-wayland\n"
+ " Interactive debugger for XKB keymaps for Wayland\n"
+ "\n"
+#endif
+#if HAVE_XKBCLI_INTERACTIVE_X11
+ " interactive-x11\n"
+ " Interactive debugger for XKB keymaps for X11\n"
+ "\n"
+#endif
+#if HAVE_XKBCLI_INTERACTIVE_EVDEV
+ " interactive-evdev\n"
+ " Interactive debugger for XKB keymaps for evdev (Linux)\n"
+ "\n"
+#endif
+#if HAVE_XKBCLI_COMPILE_KEYMAP
+ " compile-keymap\n"
+ " Compile an XKB keymap\n"
+ "\n"
+#endif
+#if HAVE_XKBCLI_HOW_TO_TYPE
+ " how-to-type\n"
+ " Print key sequences to type a Unicode codepoint\n"
+ "\n"
+#endif
+ );
+}
+
+int
+main(int argc, char **argv)
+{
+ enum options {
+ OPT_HELP = 1,
+ OPT_VERSION,
+ };
+ int option_index = 0;
+
+ while (1) {
+ int c;
+ static struct option opts[] = {
+ { "help", no_argument, 0, OPT_HELP },
+ { "version", no_argument, 0, OPT_VERSION },
+ { 0, 0, 0, 0}
+ };
+
+ c = getopt_long(argc, argv, "+hV", opts, &option_index);
+ if (c == -1)
+ break;
+
+ switch(c) {
+ case 'h':
+ case OPT_HELP:
+ usage();
+ return EXIT_SUCCESS;
+ case 'V':
+ case OPT_VERSION:
+ printf("%s\n", LIBXKBCOMMON_VERSION);
+ return EXIT_SUCCESS;
+ default:
+ usage();
+ return EXIT_INVALID_USAGE;
+ }
+ }
+
+ if (optind >= argc) {
+ usage();
+ return EXIT_INVALID_USAGE;
+ }
+
+ argv += optind;
+ argc -= optind;
+
+ return tools_exec_command("xkbcli", argc, argv);
+}