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