summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorPeter Hutterer <peter.hutterer@who-t.net>2020-06-23 16:20:08 +1000
committerPeter Hutterer <peter.hutterer@who-t.net>2020-06-25 10:32:08 +1000
commit3adbe54eacbb8dd3f23f9a4d1171ec0704a7fffb (patch)
tree00e5edfa80e351dd63f9177f02d8dcf12f94675f /tools
parentf525f9f0403c4881f32e65234d2647cd67dc826c (diff)
downloadxorg-lib-libxkbcommon-3adbe54eacbb8dd3f23f9a4d1171ec0704a7fffb.tar.gz
tools: move the remaining tools from test to here
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Diffstat (limited to 'tools')
-rw-r--r--tools/interactive-evdev.c520
-rw-r--r--tools/interactive-wayland.c712
-rw-r--r--tools/interactive-x11.c395
3 files changed, 1627 insertions, 0 deletions
diff --git a/tools/interactive-evdev.c b/tools/interactive-evdev.c
new file mode 100644
index 0000000..e09da6a
--- /dev/null
+++ b/tools/interactive-evdev.c
@@ -0,0 +1,520 @@
+/*
+ * 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 <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;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ret = EXIT_FAILURE;
+ int opt;
+ 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;
+
+ setlocale(LC_ALL, "");
+
+ while ((opt = getopt(argc, argv, "r:m:l:v:o:k:n:cdg")) != -1) {
+ switch (opt) {
+ case 'r':
+ rules = optarg;
+ break;
+ case 'm':
+ model = optarg;
+ break;
+ case 'l':
+ layout = optarg;
+ break;
+ case 'v':
+ variant = optarg;
+ break;
+ case 'o':
+ options = optarg;
+ break;
+ case 'k':
+ keymap_path = optarg;
+ break;
+ case 'n':
+ errno = 0;
+ evdev_offset = strtol(optarg, NULL, 10);
+ if (errno) {
+ fprintf(stderr, "error: -n option expects a number\n");
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'c':
+ report_state_changes = true;
+ break;
+ case 'd':
+ with_compose = true;
+ break;
+ case 'g':
+ consumed_mode = XKB_CONSUMED_MODE_GTK;
+ break;
+ case '?':
+ fprintf(stderr, " Usage: %s [-r <rules>] [-m <model>] "
+ "[-l <layout>] [-v <variant>] [-o <options>]\n",
+ argv[0]);
+ fprintf(stderr, " or: %s -k <path to keymap file>\n",
+ argv[0]);
+ fprintf(stderr, "For both: -n <evdev keycode offset>\n"
+ " -c (to report changes to the state)\n"
+ " -d (to enable compose)\n"
+ " -g (to use GTK consumed mode)\n");
+ exit(2);
+ }
+ }
+
+ 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 = isempty(rules) ? NULL : rules,
+ .model = isempty(model) ? NULL : model,
+ .layout = isempty(layout) ? NULL : layout,
+ .variant = isempty(variant) ? NULL : variant,
+ .options = isempty(options) ? 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..0ffccd0
--- /dev/null
+++ b/tools/interactive-wayland.c
@@ -0,0 +1,712 @@
+/*
+ * 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 <stdint.h>
+#include <unistd.h>
+#include <sys/mman.h>
+
+#include "xkbcommon/xkbcommon.h"
+#include "tools-common.h"
+
+#include <wayland-client.h>
+#include "xdg-shell-client-protocol.h"
+#include <wayland-util.h>
+
+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;
+
+ 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..4cc24d8
--- /dev/null
+++ b/tools/interactive-x11.c
@@ -0,0 +1,395 @@
+/*
+ * 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 <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;
+
+ 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);
+}