x11comp.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. /*
  2. * Copyright © 2014 Ran Benita <ran234@gmail.com>
  3. * SPDX-License-Identifier: MIT
  4. */
  5. #include "config.h"
  6. #include "test-config.h"
  7. #include <assert.h>
  8. #include <getopt.h>
  9. #include <limits.h>
  10. #include <signal.h>
  11. #include <spawn.h>
  12. #include <stdint.h>
  13. #include <stdio.h>
  14. #include <stdlib.h>
  15. #include <sys/types.h>
  16. #include <sys/wait.h>
  17. #include <unistd.h>
  18. #include "xkbcommon/xkbcommon.h"
  19. #include "xkbcommon/xkbcommon-keysyms.h"
  20. #include "xkbcommon/xkbcommon-names.h"
  21. #include "xkbcommon/xkbcommon-x11.h"
  22. #include "evdev-scancodes.h"
  23. #include "test.h"
  24. #include "tools/tools-common.h"
  25. #include "utils.h"
  26. #include "utils-text.h"
  27. #include "xvfb-wrapper.h"
  28. /* Offset between evdev keycodes (where KEY_ESCAPE is 1), and the evdev XKB
  29. * keycode set (where ESC is 9). */
  30. #define EVDEV_OFFSET 8
  31. enum update_files {
  32. NO_UPDATE = 0,
  33. UPDATE_USING_TEST_INPUT,
  34. UPDATE_USING_TEST_OUTPUT
  35. };
  36. static enum update_files update_test_files = NO_UPDATE;
  37. static char *
  38. prepare_keymap(const char *original, bool strip_all_comments)
  39. {
  40. if (strip_all_comments) {
  41. return strip_lines(original, strlen(original), "//");
  42. } else {
  43. char *tmp = uncomment(original, strlen(original), "//");
  44. char *ret = strip_lines(tmp, strlen(tmp), "//");
  45. free(tmp);
  46. return ret;
  47. }
  48. }
  49. /*
  50. * Reset keymap to `empty` on the server.
  51. * It seems that xkbcomp does not fully set the keymap on the server and
  52. * the conflicting leftovers may raise errors.
  53. */
  54. static int
  55. reset_keymap(const char* display)
  56. {
  57. char *envp[] = { NULL };
  58. const char* setxkbmap_argv[] = {
  59. "setxkbmap", "-display", display,
  60. "-geometry", "pc",
  61. "-keycodes", "evdev",
  62. "-compat", "basic",
  63. "-types", "basic+numpad", /* Avoid extra types */
  64. "-symbols", "us",
  65. // "-v", "10",
  66. NULL
  67. };
  68. /* Launch xkbcomp */
  69. pid_t setxkbmap_pid;
  70. int ret = posix_spawnp(&setxkbmap_pid, "setxkbmap", NULL, NULL,
  71. (char* const*)setxkbmap_argv, envp);
  72. if (ret != 0) {
  73. fprintf(stderr,
  74. "[ERROR] Cannot run setxkbmap. posix_spawnp error %d: %s\n",
  75. ret, strerror(ret));
  76. if (ret == ENOENT) {
  77. fprintf(stderr,
  78. "[ERROR] setxkbmap may be missing. "
  79. "Please install the corresponding package, "
  80. "e.g. \"setxkbmap\" or \"x11-xkb-utils\".\n");
  81. }
  82. return TEST_SETUP_FAILURE;
  83. }
  84. int status;
  85. ret = waitpid(setxkbmap_pid, &status, 0);
  86. return (ret < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
  87. ? TEST_SETUP_FAILURE
  88. : EXIT_SUCCESS;
  89. }
  90. // TODO: unused for now
  91. // /* Use xkbcomp to load a keymap from a file */
  92. // static int
  93. // run_xkbcomp_file(const char* display, xcb_connection_t *conn, int32_t device_id,
  94. // const char *xkb_path)
  95. // {
  96. // /* Prepare xkbcomp parameters */
  97. // char *envp[] = { NULL };
  98. // const char* xkbcomp_argv[] = {"xkbcomp", "-I", xkb_path, display, NULL};
  99. //
  100. // /* Launch xkbcomp */
  101. // pid_t xkbcomp_pid;
  102. // int ret = posix_spawnp(&xkbcomp_pid, "xkbcomp", NULL, NULL,
  103. // (char* const*)xkbcomp_argv, envp);
  104. // if (ret != 0) {
  105. // fprintf(stderr,
  106. // "[ERROR] Cannot run xkbcomp. posix_spawnp error %d: %s\n",
  107. // ret, strerror(ret));
  108. // if (ret == ENOENT) {
  109. // fprintf(stderr,
  110. // "[ERROR] xkbcomp may be missing. "
  111. // "Please install the corresponding package, "
  112. // "e.g. \"xkbcomp\" or \"x11-xkb-utils\".\n");
  113. // }
  114. // return TEST_SETUP_FAILURE;
  115. // }
  116. //
  117. // /* Wait for xkbcomp to complete */
  118. // int status;
  119. // ret = waitpid(xkbcomp_pid, &status, 0);
  120. // return (ret < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
  121. // ? TEST_SETUP_FAILURE
  122. // : EXIT_SUCCESS;
  123. // }
  124. enum {
  125. PIPE_READ = 0,
  126. PIPE_WRITE = 1
  127. };
  128. /* Use xkbcomp to load a keymap from a string */
  129. static int
  130. run_xkbcomp_str(const char* display, const char *include_path, const char *keymap)
  131. {
  132. int ret = EXIT_FAILURE;
  133. assert(keymap);
  134. /* Prepare xkbcomp parameters */
  135. char *envp[] = { NULL };
  136. char include_path_arg[PATH_MAX+2] = "-I";
  137. const char *xkbcomp_argv[] = {
  138. "xkbcomp", "-I" /* reset include path*/, include_path_arg,
  139. "-opt", "g", "-w", "10", "-" /* stdin */, display, NULL
  140. };
  141. if (include_path) {
  142. ret = snprintf(include_path_arg, ARRAY_SIZE(include_path_arg),
  143. "-I%s", include_path);
  144. if (ret < 0 || ret >= (int)ARRAY_SIZE(include_path_arg)) {
  145. return TEST_SETUP_FAILURE;
  146. }
  147. }
  148. /* Prepare input */
  149. int stdin_pipe[2];
  150. posix_spawn_file_actions_t action;
  151. if (pipe(stdin_pipe) == -1) {
  152. perror("pipe");
  153. ret = TEST_SETUP_FAILURE;
  154. goto pipe_error;
  155. }
  156. if (posix_spawn_file_actions_init(&action)) {
  157. perror("spawn_file_actions_init error");
  158. goto posix_spawn_file_actions_init_error;
  159. }
  160. /* Make spawned process close unused write-end of pipe, else it will not
  161. * receive EOF when write-end of the pipe is closed below and it will result
  162. * in a deadlock. */
  163. if (posix_spawn_file_actions_addclose(&action, stdin_pipe[PIPE_WRITE]))
  164. goto posix_spawn_file_actions_error;
  165. /* Make spawned process replace stdin with read end of the pipe */
  166. if (posix_spawn_file_actions_adddup2(&action, stdin_pipe[PIPE_READ], STDIN_FILENO))
  167. goto posix_spawn_file_actions_error;
  168. /* Launch xkbcomp */
  169. pid_t xkbcomp_pid;
  170. ret = posix_spawnp(&xkbcomp_pid, "xkbcomp", &action, NULL,
  171. (char* const*)xkbcomp_argv, envp);
  172. if (ret != 0) {
  173. fprintf(stderr,
  174. "[ERROR] Cannot run xkbcomp. posix_spawnp error %d: %s\n",
  175. ret, strerror(ret));
  176. if (ret == ENOENT) {
  177. fprintf(stderr,
  178. "[ERROR] xkbcomp may be missing. "
  179. "Please install the corresponding package, "
  180. "e.g. \"xkbcomp\" or \"x11-xkb-utils\".\n");
  181. }
  182. goto posix_spawn_file_actions_init_error;
  183. }
  184. /* Close unused read-end of pipe */
  185. close(stdin_pipe[PIPE_READ]);
  186. const ssize_t count = write(stdin_pipe[PIPE_WRITE], keymap, strlen(keymap));
  187. /* Close write-end of the pipe, to emit EOF */
  188. close(stdin_pipe[PIPE_WRITE]);
  189. if (count == -1) {
  190. perror("Cannot write keymap to stdin");
  191. kill(xkbcomp_pid, SIGTERM);
  192. }
  193. /* Wait for xkbcomp to complete */
  194. int status;
  195. ret = waitpid(xkbcomp_pid, &status, 0);
  196. ret = (ret < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
  197. ? TEST_SETUP_FAILURE
  198. : EXIT_SUCCESS;
  199. goto cleanup;
  200. posix_spawn_file_actions_error:
  201. perror("posix_spawn_file_actions_* error");
  202. posix_spawn_file_actions_init_error:
  203. close(stdin_pipe[PIPE_WRITE]);
  204. close(stdin_pipe[PIPE_READ]);
  205. ret = TEST_SETUP_FAILURE;
  206. cleanup:
  207. posix_spawn_file_actions_destroy(&action);
  208. pipe_error:
  209. return ret;
  210. }
  211. // TODO: implement tests to ensure compatibility with xkbcomp
  212. // struct compile_buffer_private {
  213. // const char* display;
  214. // xcb_connection_t *conn;
  215. // int32_t device_id;
  216. // };
  217. //
  218. // static struct xkb_keymap *
  219. // compile_buffer(struct xkb_context *ctx, const char *buf, size_t len,
  220. // void *private)
  221. // {
  222. // const struct compile_buffer_private *config = private;
  223. // assert(buf[len - 1] == '\0');
  224. //
  225. // char *include_path = test_get_path("");
  226. // int ret = run_xkbcomp_str(config->display, config->conn,
  227. // config->device_id, include_path, buf);
  228. // free(include_path);
  229. //
  230. // if (ret != EXIT_SUCCESS)
  231. // return NULL;
  232. //
  233. // return xkb_x11_keymap_new_from_device(ctx, config->conn, config->device_id,
  234. // TEST_KEYMAP_COMPILE_FLAGS);
  235. // }
  236. static int
  237. test_keymap_roundtrip(struct xkb_context *ctx,
  238. const char* display, xcb_connection_t *conn,
  239. int32_t device_id, bool print_keymap, bool tweak,
  240. enum xkb_keymap_serialize_flags serialize_flags,
  241. const char *keymap_path)
  242. {
  243. /* Get raw keymap */
  244. FILE *file = NULL;
  245. if (isempty(keymap_path) || strcmp(keymap_path, "-") == 0) {
  246. /* Read stdin */
  247. file = tools_read_stdin();
  248. if (!file)
  249. return TEST_SETUP_FAILURE;
  250. } else {
  251. /* Read file from path */
  252. file = fopen(keymap_path, "rb");
  253. if (!file) {
  254. perror("Unable to read file");
  255. return TEST_SETUP_FAILURE;
  256. }
  257. }
  258. char *original = read_file(keymap_path, file);
  259. fclose(file);
  260. if (!original)
  261. return TEST_SETUP_FAILURE;
  262. /* Pre-process keymap string */
  263. char* expected = prepare_keymap(original, tweak);
  264. free(original);
  265. if (!expected) {
  266. return TEST_SETUP_FAILURE;
  267. }
  268. /* Prepare X server */
  269. int ret = reset_keymap(display);
  270. if (ret != EXIT_SUCCESS) {
  271. #ifdef __APPLE__
  272. /* Brew may not provide setxkbmap */
  273. #else
  274. goto xkbcomp_error;
  275. #endif
  276. }
  277. /* Load keymap into X server */
  278. ret = run_xkbcomp_str(display, NULL, expected);
  279. if (ret != EXIT_SUCCESS)
  280. goto xkbcomp_error;
  281. /* Get keymap from X server */
  282. struct xkb_keymap *keymap =
  283. xkb_x11_keymap_new_from_device(ctx, conn, device_id,
  284. TEST_KEYMAP_COMPILE_FLAGS);
  285. assert(keymap);
  286. if (!keymap) {
  287. ret = EXIT_FAILURE;
  288. fprintf(stderr, "ERROR: Failed to get keymap from X server.\n");
  289. goto xkbcomp_error;
  290. }
  291. /* Dump keymap and compare to expected */
  292. char *got = xkb_keymap_get_as_string2(keymap, XKB_KEYMAP_USE_ORIGINAL_FORMAT,
  293. serialize_flags);
  294. if (!got) {
  295. ret = EXIT_FAILURE;
  296. fprintf(stderr, "ERROR: Failed to dump keymap.\n");
  297. } else {
  298. if (!streq(got, expected)) {
  299. ret = EXIT_FAILURE;
  300. fprintf(stderr,
  301. "ERROR: roundtrip failed. "
  302. "Lengths difference: expected %zu, got %zu.\n"
  303. "Keymap:\n",
  304. strlen(expected), strlen(got));
  305. } else {
  306. ret = EXIT_SUCCESS;
  307. fprintf(stderr, "Roundtrip succeed.\n");
  308. }
  309. if (print_keymap || ret == EXIT_FAILURE)
  310. fprintf((ret == EXIT_FAILURE ? stderr : stdout), "%s\n", got);
  311. free(got);
  312. }
  313. xkb_keymap_unref(keymap);
  314. xkbcomp_error:
  315. free(expected);
  316. return ret;
  317. }
  318. static int
  319. init_x11_connection(const char *display, xcb_connection_t **conn_rtrn,
  320. int32_t *device_id_rtrn)
  321. {
  322. xcb_connection_t *conn = xcb_connect(display, NULL);
  323. if (xcb_connection_has_error(conn)) {
  324. return TEST_SETUP_FAILURE;
  325. }
  326. int ret = xkb_x11_setup_xkb_extension(conn,
  327. XKB_X11_MIN_MAJOR_XKB_VERSION,
  328. XKB_X11_MIN_MINOR_XKB_VERSION,
  329. XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
  330. NULL, NULL, NULL, NULL);
  331. if (!ret)
  332. goto err_xcb;
  333. int32_t device_id = xkb_x11_get_core_keyboard_device_id(conn);
  334. if (device_id == -1)
  335. goto err_xcb;
  336. *conn_rtrn = conn;
  337. *device_id_rtrn = device_id;
  338. return EXIT_SUCCESS;
  339. err_xcb:
  340. xcb_disconnect(conn);
  341. return TEST_SETUP_FAILURE;
  342. }
  343. X11_TEST(test_basic)
  344. {
  345. if (update_test_files != NO_UPDATE)
  346. return EXIT_SUCCESS;
  347. xcb_connection_t *conn = NULL;
  348. int32_t device_id = 0;
  349. int ret = init_x11_connection(display, &conn, &device_id);
  350. if (ret != EXIT_SUCCESS) {
  351. ret = TEST_SETUP_FAILURE;
  352. goto err_xcb;
  353. }
  354. struct xkb_context *ctx = test_get_context(CONTEXT_NO_FLAG);
  355. assert(ctx);
  356. static const struct {
  357. const char *path;
  358. enum xkb_keymap_serialize_flags serialize_flags;
  359. } keymaps[] = {
  360. {
  361. .path = "keymaps/host-no-pretty.xkb",
  362. .serialize_flags = TEST_KEYMAP_SERIALIZE_FLAGS
  363. & ~XKB_KEYMAP_SERIALIZE_PRETTY
  364. },
  365. /* This last keymap will be used for the next tests */
  366. {
  367. .path = "keymaps/host.xkb",
  368. .serialize_flags = TEST_KEYMAP_SERIALIZE_FLAGS
  369. },
  370. };
  371. for (size_t k = 0; k < ARRAY_SIZE(keymaps); k++) {
  372. fprintf(stderr, "------\n*** %s: #%zu ***\n", __func__, k);
  373. char *keymap_path = test_get_path(keymaps[k].path);
  374. assert(keymap_path);
  375. ret = test_keymap_roundtrip(ctx, display, conn, device_id,
  376. false, false, keymaps[k].serialize_flags,
  377. keymap_path);
  378. assert(ret == EXIT_SUCCESS);
  379. free(keymap_path);
  380. }
  381. /* Reject invalid flags */
  382. assert(!xkb_x11_keymap_new_from_device(ctx, conn, device_id, -1));
  383. assert(!xkb_x11_keymap_new_from_device(ctx, conn, device_id, 0xffff));
  384. struct xkb_keymap *keymap =
  385. xkb_x11_keymap_new_from_device(ctx, conn, device_id,
  386. TEST_KEYMAP_COMPILE_FLAGS);
  387. assert(keymap);
  388. /* Check capitalization transformation */
  389. struct xkb_state *state =
  390. xkb_x11_state_new_from_device(keymap, conn, device_id);
  391. assert(state);
  392. xkb_keysym_t sym;
  393. sym = xkb_state_key_get_one_sym(state, KEY_A + EVDEV_OFFSET);
  394. assert(sym == XKB_KEY_a);
  395. sym = xkb_state_key_get_one_sym(state, KEY_LEFT + EVDEV_OFFSET);
  396. assert(sym == XKB_KEY_Left);
  397. const xkb_mod_index_t caps_idx =
  398. xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CAPS);
  399. assert(caps_idx != XKB_MOD_INVALID);
  400. const xkb_mod_mask_t caps = UINT32_C(1) << caps_idx;
  401. xkb_state_update_mask(state, 0, 0, caps, 0, 0, 0);
  402. sym = xkb_state_key_get_one_sym(state, KEY_A + EVDEV_OFFSET);
  403. assert(sym == XKB_KEY_A);
  404. sym = xkb_state_key_get_one_sym(state, KEY_LEFT + EVDEV_OFFSET);
  405. assert(sym == XKB_KEY_Left);
  406. xkb_state_unref(state);
  407. xkb_keymap_unref(keymap);
  408. xkb_context_unref(ctx);
  409. ret = EXIT_SUCCESS;
  410. err_xcb:
  411. if (conn != NULL)
  412. xcb_disconnect(conn);
  413. return ret;
  414. }
  415. struct xkbcomp_roundtrip_data {
  416. const char *path;
  417. bool tweak_comments;
  418. enum xkb_keymap_serialize_flags serialize_flags;
  419. };
  420. static int
  421. xkbcomp_roundtrip(const char *display, void *private) {
  422. const struct xkbcomp_roundtrip_data *priv = private;
  423. xcb_connection_t *conn = NULL;
  424. int32_t device_id = 0;
  425. int ret = init_x11_connection(display, &conn, &device_id);
  426. if (ret != EXIT_SUCCESS) {
  427. ret = TEST_SETUP_FAILURE;
  428. goto error_xcb;
  429. }
  430. struct xkb_context *ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
  431. if (!ctx) {
  432. ret = EXIT_FAILURE;
  433. goto error_context;
  434. }
  435. ret = test_keymap_roundtrip(ctx, display, conn, device_id, true,
  436. priv->tweak_comments, priv->serialize_flags,
  437. priv->path);
  438. xkb_context_unref(ctx);
  439. error_context:
  440. error_xcb:
  441. xcb_disconnect(conn);
  442. return ret;
  443. }
  444. static void
  445. usage(FILE *fp, char *progname)
  446. {
  447. fprintf(fp,
  448. "Usage: %s [--update] [--update-obtained] "
  449. "[--keymap KEYMAP_FILE] [--tweak] [--no-pretty] [--help]\n",
  450. progname);
  451. }
  452. int
  453. main(int argc, char **argv) {
  454. test_init();
  455. enum options {
  456. OPT_UPDATE_GOLDEN_TEST_WITH_OUTPUT,
  457. OPT_UPDATE_GOLDEN_TEST_WITH_INPUT,
  458. OPT_FILE,
  459. OPT_TWEAK_COMMENTS,
  460. OPT_NO_PRETTY,
  461. };
  462. static struct option opts[] = {
  463. {"help", no_argument, 0, 'h'},
  464. {"update-obtained", no_argument, 0, OPT_UPDATE_GOLDEN_TEST_WITH_OUTPUT},
  465. {"update", no_argument, 0, OPT_UPDATE_GOLDEN_TEST_WITH_INPUT},
  466. {"keymap", required_argument, 0, OPT_FILE},
  467. {"tweak", no_argument, 0, OPT_TWEAK_COMMENTS},
  468. {"no-pretty", no_argument, 0, OPT_NO_PRETTY},
  469. {0, 0, 0, 0},
  470. };
  471. bool tweak_comments = false;
  472. const char *path = NULL;
  473. enum xkb_keymap_serialize_flags serialize_flags =
  474. (enum xkb_keymap_serialize_flags) TEST_KEYMAP_SERIALIZE_FLAGS;
  475. while (1) {
  476. int opt;
  477. int option_index = 0;
  478. opt = getopt_long(argc, argv, "h", opts, &option_index);
  479. if (opt == -1)
  480. break;
  481. switch (opt) {
  482. case OPT_UPDATE_GOLDEN_TEST_WITH_OUTPUT:
  483. update_test_files = UPDATE_USING_TEST_OUTPUT;
  484. break;
  485. case OPT_UPDATE_GOLDEN_TEST_WITH_INPUT:
  486. update_test_files = UPDATE_USING_TEST_INPUT;
  487. break;
  488. case OPT_FILE:
  489. path = optarg;
  490. break;
  491. case OPT_TWEAK_COMMENTS:
  492. tweak_comments = optarg;
  493. break;
  494. case OPT_NO_PRETTY:
  495. serialize_flags &= ~XKB_KEYMAP_SERIALIZE_PRETTY;
  496. break;
  497. case 'h':
  498. usage(stdout, argv[0]);
  499. return EXIT_SUCCESS;
  500. default:
  501. usage(stderr, argv[0]);
  502. return EXIT_INVALID_USAGE;
  503. }
  504. }
  505. if (update_test_files != NO_UPDATE && path != NULL) {
  506. fprintf(stderr,
  507. "ERROR: --update* and --keymap are mutually exclusive.\n");
  508. usage(stderr, argv[0]);
  509. return EXIT_INVALID_USAGE;
  510. }
  511. if (path == NULL) {
  512. return x11_tests_run();
  513. } else {
  514. struct xkbcomp_roundtrip_data priv = {
  515. .path = path,
  516. .tweak_comments = tweak_comments,
  517. .serialize_flags = serialize_flags
  518. };
  519. return xvfb_wrapper(xkbcomp_roundtrip, &priv);
  520. }
  521. }