diff options
author | Haibo Huang <hhb@google.com> | 2020-09-08 17:10:03 -0700 |
---|---|---|
committer | Haibo Huang <hhb@google.com> | 2020-09-10 22:20:42 +0000 |
commit | bffa8499cb8ce3cc4366055be8fe62d501d6a8e5 (patch) | |
tree | 648dfaada5799a6227dd5f1af43d89ed8d71d96d /tools | |
parent | e4e474780d90ed6166f7113a7464371baa275007 (diff) | |
download | libxkbcommon-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.c | 375 | ||||
-rw-r--r-- | tools/how-to-type.c | 224 | ||||
-rw-r--r-- | tools/interactive-evdev.c | 567 | ||||
-rw-r--r-- | tools/interactive-wayland.c | 725 | ||||
-rw-r--r-- | tools/interactive-x11.c | 406 | ||||
-rw-r--r-- | tools/registry-list.c | 225 | ||||
-rw-r--r-- | tools/tools-common.c | 242 | ||||
-rw-r--r-- | tools/tools-common.h | 60 | ||||
-rw-r--r-- | tools/xkbcli-compile-keymap.1 | 62 | ||||
-rw-r--r-- | tools/xkbcli-how-to-type.1 | 41 | ||||
-rw-r--r-- | tools/xkbcli-interactive-evdev.1 | 77 | ||||
-rw-r--r-- | tools/xkbcli-interactive-wayland.1 | 37 | ||||
-rw-r--r-- | tools/xkbcli-interactive-x11.1 | 37 | ||||
-rw-r--r-- | tools/xkbcli-list.1 | 39 | ||||
-rw-r--r-- | tools/xkbcli.1 | 65 | ||||
-rw-r--r-- | tools/xkbcli.c | 119 |
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, ®istry_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); +} |