1 /*
2 * Copyright © 2013 Ran Benita <[email protected]>
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 "config.h"
25
26 #include <locale.h>
27 #include <stdbool.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include <xcb/xkb.h>
32
33 #include "xkbcommon/xkbcommon-x11.h"
34 #include "tools-common.h"
35
36 /*
37 * Note: This program only handles the core keyboard device for now.
38 * It should be straigtforward to change struct keyboard to a list of
39 * keyboards with device IDs, as in tools/interactive-evdev.c. This would
40 * require:
41 *
42 * - Initially listing the keyboard devices.
43 * - Listening to device changes.
44 * - Matching events to their devices.
45 *
46 * XKB itself knows about xinput1 devices, and most requests and events are
47 * device-specific.
48 *
49 * In order to list the devices and react to changes, you need xinput1/2.
50 * You also need xinput for the key press/release event, since the core
51 * protocol key press event does not carry a device ID to match on.
52 */
53
54 struct keyboard {
55 xcb_connection_t *conn;
56 uint8_t first_xkb_event;
57 struct xkb_context *ctx;
58
59 struct xkb_keymap *keymap;
60 struct xkb_state *state;
61 int32_t device_id;
62 };
63
64 static bool terminate;
65
66 static int
select_xkb_events_for_device(xcb_connection_t * conn,int32_t device_id)67 select_xkb_events_for_device(xcb_connection_t *conn, int32_t device_id)
68 {
69 enum {
70 required_events =
71 (XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY |
72 XCB_XKB_EVENT_TYPE_MAP_NOTIFY |
73 XCB_XKB_EVENT_TYPE_STATE_NOTIFY),
74
75 required_nkn_details =
76 (XCB_XKB_NKN_DETAIL_KEYCODES),
77
78 required_map_parts =
79 (XCB_XKB_MAP_PART_KEY_TYPES |
80 XCB_XKB_MAP_PART_KEY_SYMS |
81 XCB_XKB_MAP_PART_MODIFIER_MAP |
82 XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS |
83 XCB_XKB_MAP_PART_KEY_ACTIONS |
84 XCB_XKB_MAP_PART_VIRTUAL_MODS |
85 XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP),
86
87 required_state_details =
88 (XCB_XKB_STATE_PART_MODIFIER_BASE |
89 XCB_XKB_STATE_PART_MODIFIER_LATCH |
90 XCB_XKB_STATE_PART_MODIFIER_LOCK |
91 XCB_XKB_STATE_PART_GROUP_BASE |
92 XCB_XKB_STATE_PART_GROUP_LATCH |
93 XCB_XKB_STATE_PART_GROUP_LOCK),
94 };
95
96 static const xcb_xkb_select_events_details_t details = {
97 .affectNewKeyboard = required_nkn_details,
98 .newKeyboardDetails = required_nkn_details,
99 .affectState = required_state_details,
100 .stateDetails = required_state_details,
101 };
102
103 xcb_void_cookie_t cookie =
104 xcb_xkb_select_events_aux_checked(conn,
105 device_id,
106 required_events, /* affectWhich */
107 0, /* clear */
108 0, /* selectAll */
109 required_map_parts, /* affectMap */
110 required_map_parts, /* map */
111 &details); /* details */
112
113 xcb_generic_error_t *error = xcb_request_check(conn, cookie);
114 if (error) {
115 free(error);
116 return -1;
117 }
118
119 return 0;
120 }
121
122 static int
update_keymap(struct keyboard * kbd)123 update_keymap(struct keyboard *kbd)
124 {
125 struct xkb_keymap *new_keymap;
126 struct xkb_state *new_state;
127
128 new_keymap = xkb_x11_keymap_new_from_device(kbd->ctx, kbd->conn,
129 kbd->device_id,
130 XKB_KEYMAP_COMPILE_NO_FLAGS);
131 if (!new_keymap)
132 goto err_out;
133
134 new_state = xkb_x11_state_new_from_device(new_keymap, kbd->conn,
135 kbd->device_id);
136 if (!new_state)
137 goto err_keymap;
138
139 if (kbd->keymap)
140 printf("Keymap updated!\n");
141
142 xkb_state_unref(kbd->state);
143 xkb_keymap_unref(kbd->keymap);
144 kbd->keymap = new_keymap;
145 kbd->state = new_state;
146 return 0;
147
148 err_keymap:
149 xkb_keymap_unref(new_keymap);
150 err_out:
151 return -1;
152 }
153
154 static int
init_kbd(struct keyboard * kbd,xcb_connection_t * conn,uint8_t first_xkb_event,int32_t device_id,struct xkb_context * ctx)155 init_kbd(struct keyboard *kbd, xcb_connection_t *conn, uint8_t first_xkb_event,
156 int32_t device_id, struct xkb_context *ctx)
157 {
158 int ret;
159
160 kbd->conn = conn;
161 kbd->first_xkb_event = first_xkb_event;
162 kbd->ctx = ctx;
163 kbd->keymap = NULL;
164 kbd->state = NULL;
165 kbd->device_id = device_id;
166
167 ret = update_keymap(kbd);
168 if (ret)
169 goto err_out;
170
171 ret = select_xkb_events_for_device(conn, device_id);
172 if (ret)
173 goto err_state;
174
175 return 0;
176
177 err_state:
178 xkb_state_unref(kbd->state);
179 xkb_keymap_unref(kbd->keymap);
180 err_out:
181 return -1;
182 }
183
184 static void
deinit_kbd(struct keyboard * kbd)185 deinit_kbd(struct keyboard *kbd)
186 {
187 xkb_state_unref(kbd->state);
188 xkb_keymap_unref(kbd->keymap);
189 }
190
191 static void
process_xkb_event(xcb_generic_event_t * gevent,struct keyboard * kbd)192 process_xkb_event(xcb_generic_event_t *gevent, struct keyboard *kbd)
193 {
194 union xkb_event {
195 struct {
196 uint8_t response_type;
197 uint8_t xkbType;
198 uint16_t sequence;
199 xcb_timestamp_t time;
200 uint8_t deviceID;
201 } any;
202 xcb_xkb_new_keyboard_notify_event_t new_keyboard_notify;
203 xcb_xkb_map_notify_event_t map_notify;
204 xcb_xkb_state_notify_event_t state_notify;
205 } *event = (union xkb_event *) gevent;
206
207 if (event->any.deviceID != kbd->device_id)
208 return;
209
210 /*
211 * XkbNewKkdNotify and XkbMapNotify together capture all sorts of keymap
212 * updates (e.g. xmodmap, xkbcomp, setxkbmap), with minimal redundent
213 * recompilations.
214 */
215 switch (event->any.xkbType) {
216 case XCB_XKB_NEW_KEYBOARD_NOTIFY:
217 if (event->new_keyboard_notify.changed & XCB_XKB_NKN_DETAIL_KEYCODES)
218 update_keymap(kbd);
219 break;
220
221 case XCB_XKB_MAP_NOTIFY:
222 update_keymap(kbd);
223 break;
224
225 case XCB_XKB_STATE_NOTIFY:
226 xkb_state_update_mask(kbd->state,
227 event->state_notify.baseMods,
228 event->state_notify.latchedMods,
229 event->state_notify.lockedMods,
230 event->state_notify.baseGroup,
231 event->state_notify.latchedGroup,
232 event->state_notify.lockedGroup);
233 break;
234 }
235 }
236
237 static void
process_event(xcb_generic_event_t * gevent,struct keyboard * kbd)238 process_event(xcb_generic_event_t *gevent, struct keyboard *kbd)
239 {
240 switch (gevent->response_type) {
241 case XCB_KEY_PRESS: {
242 xcb_key_press_event_t *event = (xcb_key_press_event_t *) gevent;
243 xkb_keycode_t keycode = event->detail;
244
245 tools_print_keycode_state(kbd->state, NULL, keycode,
246 XKB_CONSUMED_MODE_XKB);
247
248 /* Exit on ESC. */
249 if (xkb_state_key_get_one_sym(kbd->state, keycode) == XKB_KEY_Escape)
250 terminate = true;
251 break;
252 }
253 default:
254 if (gevent->response_type == kbd->first_xkb_event)
255 process_xkb_event(gevent, kbd);
256 break;
257 }
258 }
259
260 static int
loop(xcb_connection_t * conn,struct keyboard * kbd)261 loop(xcb_connection_t *conn, struct keyboard *kbd)
262 {
263 while (!terminate) {
264 xcb_generic_event_t *event;
265
266 switch (xcb_connection_has_error(conn)) {
267 case 0:
268 break;
269 case XCB_CONN_ERROR:
270 fprintf(stderr,
271 "Closed connection to X server: connection error\n");
272 return -1;
273 case XCB_CONN_CLOSED_EXT_NOTSUPPORTED:
274 fprintf(stderr,
275 "Closed connection to X server: extension not supported\n");
276 return -1;
277 default:
278 fprintf(stderr,
279 "Closed connection to X server: error code %d\n",
280 xcb_connection_has_error(conn));
281 return -1;
282 }
283
284 event = xcb_wait_for_event(conn);
285 if (!event) {
286 continue;
287 }
288
289 process_event(event, kbd);
290 free(event);
291 }
292
293 return 0;
294 }
295
296 static int
create_capture_window(xcb_connection_t * conn)297 create_capture_window(xcb_connection_t *conn)
298 {
299 xcb_generic_error_t *error;
300 xcb_void_cookie_t cookie;
301 xcb_screen_t *screen =
302 xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
303 xcb_window_t window = xcb_generate_id(conn);
304 uint32_t values[2] = {
305 screen->white_pixel,
306 XCB_EVENT_MASK_KEY_PRESS,
307 };
308
309 cookie = xcb_create_window_checked(conn, XCB_COPY_FROM_PARENT,
310 window, screen->root,
311 10, 10, 100, 100, 1,
312 XCB_WINDOW_CLASS_INPUT_OUTPUT,
313 screen->root_visual,
314 XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
315 values);
316 if ((error = xcb_request_check(conn, cookie)) != NULL) {
317 free(error);
318 return -1;
319 }
320
321 cookie = xcb_map_window_checked(conn, window);
322 if ((error = xcb_request_check(conn, cookie)) != NULL) {
323 free(error);
324 return -1;
325 }
326
327 return 0;
328 }
329
330 int
main(int argc,char * argv[])331 main(int argc, char *argv[])
332 {
333 int ret;
334 xcb_connection_t *conn;
335 uint8_t first_xkb_event;
336 int32_t core_kbd_device_id;
337 struct xkb_context *ctx;
338 struct keyboard core_kbd;
339
340 if (argc != 1) {
341 ret = strcmp(argv[1], "--help");
342 fprintf(ret ? stderr : stdout, "Usage: %s [--help]\n", argv[0]);
343 if (ret)
344 fprintf(stderr, "unrecognized option: %s\n", argv[1]);
345 return ret ? EXIT_INVALID_USAGE : EXIT_SUCCESS;
346 }
347
348 setlocale(LC_ALL, "");
349
350 conn = xcb_connect(NULL, NULL);
351 if (!conn || xcb_connection_has_error(conn)) {
352 fprintf(stderr, "Couldn't connect to X server: error code %d\n",
353 conn ? xcb_connection_has_error(conn) : -1);
354 ret = -1;
355 goto err_out;
356 }
357
358 ret = xkb_x11_setup_xkb_extension(conn,
359 XKB_X11_MIN_MAJOR_XKB_VERSION,
360 XKB_X11_MIN_MINOR_XKB_VERSION,
361 XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
362 NULL, NULL, &first_xkb_event, NULL);
363 if (!ret) {
364 fprintf(stderr, "Couldn't setup XKB extension\n");
365 goto err_conn;
366 }
367
368 ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
369 if (!ctx) {
370 ret = -1;
371 fprintf(stderr, "Couldn't create xkb context\n");
372 goto err_conn;
373 }
374
375 core_kbd_device_id = xkb_x11_get_core_keyboard_device_id(conn);
376 if (core_kbd_device_id == -1) {
377 ret = -1;
378 fprintf(stderr, "Couldn't find core keyboard device\n");
379 goto err_ctx;
380 }
381
382 ret = init_kbd(&core_kbd, conn, first_xkb_event, core_kbd_device_id, ctx);
383 if (ret) {
384 fprintf(stderr, "Couldn't initialize core keyboard device\n");
385 goto err_ctx;
386 }
387
388 ret = create_capture_window(conn);
389 if (ret) {
390 fprintf(stderr, "Couldn't create a capture window\n");
391 goto err_core_kbd;
392 }
393
394 tools_disable_stdin_echo();
395 ret = loop(conn, &core_kbd);
396 tools_enable_stdin_echo();
397
398 err_core_kbd:
399 deinit_kbd(&core_kbd);
400 err_ctx:
401 xkb_context_unref(ctx);
402 err_conn:
403 xcb_disconnect(conn);
404 err_out:
405 exit(ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
406 }
407