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
49 struct 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
59 static bool terminate;
60
61 static int
select_xkb_events_for_device(xcb_connection_t * conn,int32_t device_id)62 select_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
117 static int
update_keymap(struct keyboard * kbd)118 update_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
142 err_keymap:
143 xkb_keymap_unref(new_keymap);
144 err_out:
145 return -1;
146 }
147
148 static int
init_kbd(struct keyboard * kbd,xcb_connection_t * conn,uint8_t first_xkb_event,int32_t device_id,struct xkb_context * ctx)149 init_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
171 err_state:
172 xkb_state_unref(kbd->state);
173 xkb_keymap_unref(kbd->keymap);
174 err_out:
175 return -1;
176 }
177
178 static void
deinit_kbd(struct keyboard * kbd)179 deinit_kbd(struct keyboard *kbd)
180 {
181 xkb_state_unref(kbd->state);
182 xkb_keymap_unref(kbd->keymap);
183 }
184
185 static void
process_xkb_event(xcb_generic_event_t * gevent,struct keyboard * kbd)186 process_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
231 static void
process_event(xcb_generic_event_t * gevent,struct keyboard * kbd)232 process_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
253 static int
loop(xcb_connection_t * conn,struct keyboard * kbd)254 loop(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
285 static int
create_capture_window(xcb_connection_t * conn)286 create_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
319 int
main(int argc,char * argv[])320 main(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
379 err_core_kbd:
380 deinit_kbd(&core_kbd);
381 err_ctx:
382 xkb_context_unref(ctx);
383 err_conn:
384 xcb_disconnect(conn);
385 err_out:
386 exit(ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
387 }
388