| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 |
- /*
- * Copyright © 2014 Ran Benita <ran234@gmail.com>
- * SPDX-License-Identifier: MIT
- */
- #include "config.h"
- #include "test-config.h"
- #include <assert.h>
- #include <getopt.h>
- #include <limits.h>
- #include <signal.h>
- #include <spawn.h>
- #include <stdint.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <sys/types.h>
- #include <sys/wait.h>
- #include <unistd.h>
- #include "xkbcommon/xkbcommon.h"
- #include "xkbcommon/xkbcommon-keysyms.h"
- #include "xkbcommon/xkbcommon-names.h"
- #include "xkbcommon/xkbcommon-x11.h"
- #include "evdev-scancodes.h"
- #include "test.h"
- #include "tools/tools-common.h"
- #include "utils.h"
- #include "utils-text.h"
- #include "xvfb-wrapper.h"
- /* Offset between evdev keycodes (where KEY_ESCAPE is 1), and the evdev XKB
- * keycode set (where ESC is 9). */
- #define EVDEV_OFFSET 8
- enum update_files {
- NO_UPDATE = 0,
- UPDATE_USING_TEST_INPUT,
- UPDATE_USING_TEST_OUTPUT
- };
- static enum update_files update_test_files = NO_UPDATE;
- static char *
- prepare_keymap(const char *original, bool strip_all_comments)
- {
- if (strip_all_comments) {
- return strip_lines(original, strlen(original), "//");
- } else {
- char *tmp = uncomment(original, strlen(original), "//");
- char *ret = strip_lines(tmp, strlen(tmp), "//");
- free(tmp);
- return ret;
- }
- }
- /*
- * Reset keymap to `empty` on the server.
- * It seems that xkbcomp does not fully set the keymap on the server and
- * the conflicting leftovers may raise errors.
- */
- static int
- reset_keymap(const char* display)
- {
- char *envp[] = { NULL };
- const char* setxkbmap_argv[] = {
- "setxkbmap", "-display", display,
- "-geometry", "pc",
- "-keycodes", "evdev",
- "-compat", "basic",
- "-types", "basic+numpad", /* Avoid extra types */
- "-symbols", "us",
- // "-v", "10",
- NULL
- };
- /* Launch xkbcomp */
- pid_t setxkbmap_pid;
- int ret = posix_spawnp(&setxkbmap_pid, "setxkbmap", NULL, NULL,
- (char* const*)setxkbmap_argv, envp);
- if (ret != 0) {
- fprintf(stderr,
- "[ERROR] Cannot run setxkbmap. posix_spawnp error %d: %s\n",
- ret, strerror(ret));
- if (ret == ENOENT) {
- fprintf(stderr,
- "[ERROR] setxkbmap may be missing. "
- "Please install the corresponding package, "
- "e.g. \"setxkbmap\" or \"x11-xkb-utils\".\n");
- }
- return TEST_SETUP_FAILURE;
- }
- int status;
- ret = waitpid(setxkbmap_pid, &status, 0);
- return (ret < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
- ? TEST_SETUP_FAILURE
- : EXIT_SUCCESS;
- }
- // TODO: unused for now
- // /* Use xkbcomp to load a keymap from a file */
- // static int
- // run_xkbcomp_file(const char* display, xcb_connection_t *conn, int32_t device_id,
- // const char *xkb_path)
- // {
- // /* Prepare xkbcomp parameters */
- // char *envp[] = { NULL };
- // const char* xkbcomp_argv[] = {"xkbcomp", "-I", xkb_path, display, NULL};
- //
- // /* Launch xkbcomp */
- // pid_t xkbcomp_pid;
- // int ret = posix_spawnp(&xkbcomp_pid, "xkbcomp", NULL, NULL,
- // (char* const*)xkbcomp_argv, envp);
- // if (ret != 0) {
- // fprintf(stderr,
- // "[ERROR] Cannot run xkbcomp. posix_spawnp error %d: %s\n",
- // ret, strerror(ret));
- // if (ret == ENOENT) {
- // fprintf(stderr,
- // "[ERROR] xkbcomp may be missing. "
- // "Please install the corresponding package, "
- // "e.g. \"xkbcomp\" or \"x11-xkb-utils\".\n");
- // }
- // return TEST_SETUP_FAILURE;
- // }
- //
- // /* Wait for xkbcomp to complete */
- // int status;
- // ret = waitpid(xkbcomp_pid, &status, 0);
- // return (ret < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
- // ? TEST_SETUP_FAILURE
- // : EXIT_SUCCESS;
- // }
- enum {
- PIPE_READ = 0,
- PIPE_WRITE = 1
- };
- /* Use xkbcomp to load a keymap from a string */
- static int
- run_xkbcomp_str(const char* display, const char *include_path, const char *keymap)
- {
- int ret = EXIT_FAILURE;
- assert(keymap);
- /* Prepare xkbcomp parameters */
- char *envp[] = { NULL };
- char include_path_arg[PATH_MAX+2] = "-I";
- const char *xkbcomp_argv[] = {
- "xkbcomp", "-I" /* reset include path*/, include_path_arg,
- "-opt", "g", "-w", "10", "-" /* stdin */, display, NULL
- };
- if (include_path) {
- ret = snprintf(include_path_arg, ARRAY_SIZE(include_path_arg),
- "-I%s", include_path);
- if (ret < 0 || ret >= (int)ARRAY_SIZE(include_path_arg)) {
- return TEST_SETUP_FAILURE;
- }
- }
- /* Prepare input */
- int stdin_pipe[2];
- posix_spawn_file_actions_t action;
- if (pipe(stdin_pipe) == -1) {
- perror("pipe");
- ret = TEST_SETUP_FAILURE;
- goto pipe_error;
- }
- if (posix_spawn_file_actions_init(&action)) {
- perror("spawn_file_actions_init error");
- goto posix_spawn_file_actions_init_error;
- }
- /* Make spawned process close unused write-end of pipe, else it will not
- * receive EOF when write-end of the pipe is closed below and it will result
- * in a deadlock. */
- if (posix_spawn_file_actions_addclose(&action, stdin_pipe[PIPE_WRITE]))
- goto posix_spawn_file_actions_error;
- /* Make spawned process replace stdin with read end of the pipe */
- if (posix_spawn_file_actions_adddup2(&action, stdin_pipe[PIPE_READ], STDIN_FILENO))
- goto posix_spawn_file_actions_error;
- /* Launch xkbcomp */
- pid_t xkbcomp_pid;
- ret = posix_spawnp(&xkbcomp_pid, "xkbcomp", &action, NULL,
- (char* const*)xkbcomp_argv, envp);
- if (ret != 0) {
- fprintf(stderr,
- "[ERROR] Cannot run xkbcomp. posix_spawnp error %d: %s\n",
- ret, strerror(ret));
- if (ret == ENOENT) {
- fprintf(stderr,
- "[ERROR] xkbcomp may be missing. "
- "Please install the corresponding package, "
- "e.g. \"xkbcomp\" or \"x11-xkb-utils\".\n");
- }
- goto posix_spawn_file_actions_init_error;
- }
- /* Close unused read-end of pipe */
- close(stdin_pipe[PIPE_READ]);
- const ssize_t count = write(stdin_pipe[PIPE_WRITE], keymap, strlen(keymap));
- /* Close write-end of the pipe, to emit EOF */
- close(stdin_pipe[PIPE_WRITE]);
- if (count == -1) {
- perror("Cannot write keymap to stdin");
- kill(xkbcomp_pid, SIGTERM);
- }
- /* Wait for xkbcomp to complete */
- int status;
- ret = waitpid(xkbcomp_pid, &status, 0);
- ret = (ret < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
- ? TEST_SETUP_FAILURE
- : EXIT_SUCCESS;
- goto cleanup;
- posix_spawn_file_actions_error:
- perror("posix_spawn_file_actions_* error");
- posix_spawn_file_actions_init_error:
- close(stdin_pipe[PIPE_WRITE]);
- close(stdin_pipe[PIPE_READ]);
- ret = TEST_SETUP_FAILURE;
- cleanup:
- posix_spawn_file_actions_destroy(&action);
- pipe_error:
- return ret;
- }
- // TODO: implement tests to ensure compatibility with xkbcomp
- // struct compile_buffer_private {
- // const char* display;
- // xcb_connection_t *conn;
- // int32_t device_id;
- // };
- //
- // static struct xkb_keymap *
- // compile_buffer(struct xkb_context *ctx, const char *buf, size_t len,
- // void *private)
- // {
- // const struct compile_buffer_private *config = private;
- // assert(buf[len - 1] == '\0');
- //
- // char *include_path = test_get_path("");
- // int ret = run_xkbcomp_str(config->display, config->conn,
- // config->device_id, include_path, buf);
- // free(include_path);
- //
- // if (ret != EXIT_SUCCESS)
- // return NULL;
- //
- // return xkb_x11_keymap_new_from_device(ctx, config->conn, config->device_id,
- // TEST_KEYMAP_COMPILE_FLAGS);
- // }
- static int
- test_keymap_roundtrip(struct xkb_context *ctx,
- const char* display, xcb_connection_t *conn,
- int32_t device_id, bool print_keymap, bool tweak,
- enum xkb_keymap_serialize_flags serialize_flags,
- const char *keymap_path)
- {
- /* Get raw keymap */
- FILE *file = NULL;
- if (isempty(keymap_path) || strcmp(keymap_path, "-") == 0) {
- /* Read stdin */
- file = tools_read_stdin();
- if (!file)
- return TEST_SETUP_FAILURE;
- } else {
- /* Read file from path */
- file = fopen(keymap_path, "rb");
- if (!file) {
- perror("Unable to read file");
- return TEST_SETUP_FAILURE;
- }
- }
- char *original = read_file(keymap_path, file);
- fclose(file);
- if (!original)
- return TEST_SETUP_FAILURE;
- /* Pre-process keymap string */
- char* expected = prepare_keymap(original, tweak);
- free(original);
- if (!expected) {
- return TEST_SETUP_FAILURE;
- }
- /* Prepare X server */
- int ret = reset_keymap(display);
- if (ret != EXIT_SUCCESS) {
- #ifdef __APPLE__
- /* Brew may not provide setxkbmap */
- #else
- goto xkbcomp_error;
- #endif
- }
- /* Load keymap into X server */
- ret = run_xkbcomp_str(display, NULL, expected);
- if (ret != EXIT_SUCCESS)
- goto xkbcomp_error;
- /* Get keymap from X server */
- struct xkb_keymap *keymap =
- xkb_x11_keymap_new_from_device(ctx, conn, device_id,
- TEST_KEYMAP_COMPILE_FLAGS);
- assert(keymap);
- if (!keymap) {
- ret = EXIT_FAILURE;
- fprintf(stderr, "ERROR: Failed to get keymap from X server.\n");
- goto xkbcomp_error;
- }
- /* Dump keymap and compare to expected */
- char *got = xkb_keymap_get_as_string2(keymap, XKB_KEYMAP_USE_ORIGINAL_FORMAT,
- serialize_flags);
- if (!got) {
- ret = EXIT_FAILURE;
- fprintf(stderr, "ERROR: Failed to dump keymap.\n");
- } else {
- if (!streq(got, expected)) {
- ret = EXIT_FAILURE;
- fprintf(stderr,
- "ERROR: roundtrip failed. "
- "Lengths difference: expected %zu, got %zu.\n"
- "Keymap:\n",
- strlen(expected), strlen(got));
- } else {
- ret = EXIT_SUCCESS;
- fprintf(stderr, "Roundtrip succeed.\n");
- }
- if (print_keymap || ret == EXIT_FAILURE)
- fprintf((ret == EXIT_FAILURE ? stderr : stdout), "%s\n", got);
- free(got);
- }
- xkb_keymap_unref(keymap);
- xkbcomp_error:
- free(expected);
- return ret;
- }
- static int
- init_x11_connection(const char *display, xcb_connection_t **conn_rtrn,
- int32_t *device_id_rtrn)
- {
- xcb_connection_t *conn = xcb_connect(display, NULL);
- if (xcb_connection_has_error(conn)) {
- return TEST_SETUP_FAILURE;
- }
- int 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, NULL, NULL);
- if (!ret)
- goto err_xcb;
- int32_t device_id = xkb_x11_get_core_keyboard_device_id(conn);
- if (device_id == -1)
- goto err_xcb;
- *conn_rtrn = conn;
- *device_id_rtrn = device_id;
- return EXIT_SUCCESS;
- err_xcb:
- xcb_disconnect(conn);
- return TEST_SETUP_FAILURE;
- }
- X11_TEST(test_basic)
- {
- if (update_test_files != NO_UPDATE)
- return EXIT_SUCCESS;
- xcb_connection_t *conn = NULL;
- int32_t device_id = 0;
- int ret = init_x11_connection(display, &conn, &device_id);
- if (ret != EXIT_SUCCESS) {
- ret = TEST_SETUP_FAILURE;
- goto err_xcb;
- }
- struct xkb_context *ctx = test_get_context(CONTEXT_NO_FLAG);
- assert(ctx);
- static const struct {
- const char *path;
- enum xkb_keymap_serialize_flags serialize_flags;
- } keymaps[] = {
- {
- .path = "keymaps/host-no-pretty.xkb",
- .serialize_flags = TEST_KEYMAP_SERIALIZE_FLAGS
- & ~XKB_KEYMAP_SERIALIZE_PRETTY
- },
- /* This last keymap will be used for the next tests */
- {
- .path = "keymaps/host.xkb",
- .serialize_flags = TEST_KEYMAP_SERIALIZE_FLAGS
- },
- };
- for (size_t k = 0; k < ARRAY_SIZE(keymaps); k++) {
- fprintf(stderr, "------\n*** %s: #%zu ***\n", __func__, k);
- char *keymap_path = test_get_path(keymaps[k].path);
- assert(keymap_path);
- ret = test_keymap_roundtrip(ctx, display, conn, device_id,
- false, false, keymaps[k].serialize_flags,
- keymap_path);
- assert(ret == EXIT_SUCCESS);
- free(keymap_path);
- }
- /* Reject invalid flags */
- assert(!xkb_x11_keymap_new_from_device(ctx, conn, device_id, -1));
- assert(!xkb_x11_keymap_new_from_device(ctx, conn, device_id, 0xffff));
- struct xkb_keymap *keymap =
- xkb_x11_keymap_new_from_device(ctx, conn, device_id,
- TEST_KEYMAP_COMPILE_FLAGS);
- assert(keymap);
- /* Check capitalization transformation */
- struct xkb_state *state =
- xkb_x11_state_new_from_device(keymap, conn, device_id);
- assert(state);
- xkb_keysym_t sym;
- sym = xkb_state_key_get_one_sym(state, KEY_A + EVDEV_OFFSET);
- assert(sym == XKB_KEY_a);
- sym = xkb_state_key_get_one_sym(state, KEY_LEFT + EVDEV_OFFSET);
- assert(sym == XKB_KEY_Left);
- const xkb_mod_index_t caps_idx =
- xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CAPS);
- assert(caps_idx != XKB_MOD_INVALID);
- const xkb_mod_mask_t caps = UINT32_C(1) << caps_idx;
- xkb_state_update_mask(state, 0, 0, caps, 0, 0, 0);
- sym = xkb_state_key_get_one_sym(state, KEY_A + EVDEV_OFFSET);
- assert(sym == XKB_KEY_A);
- sym = xkb_state_key_get_one_sym(state, KEY_LEFT + EVDEV_OFFSET);
- assert(sym == XKB_KEY_Left);
- xkb_state_unref(state);
- xkb_keymap_unref(keymap);
- xkb_context_unref(ctx);
- ret = EXIT_SUCCESS;
- err_xcb:
- if (conn != NULL)
- xcb_disconnect(conn);
- return ret;
- }
- struct xkbcomp_roundtrip_data {
- const char *path;
- bool tweak_comments;
- enum xkb_keymap_serialize_flags serialize_flags;
- };
- static int
- xkbcomp_roundtrip(const char *display, void *private) {
- const struct xkbcomp_roundtrip_data *priv = private;
- xcb_connection_t *conn = NULL;
- int32_t device_id = 0;
- int ret = init_x11_connection(display, &conn, &device_id);
- if (ret != EXIT_SUCCESS) {
- ret = TEST_SETUP_FAILURE;
- goto error_xcb;
- }
- struct xkb_context *ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
- if (!ctx) {
- ret = EXIT_FAILURE;
- goto error_context;
- }
- ret = test_keymap_roundtrip(ctx, display, conn, device_id, true,
- priv->tweak_comments, priv->serialize_flags,
- priv->path);
- xkb_context_unref(ctx);
- error_context:
- error_xcb:
- xcb_disconnect(conn);
- return ret;
- }
- static void
- usage(FILE *fp, char *progname)
- {
- fprintf(fp,
- "Usage: %s [--update] [--update-obtained] "
- "[--keymap KEYMAP_FILE] [--tweak] [--no-pretty] [--help]\n",
- progname);
- }
- int
- main(int argc, char **argv) {
- test_init();
- enum options {
- OPT_UPDATE_GOLDEN_TEST_WITH_OUTPUT,
- OPT_UPDATE_GOLDEN_TEST_WITH_INPUT,
- OPT_FILE,
- OPT_TWEAK_COMMENTS,
- OPT_NO_PRETTY,
- };
- static struct option opts[] = {
- {"help", no_argument, 0, 'h'},
- {"update-obtained", no_argument, 0, OPT_UPDATE_GOLDEN_TEST_WITH_OUTPUT},
- {"update", no_argument, 0, OPT_UPDATE_GOLDEN_TEST_WITH_INPUT},
- {"keymap", required_argument, 0, OPT_FILE},
- {"tweak", no_argument, 0, OPT_TWEAK_COMMENTS},
- {"no-pretty", no_argument, 0, OPT_NO_PRETTY},
- {0, 0, 0, 0},
- };
- bool tweak_comments = false;
- const char *path = NULL;
- enum xkb_keymap_serialize_flags serialize_flags =
- (enum xkb_keymap_serialize_flags) TEST_KEYMAP_SERIALIZE_FLAGS;
- 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_UPDATE_GOLDEN_TEST_WITH_OUTPUT:
- update_test_files = UPDATE_USING_TEST_OUTPUT;
- break;
- case OPT_UPDATE_GOLDEN_TEST_WITH_INPUT:
- update_test_files = UPDATE_USING_TEST_INPUT;
- break;
- case OPT_FILE:
- path = optarg;
- break;
- case OPT_TWEAK_COMMENTS:
- tweak_comments = optarg;
- break;
- case OPT_NO_PRETTY:
- serialize_flags &= ~XKB_KEYMAP_SERIALIZE_PRETTY;
- break;
- case 'h':
- usage(stdout, argv[0]);
- return EXIT_SUCCESS;
- default:
- usage(stderr, argv[0]);
- return EXIT_INVALID_USAGE;
- }
- }
- if (update_test_files != NO_UPDATE && path != NULL) {
- fprintf(stderr,
- "ERROR: --update* and --keymap are mutually exclusive.\n");
- usage(stderr, argv[0]);
- return EXIT_INVALID_USAGE;
- }
- if (path == NULL) {
- return x11_tests_run();
- } else {
- struct xkbcomp_roundtrip_data priv = {
- .path = path,
- .tweak_comments = tweak_comments,
- .serialize_flags = serialize_flags
- };
- return xvfb_wrapper(xkbcomp_roundtrip, &priv);
- }
- }
|