1/* 2 * Copyright © 2013 Ran Benita <ran234@gmail.com> 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a 5 * copy of this software and associated documentation files (the "Software"), 6 * to deal in the Software without restriction, including without limitation 7 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 * and/or sell copies of the Software, and to permit persons to whom the 9 * Software is furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice (including the next 12 * paragraph) shall be included in all copies or substantial portions of the 13 * Software. 14 * 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 * DEALINGS IN THE SOFTWARE. 22 */ 23 24#include <locale.h> 25 26#include "xkbcommon/xkbcommon-x11.h" 27#include "test.h" 28 29#include <xcb/xkb.h> 30 31/* 32 * Note: This program only handles the core keyboard device for now. 33 * It should be straigtforward to change struct keyboard to a list of 34 * keyboards with device IDs, as in test/interactive-evdev.c. This would 35 * require: 36 * 37 * - Initially listing the keyboard devices. 38 * - Listening to device changes. 39 * - Matching events to their devices. 40 * 41 * XKB itself knows about xinput1 devices, and most requests and events are 42 * device-specific. 43 * 44 * In order to list the devices and react to changes, you need xinput1/2. 45 * You also need xinput for the key press/release event, since the core 46 * protocol key press event does not carry a device ID to match on. 47 */ 48 49struct keyboard { 50 xcb_connection_t *conn; 51 uint8_t first_xkb_event; 52 struct xkb_context *ctx; 53 54 struct xkb_keymap *keymap; 55 struct xkb_state *state; 56 int32_t device_id; 57}; 58 59static bool terminate; 60 61static int 62select_xkb_events_for_device(xcb_connection_t *conn, int32_t device_id) 63{ 64 enum { 65 required_events = 66 (XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY | 67 XCB_XKB_EVENT_TYPE_MAP_NOTIFY | 68 XCB_XKB_EVENT_TYPE_STATE_NOTIFY), 69 70 required_nkn_details = 71 (XCB_XKB_NKN_DETAIL_KEYCODES), 72 73 required_map_parts = 74 (XCB_XKB_MAP_PART_KEY_TYPES | 75 XCB_XKB_MAP_PART_KEY_SYMS | 76 XCB_XKB_MAP_PART_MODIFIER_MAP | 77 XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS | 78 XCB_XKB_MAP_PART_KEY_ACTIONS | 79 XCB_XKB_MAP_PART_VIRTUAL_MODS | 80 XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP), 81 82 required_state_details = 83 (XCB_XKB_STATE_PART_MODIFIER_BASE | 84 XCB_XKB_STATE_PART_MODIFIER_LATCH | 85 XCB_XKB_STATE_PART_MODIFIER_LOCK | 86 XCB_XKB_STATE_PART_GROUP_BASE | 87 XCB_XKB_STATE_PART_GROUP_LATCH | 88 XCB_XKB_STATE_PART_GROUP_LOCK), 89 }; 90 91 static const xcb_xkb_select_events_details_t details = { 92 .affectNewKeyboard = required_nkn_details, 93 .newKeyboardDetails = required_nkn_details, 94 .affectState = required_state_details, 95 .stateDetails = required_state_details, 96 }; 97 98 xcb_void_cookie_t cookie = 99 xcb_xkb_select_events_aux_checked(conn, 100 device_id, 101 required_events, /* affectWhich */ 102 0, /* clear */ 103 0, /* selectAll */ 104 required_map_parts, /* affectMap */ 105 required_map_parts, /* map */ 106 &details); /* details */ 107 108 xcb_generic_error_t *error = xcb_request_check(conn, cookie); 109 if (error) { 110 free(error); 111 return -1; 112 } 113 114 return 0; 115} 116 117static int 118update_keymap(struct keyboard *kbd) 119{ 120 struct xkb_keymap *new_keymap; 121 struct xkb_state *new_state; 122 123 new_keymap = xkb_x11_keymap_new_from_device(kbd->ctx, kbd->conn, 124 kbd->device_id, 0); 125 if (!new_keymap) 126 goto err_out; 127 128 new_state = xkb_x11_state_new_from_device(new_keymap, kbd->conn, 129 kbd->device_id); 130 if (!new_state) 131 goto err_keymap; 132 133 if (kbd->keymap) 134 printf("Keymap updated!\n"); 135 136 xkb_state_unref(kbd->state); 137 xkb_keymap_unref(kbd->keymap); 138 kbd->keymap = new_keymap; 139 kbd->state = new_state; 140 return 0; 141 142err_keymap: 143 xkb_keymap_unref(new_keymap); 144err_out: 145 return -1; 146} 147 148static int 149init_kbd(struct keyboard *kbd, xcb_connection_t *conn, uint8_t first_xkb_event, 150 int32_t device_id, struct xkb_context *ctx) 151{ 152 int ret; 153 154 kbd->conn = conn; 155 kbd->first_xkb_event = first_xkb_event; 156 kbd->ctx = ctx; 157 kbd->keymap = NULL; 158 kbd->state = NULL; 159 kbd->device_id = device_id; 160 161 ret = update_keymap(kbd); 162 if (ret) 163 goto err_out; 164 165 ret = select_xkb_events_for_device(conn, device_id); 166 if (ret) 167 goto err_state; 168 169 return 0; 170 171err_state: 172 xkb_state_unref(kbd->state); 173 xkb_keymap_unref(kbd->keymap); 174err_out: 175 return -1; 176} 177 178static void 179deinit_kbd(struct keyboard *kbd) 180{ 181 xkb_state_unref(kbd->state); 182 xkb_keymap_unref(kbd->keymap); 183} 184 185static void 186process_xkb_event(xcb_generic_event_t *gevent, struct keyboard *kbd) 187{ 188 union xkb_event { 189 struct { 190 uint8_t response_type; 191 uint8_t xkbType; 192 uint16_t sequence; 193 xcb_timestamp_t time; 194 uint8_t deviceID; 195 } any; 196 xcb_xkb_new_keyboard_notify_event_t new_keyboard_notify; 197 xcb_xkb_map_notify_event_t map_notify; 198 xcb_xkb_state_notify_event_t state_notify; 199 } *event = (union xkb_event *) gevent; 200 201 if (event->any.deviceID != kbd->device_id) 202 return; 203 204 /* 205 * XkbNewKkdNotify and XkbMapNotify together capture all sorts of keymap 206 * updates (e.g. xmodmap, xkbcomp, setxkbmap), with minimal redundent 207 * recompilations. 208 */ 209 switch (event->any.xkbType) { 210 case XCB_XKB_NEW_KEYBOARD_NOTIFY: 211 if (event->new_keyboard_notify.changed & XCB_XKB_NKN_DETAIL_KEYCODES) 212 update_keymap(kbd); 213 break; 214 215 case XCB_XKB_MAP_NOTIFY: 216 update_keymap(kbd); 217 break; 218 219 case XCB_XKB_STATE_NOTIFY: 220 xkb_state_update_mask(kbd->state, 221 event->state_notify.baseMods, 222 event->state_notify.latchedMods, 223 event->state_notify.lockedMods, 224 event->state_notify.baseGroup, 225 event->state_notify.latchedGroup, 226 event->state_notify.lockedGroup); 227 break; 228 } 229} 230 231static void 232process_event(xcb_generic_event_t *gevent, struct keyboard *kbd) 233{ 234 switch (gevent->response_type) { 235 case XCB_KEY_PRESS: { 236 xcb_key_press_event_t *event = (xcb_key_press_event_t *) gevent; 237 xkb_keycode_t keycode = event->detail; 238 239 test_print_keycode_state(kbd->state, NULL, keycode); 240 241 /* Exit on ESC. */ 242 if (keycode == 9) 243 terminate = true; 244 break; 245 } 246 default: 247 if (gevent->response_type == kbd->first_xkb_event) 248 process_xkb_event(gevent, kbd); 249 break; 250 } 251} 252 253static int 254loop(xcb_connection_t *conn, struct keyboard *kbd) 255{ 256 while (!terminate) { 257 xcb_generic_event_t *event; 258 259 switch (xcb_connection_has_error(conn)) { 260 case 0: 261 break; 262 case XCB_CONN_ERROR: 263 fprintf(stderr, 264 "Closed connection to X server: connection error\n"); 265 return -1; 266 case XCB_CONN_CLOSED_EXT_NOTSUPPORTED: 267 fprintf(stderr, 268 "Closed connection to X server: extension not supported\n"); 269 return -1; 270 default: 271 fprintf(stderr, 272 "Closed connection to X server: error code %d\n", 273 xcb_connection_has_error(conn)); 274 return -1; 275 } 276 277 event = xcb_wait_for_event(conn); 278 process_event(event, kbd); 279 free(event); 280 } 281 282 return 0; 283} 284 285static int 286create_capture_window(xcb_connection_t *conn) 287{ 288 xcb_generic_error_t *error; 289 xcb_void_cookie_t cookie; 290 xcb_screen_t *screen = 291 xcb_setup_roots_iterator(xcb_get_setup(conn)).data; 292 xcb_window_t window = xcb_generate_id(conn); 293 uint32_t values[2] = { 294 screen->white_pixel, 295 XCB_EVENT_MASK_KEY_PRESS, 296 }; 297 298 cookie = xcb_create_window_checked(conn, XCB_COPY_FROM_PARENT, 299 window, screen->root, 300 10, 10, 100, 100, 1, 301 XCB_WINDOW_CLASS_INPUT_OUTPUT, 302 screen->root_visual, 303 XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, 304 values); 305 if ((error = xcb_request_check(conn, cookie)) != NULL) { 306 free(error); 307 return -1; 308 } 309 310 cookie = xcb_map_window_checked(conn, window); 311 if ((error = xcb_request_check(conn, cookie)) != NULL) { 312 free(error); 313 return -1; 314 } 315 316 return 0; 317} 318 319int 320main(int argc, char *argv[]) 321{ 322 int ret; 323 xcb_connection_t *conn; 324 uint8_t first_xkb_event; 325 int32_t core_kbd_device_id; 326 struct xkb_context *ctx; 327 struct keyboard core_kbd; 328 329 setlocale(LC_ALL, ""); 330 331 conn = xcb_connect(NULL, NULL); 332 if (!conn || xcb_connection_has_error(conn)) { 333 fprintf(stderr, "Couldn't connect to X server: error code %d\n", 334 conn ? xcb_connection_has_error(conn) : -1); 335 ret = -1; 336 goto err_out; 337 } 338 339 ret = xkb_x11_setup_xkb_extension(conn, 340 XKB_X11_MIN_MAJOR_XKB_VERSION, 341 XKB_X11_MIN_MINOR_XKB_VERSION, 342 XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, 343 NULL, NULL, &first_xkb_event, NULL); 344 if (!ret) { 345 fprintf(stderr, "Couldn't setup XKB extension\n"); 346 goto err_conn; 347 } 348 349 ctx = test_get_context(0); 350 if (!ctx) { 351 ret = -1; 352 fprintf(stderr, "Couldn't create xkb context\n"); 353 goto err_conn; 354 } 355 356 core_kbd_device_id = xkb_x11_get_core_keyboard_device_id(conn); 357 if (core_kbd_device_id == -1) { 358 ret = -1; 359 fprintf(stderr, "Couldn't find core keyboard device\n"); 360 goto err_ctx; 361 } 362 363 ret = init_kbd(&core_kbd, conn, first_xkb_event, core_kbd_device_id, ctx); 364 if (ret) { 365 fprintf(stderr, "Couldn't initialize core keyboard device\n"); 366 goto err_ctx; 367 } 368 369 ret = create_capture_window(conn); 370 if (ret) { 371 fprintf(stderr, "Couldn't create a capture window\n"); 372 goto err_core_kbd; 373 } 374 375 system("stty -echo"); 376 ret = loop(conn, &core_kbd); 377 system("stty echo"); 378 379err_core_kbd: 380 deinit_kbd(&core_kbd); 381err_ctx: 382 xkb_context_unref(ctx); 383err_conn: 384 xcb_disconnect(conn); 385err_out: 386 exit(ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE); 387} 388