| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958 |
- /*
- * Copyright © 2013 Ran Benita <ran234@gmail.com>
- * SPDX-License-Identifier: MIT
- */
- #include "config.h"
- #include <assert.h>
- #include <getopt.h>
- #include <locale.h>
- #include <stdbool.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <xcb/xkb.h>
- #include <xcb/xproto.h>
- #include "xkbcommon/xkbcommon.h"
- #include "xkbcommon/xkbcommon-x11.h"
- #include "xkbcommon/xkbcommon-compose.h"
- #include "src/keymap-formats.h"
- #include "src/utils.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;
- struct xkb_machine *machine;
- struct xkb_events *events;
- struct xkb_compose_state *compose_state;
- xkb_keycode_t repeated_key;
- int32_t device_id;
- };
- static bool terminate;
- static enum xkb_keymap_compile_flags compile_flags =
- (enum xkb_keymap_compile_flags) DEFAULT_KEYMAP_COMPILE_FLAGS;
- #ifdef KEYMAP_DUMP
- static_assert(DEFAULT_OUTPUT_KEYMAP_FORMAT == XKB_KEYMAP_USE_ORIGINAL_FORMAT,
- "Out of sync usage()");
- static enum xkb_keymap_format keymap_format = DEFAULT_OUTPUT_KEYMAP_FORMAT;
- static enum xkb_keymap_serialize_flags serialize_flags =
- (enum xkb_keymap_serialize_flags) DEFAULT_KEYMAP_SERIALIZE_FLAGS;
- #else
- static bool detect_repeat = false;
- static bool use_events_api = true;
- static enum xkb_consumed_mode consumed_mode = XKB_CONSUMED_MODE_XKB;
- static enum print_state_options print_options = DEFAULT_PRINT_OPTIONS;
- static bool report_state_changes = true;
- static bool use_local_state = false;
- static struct xkb_machine_options machine_options = xkb_machine_options_new();
- static struct xkb_keymap *custom_keymap = NULL;
- #endif
- 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)
- {
- #ifndef KEYMAP_DUMP
- if (custom_keymap) {
- if (!kbd->keymap) {
- /* Keymap is only parsed the one time, then we just reference it. */
- kbd->keymap = xkb_keymap_ref(custom_keymap);
- }
- } else {
- #endif
- const bool update = !!kbd->keymap && !!kbd->state;
- xkb_keymap_unref(kbd->keymap);
- kbd->keymap = xkb_x11_keymap_new_from_device(kbd->ctx, kbd->conn,
- kbd->device_id,
- XKB_KEYMAP_COMPILE_NO_FLAGS);
- if (!kbd->keymap)
- return -1;
- if (update)
- printf("Keymap updated!\n");
- #ifndef KEYMAP_DUMP
- }
- if (!use_local_state) {
- #endif
- /* Reset state on keymap reset */
- xkb_state_unref(kbd->state);
- kbd->state = xkb_x11_state_new_from_device(kbd->keymap, kbd->conn,
- kbd->device_id);
- if (!kbd->state)
- return -1;
- #ifndef KEYMAP_DUMP
- } else {
- /* Ignore state from server and reset only if state if undefined. */
- if (!kbd->state) {
- kbd->state = xkb_state_new(kbd->keymap);
- if (!kbd->state)
- return -1;
- }
- const struct xkb_state_components_update components_update = {
- .size = sizeof(components_update),
- .components = XKB_STATE_CONTROLS,
- .affect_controls = machine_options.controls.boolean.affect,
- .controls = machine_options.controls.boolean.flags,
- };
- const struct xkb_state_update update = {
- .size = sizeof(update),
- .components = &components_update,
- };
- if (use_events_api) {
- if (!kbd->machine) {
- struct xkb_machine_builder * machine_builder =
- xkb_machine_builder_new_from_options(kbd->keymap, &machine_options);
- if (!machine_builder) {
- fprintf(stderr, "ERROR: Couldn't create xkb state machine builder\n");
- return - 1;
- }
- kbd->machine = xkb_machine_new(machine_builder);
- xkb_machine_builder_destroy(machine_builder);
- if (!kbd->machine)
- return -1;
- }
- if (!kbd->events) {
- kbd->events = xkb_events_new_batch(kbd->ctx,
- XKB_EVENTS_NO_FLAGS);
- if (!kbd->events)
- return -1;
- }
- const int ret = xkb_machine_process_synthetic(kbd->machine,
- &update, kbd->events);
- if (ret)
- return ret;
- const struct xkb_event *event;
- while ((event = xkb_events_next(kbd->events))) {
- xkb_state_update_event(kbd->state, event);
- }
- } else {
- return xkb_state_update_synthetic(kbd->state, &update, NULL);
- }
- }
- #endif
- return 0;
- }
- static int
- init_kbd(struct keyboard *kbd, xcb_connection_t *conn, uint8_t first_xkb_event,
- int32_t device_id, struct xkb_context *ctx,
- struct xkb_compose_table *compose_table)
- {
- int ret;
- kbd->conn = conn;
- kbd->first_xkb_event = first_xkb_event;
- kbd->ctx = ctx;
- kbd->keymap = NULL;
- kbd->state = NULL;
- kbd->compose_state = NULL;
- kbd->repeated_key = XKB_KEYCODE_INVALID;
- kbd->device_id = device_id;
- ret = update_keymap(kbd);
- if (ret)
- goto err_out;
- #ifdef KEYMAP_DUMP
- /* Dump the keymap */
- char *dump = xkb_keymap_get_as_string2(kbd->keymap, keymap_format,
- serialize_flags);
- fprintf(stdout, "%s", dump);
- free(dump);
- terminate = true;
- return 0;
- #endif
- if (compose_table)
- kbd->compose_state = xkb_compose_state_new(compose_table,
- XKB_COMPOSE_STATE_NO_FLAGS);
- ret = select_xkb_events_for_device(conn, device_id);
- if (ret)
- goto err_state;
- return 0;
- err_state:
- xkb_state_unref(kbd->state);
- xkb_machine_unref(kbd->machine);
- xkb_events_destroy(kbd->events);
- xkb_compose_state_unref(kbd->compose_state);
- xkb_keymap_unref(kbd->keymap);
- err_out:
- return -1;
- }
- static void
- deinit_kbd(struct keyboard *kbd)
- {
- xkb_state_unref(kbd->state);
- xkb_machine_unref(kbd->machine);
- xkb_events_destroy(kbd->events);
- xkb_compose_state_unref(kbd->compose_state);
- xkb_keymap_unref(kbd->keymap);
- }
- #ifndef KEYMAP_DUMP
- 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: {
- if (use_local_state) {
- /* Ignore state update if using a local state machine */
- break;
- }
- const enum xkb_state_component changed =
- 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);
- if (report_state_changes)
- tools_print_state_changes(NULL, kbd->state, changed, print_options);
- break;
- }
- default:
- /* Ignore */
- 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;
- const xkb_keycode_t keycode = event->detail;
- const bool repeated = detect_repeat && kbd->repeated_key == keycode;
- if (!repeated)
- kbd->repeated_key = keycode;
- const enum xkb_key_direction direction = (repeated)
- ? XKB_KEY_REPEATED
- : XKB_KEY_DOWN;
- if (use_local_state && use_events_api) {
- /* Run our local state machine with the event API */
- const int ret = xkb_machine_process_key(kbd->machine,
- keycode, direction,
- kbd->events);
- if (ret) {
- fprintf(stderr, "ERROR: could not update the state machine\n");
- // TODO: better error handling
- } else {
- tools_print_events(NULL, kbd->state, kbd->events,
- kbd->compose_state, consumed_mode,
- print_options, report_state_changes);
- }
- } else {
- if (kbd->compose_state) {
- xkb_keysym_t keysym = xkb_state_key_get_one_sym(kbd->state,
- keycode);
- xkb_compose_state_feed(kbd->compose_state, keysym);
- }
- tools_print_keycode_state(NULL, kbd->state, kbd->compose_state,
- keycode, direction,
- consumed_mode, print_options);
- if (kbd->compose_state) {
- enum xkb_compose_status 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 (use_local_state) {
- /* Run our local state machine with the legacy API */
- const enum xkb_state_component changed =
- xkb_state_update_key(kbd->state, keycode, direction);
- if (changed && report_state_changes)
- tools_print_state_changes(NULL, kbd->state,
- changed, print_options);
- }
- }
- /* Exit on ESC. */
- if (xkb_state_key_get_one_sym(kbd->state, keycode) == XKB_KEY_Escape)
- terminate = true;
- break;
- }
- case XCB_KEY_RELEASE: {
- xcb_key_press_event_t *event = (xcb_key_press_event_t *) gevent;
- const xkb_keycode_t keycode = event->detail;
- if (kbd->repeated_key == keycode)
- kbd->repeated_key = XKB_KEYCODE_INVALID;
- if (use_local_state && use_events_api) {
- /* Run our local state machine */
- const int ret = xkb_machine_process_key(kbd->machine,
- keycode, XKB_KEY_UP,
- kbd->events);
- if (ret) {
- fprintf(stderr, "ERROR: could not update the state machine\n");
- // TODO: better error handling
- } else {
- tools_print_events(NULL, kbd->state, kbd->events,
- kbd->compose_state, consumed_mode,
- print_options, report_state_changes);
- }
- } else {
- tools_print_keycode_state(NULL, kbd->state, kbd->compose_state,
- keycode, XKB_KEY_UP, consumed_mode,
- print_options);
- if (use_local_state) {
- /* Run our local state machine with the legacy API */
- const enum xkb_state_component changed =
- xkb_state_update_key(kbd->state, keycode, XKB_KEY_UP);
- if (changed && report_state_changes)
- tools_print_state_changes(NULL, kbd->state,
- changed, print_options);
- }
- }
- 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 | XCB_EVENT_MASK_KEY_RELEASE,
- };
- 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;
- }
- #endif
- static void
- usage(FILE *fp, char *progname)
- {
- fprintf(fp,
- "Usage: %s [--help] [--version] [--verbose]"
- #ifndef KEYMAP_DUMP
- " [--uniline] [--multiline] [--consumed-mode={xkb|gtk}] [--no-state-report]"
- #endif
- " [--format FORMAT] [--strict]"
- #ifdef KEYMAP_DUMP
- " [--no-pretty] [--drop-unused]"
- #else
- " [--enable-compose]"
- " [--local-state] [--legacy-state-api true|false]"
- " [--controls CONTROLS] [--modifiers-mapping MAPPING]"
- " [--shortcuts-mask MASK] [--shortcuts-mapping]"
- " [--keymap FILE]"
- #endif
- "\n",
- progname);
- fprintf(fp,
- #ifndef KEYMAP_DUMP
- " --enable-compose enable Compose\n"
- " --local-state enable local state handling and ignore modifiers/layouts\n"
- " state updates from the X11 server\n"
- " --legacy-state-api [=true|false]\n"
- " use the legacy state API instead of the event API.\n"
- " It implies --local-state if explicitly disabled.\n"
- " Default: false.\n"
- " --controls [<CONTROLS>]\n"
- " use the given keyboard controls; available values are:\n"
- " sticky-keys, sticky-keys-no-simultaneous,\n"
- " sticky-keys-latch-to-lock, latch-simultaneous and overlay{1-2}.\n"
- " It implies --local-state and --legacy-state-api=false.\n"
- " --modifiers-mapping <MAPPING>\n"
- " use the given modifiers mapping.\n"
- " <MAPPING> is a comma-separated list of modifiers masks\n"
- " mappings with format \"source:target\", e.g.\n"
- " \"Control+Alt:LevelThree,Alt:Meta\".\n"
- " It implies --local-state and --legacy-state-api=false.\n"
- " --shortcuts-mask <MASK>\n"
- " use the given modifier mask to enable selecting a specific\n"
- " layout (see --shortcuts-mapping) when some of these modifiers\n"
- " are active. The modifier mask is a plus-separated list of\n"
- " modifiers names, e.g. \"Control+Alt+Super\".\n"
- " It implies --local-state and --legacy-state-api=false.\n"
- " --shortcuts-mapping <MAPPING>\n"
- " use the given layout mapping to enable selecting a specific\n"
- " layout when some modifiers are active (see --shortcuts-mask).\n"
- " <MAPPING> is a comma-separated list of 1-indexed layout\n"
- " indices mappings with format \"source:target\", e.g. \"2:1,3:1\".\n"
- " It implies --local-state and --legacy-state-api=false.\n"
- " --keymap [<FILE>] use the given keymap instead of the keymap from the compositor.\n"
- " It implies --local-state.\n"
- " If <FILE> is \"-\" or missing, then load from stdin.\n"
- #endif
- " --format <FORMAT> use keymap format <FORMAT> (default: '%s')\n"
- " --strict parse using strict mode\n"
- #ifdef KEYMAP_DUMP
- " --no-pretty do not pretty-print when serializing a keymap\n"
- " --drop-unused disable unused bits serialization\n"
- #else
- " -1, --uniline enable uniline event output\n"
- " -*, --multiline enable multiline event output\n"
- " --consumed-mode={xkb|gtk}\n"
- " select the consumed modifiers mode (default: xkb)\n"
- " --no-state-report do not report changes to the state\n"
- #endif
- " --verbose enable verbose debugging output\n"
- " --help display this help and exit\n"
- " --version print version information and exit\n",
- xkb_keymap_get_format_label(DEFAULT_INPUT_KEYMAP_FORMAT)
- );
- }
- int
- main(int argc, char *argv[])
- {
- int ret;
- bool verbose = false;
- xcb_connection_t *conn;
- uint8_t first_xkb_event;
- int32_t core_kbd_device_id;
- struct xkb_context *ctx;
- struct keyboard core_kbd = {0};
- struct xkb_compose_table *compose_table = NULL;
- #ifndef KEYMAP_DUMP
- bool with_keymap_file = false;
- enum xkb_keymap_format keymap_format = DEFAULT_INPUT_KEYMAP_FORMAT;
- const char *keymap_path = NULL;
- bool with_compose = false;
- const char *locale;
- #endif
- enum options {
- OPT_VERBOSE,
- OPT_UNILINE,
- OPT_MULTILINE,
- OPT_CONSUMED_MODE,
- OPT_NO_STATE_REPORT,
- OPT_COMPOSE,
- OPT_LOCAL_STATE,
- OPT_LEGACY_STATE_API,
- OPT_CONTROLS,
- OPT_MODIFIERS_TWEAK_MAPPING,
- OPT_SHORTCUTS_TWEAK_MASK,
- OPT_SHORTCUTS_TWEAK_MAPPING,
- OPT_KEYMAP_FORMAT,
- OPT_KEYMAP_STRICT_PARSER,
- OPT_KEYMAP_NO_PRETTY,
- OPT_KEYMAP_DROP_UNUSED,
- OPT_KEYMAP,
- };
- static struct option opts[] = {
- {"help", no_argument, 0, 'h'},
- {"version", no_argument, 0, 'V'},
- {"verbose", no_argument, 0, OPT_VERBOSE},
- {"format", required_argument, 0, OPT_KEYMAP_FORMAT},
- {"strict", no_argument, 0, OPT_KEYMAP_STRICT_PARSER},
- #ifdef KEYMAP_DUMP
- {"no-pretty", no_argument, 0, OPT_KEYMAP_NO_PRETTY},
- {"drop-unused", no_argument, 0, OPT_KEYMAP_DROP_UNUSED},
- #else
- {"uniline", no_argument, 0, OPT_UNILINE},
- {"multiline", no_argument, 0, OPT_MULTILINE},
- {"consumed-mode", required_argument, 0, OPT_CONSUMED_MODE},
- {"no-state-report", no_argument, 0, OPT_NO_STATE_REPORT},
- {"enable-compose", no_argument, 0, OPT_COMPOSE},
- {"local-state", no_argument, 0, OPT_LOCAL_STATE},
- {"legacy-state-api", optional_argument, 0, OPT_LEGACY_STATE_API},
- {"controls", required_argument, 0, OPT_CONTROLS},
- {"modifiers-mapping", required_argument, 0, OPT_MODIFIERS_TWEAK_MAPPING},
- {"shortcuts-mask", required_argument, 0, OPT_SHORTCUTS_TWEAK_MASK},
- {"shortcuts-mapping", required_argument, 0, OPT_SHORTCUTS_TWEAK_MAPPING},
- {"keymap", optional_argument, 0, OPT_KEYMAP},
- #endif
- {0, 0, 0, 0},
- };
- setlocale(LC_ALL, "");
- #ifndef KEYMAP_DUMP
- /* Ensure synced with usage() and man page */
- assert(use_events_api);
- assert(consumed_mode == XKB_CONSUMED_MODE_XKB);
- #endif
- while (1) {
- int opt;
- int option_index = 0;
- opt = getopt_long(argc, argv, "*1hV", opts, &option_index);
- if (opt == -1)
- break;
- switch (opt) {
- case OPT_VERBOSE:
- verbose = true;
- break;
- case OPT_KEYMAP_FORMAT:
- /*
- * This is either:
- * - the input format for the interactive tool
- * - the output format for the dump tool
- */
- keymap_format = xkb_keymap_parse_format(optarg);
- if (!keymap_format) {
- fprintf(stderr, "ERROR: invalid --format \"%s\"\n", optarg);
- goto invalid_usage;
- }
- break;
- case OPT_KEYMAP_STRICT_PARSER:
- compile_flags |= XKB_KEYMAP_COMPILE_STRICT_MODE;
- break;
- #ifdef KEYMAP_DUMP
- case OPT_KEYMAP_NO_PRETTY:
- serialize_flags &= ~XKB_KEYMAP_SERIALIZE_PRETTY;
- break;
- case OPT_KEYMAP_DROP_UNUSED:
- serialize_flags &= ~XKB_KEYMAP_SERIALIZE_KEEP_UNUSED;
- break;
- #else
- case OPT_COMPOSE:
- with_compose = true;
- break;
- case OPT_LOCAL_STATE:
- local_state:
- use_local_state = true;
- break;
- case OPT_LEGACY_STATE_API: {
- bool legacy_api = true;
- if (!tools_parse_bool(optarg, TOOLS_ARG_OPTIONAL, &legacy_api))
- goto invalid_usage;
- use_events_api = !legacy_api;
- if (use_events_api)
- goto local_state;
- break;
- }
- case OPT_CONTROLS:
- if (!tools_parse_controls(optarg, &machine_options))
- goto invalid_usage;
- /* --local-state and --legacy-state-api=false are implied */
- use_events_api = true;
- goto local_state;
- case OPT_MODIFIERS_TWEAK_MAPPING:
- if (!tools_parse_modifiers_mappings(optarg, &machine_options))
- goto invalid_usage;
- /* --local-state and --legacy-state-api=false are implied */
- use_events_api = true;
- goto local_state;
- case OPT_SHORTCUTS_TWEAK_MASK:
- if (!tools_parse_shortcuts_mask(optarg, &machine_options))
- goto invalid_usage;
- /* --local-state and --legacy-state-api=false are implied */
- use_events_api = true;
- goto local_state;
- case OPT_SHORTCUTS_TWEAK_MAPPING:
- if (!tools_parse_shortcuts_mappings(optarg, &machine_options))
- goto invalid_usage;
- /* --local-state and --legacy-state-api=false are implied */
- use_events_api = true;
- goto local_state;
- case OPT_KEYMAP:
- with_keymap_file = true;
- /* Optional arguments require `=`, but we want to make this
- * requirement optional too, so that both `--keymap=xxx` and
- * `--keymap xxx` work. */
- if (!optarg && argv[optind] &&
- (argv[optind][0] != '-' || strcmp(argv[optind], "-") == 0 )) {
- keymap_path = argv[optind++];
- } else {
- keymap_path = optarg;
- }
- /* --local-state is implied */
- goto local_state;
- case '1':
- case OPT_UNILINE:
- print_options |= PRINT_UNILINE;
- break;
- case '*':
- case OPT_MULTILINE:
- print_options &= ~PRINT_UNILINE;
- 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]);
- ret = EXIT_INVALID_USAGE;
- goto error_parse_args;
- }
- break;
- case OPT_NO_STATE_REPORT:
- report_state_changes = false;
- break;
- #endif
- case 'h':
- usage(stdout, argv[0]);
- ret = EXIT_SUCCESS;
- goto error_parse_args;
- case 'V':
- printf("%s\n", LIBXKBCOMMON_VERSION);
- ret = EXIT_SUCCESS;
- goto error_parse_args;
- default:
- invalid_usage:
- usage(stderr, argv[0]);
- ret = EXIT_INVALID_USAGE;
- goto error_parse_args;
- }
- }
- #ifndef KEYMAP_DUMP
- if (optind < argc && !isempty(argv[optind])) {
- /* Some positional arguments left: use as a keymap input */
- if (keymap_path)
- goto too_much_arguments;
- keymap_path = argv[optind++];
- if (optind < argc) {
- /* Further positional arguments is an error */
- too_much_arguments:
- fprintf(stderr, "ERROR: Too many positional arguments\n");
- goto invalid_usage;
- }
- with_keymap_file = true;
- } else if (is_pipe_or_regular_file(STDIN_FILENO) && !with_keymap_file) {
- /* No positional argument: piping detected */
- with_keymap_file = true;
- }
- if (with_keymap_file) {
- /* --local-state is implied with custom keymap */
- use_local_state = true;
- }
- if (isempty(keymap_path) || strcmp(keymap_path, "-") == 0)
- keymap_path = NULL;
- #endif
- 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;
- }
- #ifndef KEYMAP_DUMP
- /*
- * Enable detection of repeated keys
- *
- * The X server usually generates both press and release events whenever an
- * autorepeating key is held down. With the following change, client will
- * receive: Press → Press → Press → Release
- * instead of: Press → Release → Press → Release → Press → Release
- *
- * See: https://www.x.org/releases/X11R7.7/doc/kbproto/xkbproto.html#Detectable_Autorepeat
- */
- xcb_xkb_per_client_flags_cookie_t pcf = xcb_xkb_per_client_flags(
- conn, XCB_XKB_ID_USE_CORE_KBD,
- XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT,
- XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT,
- 0, 0, 0
- );
- xcb_xkb_per_client_flags_reply_t *pcf_reply;
- if (!(pcf_reply = xcb_xkb_per_client_flags_reply(conn, pcf, NULL))) {
- fprintf(stderr, "ERROR: Couldn't set X11 detectable auto-repeat\n");
- } else {
- detect_repeat =
- (pcf_reply->value & XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT);
- if (!detect_repeat)
- fprintf(stderr,
- "WARNING: Detectable auto-repeat is not supported by "
- "the X server\n");
- free(pcf_reply);
- }
- #endif
- ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
- if (!ctx) {
- ret = -1;
- fprintf(stderr, "Couldn't create xkb context\n");
- goto err_conn;
- }
- if (verbose)
- tools_enable_verbose_logging(ctx);
- #ifndef KEYMAP_DUMP
- if (with_keymap_file) {
- FILE *file = NULL;
- if (keymap_path) {
- /* Read from regular file */
- file = fopen(keymap_path, "rb");
- } else {
- /* Read from stdin */
- file = tools_read_stdin();
- }
- if (!file) {
- fprintf(stderr, "ERROR: Failed to open keymap file \"%s\": %s\n",
- keymap_path ? keymap_path : "stdin", strerror(errno));
- xkb_context_unref(ctx);
- goto err_out;
- }
- custom_keymap = xkb_keymap_new_from_file(ctx, file, keymap_format,
- compile_flags);
- if (!custom_keymap)
- goto err_keymap;
- fclose(file);
- }
- 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 err_compose;
- }
- } else {
- compose_table = NULL;
- }
- #endif
- 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, compose_table);
- if (ret) {
- fprintf(stderr, "Couldn't initialize core keyboard device\n");
- goto err_ctx;
- }
- #ifndef KEYMAP_DUMP
- ret = create_capture_window(conn);
- if (ret) {
- fprintf(stderr, "Couldn't create a capture window\n");
- goto err_window;
- }
- tools_disable_stdin_echo();
- ret = loop(conn, &core_kbd);
- tools_enable_stdin_echo();
- err_window:
- xkb_compose_table_unref(compose_table);
- err_compose:
- xkb_keymap_unref(custom_keymap);
- err_keymap:
- #endif
- deinit_kbd(&core_kbd);
- err_ctx:
- xkb_context_unref(ctx);
- err_conn:
- xcb_disconnect(conn);
- err_out:
- ret = (ret >= 0) ? EXIT_SUCCESS : EXIT_FAILURE;
- error_parse_args:
- #ifndef KEYMAP_DUMP
- xkb_machine_options_free(&machine_options);
- #endif
- exit(ret);
- }
|