x11: add a couple of tests
[platform/upstream/libxkbcommon.git] / test / 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 <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
62 select_xkb_events_for_device(xcb_connection_t *conn, int32_t device_id)
63 {
64     static const xcb_xkb_map_part_t required_map_parts =
65         (XCB_XKB_MAP_PART_KEY_TYPES |
66          XCB_XKB_MAP_PART_KEY_SYMS |
67          XCB_XKB_MAP_PART_MODIFIER_MAP |
68          XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS |
69          XCB_XKB_MAP_PART_KEY_ACTIONS |
70          XCB_XKB_MAP_PART_VIRTUAL_MODS |
71          XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP);
72
73     static const xcb_xkb_event_type_t required_events =
74         (XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY |
75          XCB_XKB_EVENT_TYPE_MAP_NOTIFY |
76          XCB_XKB_EVENT_TYPE_STATE_NOTIFY);
77
78     xcb_void_cookie_t cookie =
79         xcb_xkb_select_events_checked(conn,
80                                       device_id,
81                                       required_events,
82                                       0,
83                                       required_events,
84                                       required_map_parts,
85                                       required_map_parts,
86                                       0);
87
88     xcb_generic_error_t *error = xcb_request_check(conn, cookie);
89     if (error) {
90         free(error);
91         return -1;
92     }
93
94     return 0;
95 }
96
97 static int
98 update_keymap(struct keyboard *kbd)
99 {
100     struct xkb_keymap *new_keymap;
101     struct xkb_state *new_state;
102
103     new_keymap = xkb_x11_keymap_new_from_device(kbd->ctx, kbd->conn,
104                                                 kbd->device_id, 0);
105     if (!new_keymap)
106         goto err_out;
107
108     new_state = xkb_x11_state_new_from_device(new_keymap, kbd->conn,
109                                               kbd->device_id);
110     if (!new_state)
111         goto err_keymap;
112
113     if (kbd->keymap)
114         printf("Keymap updated!\n");
115
116     xkb_state_unref(kbd->state);
117     xkb_keymap_unref(kbd->keymap);
118     kbd->keymap = new_keymap;
119     kbd->state = new_state;
120     return 0;
121
122 err_keymap:
123     xkb_keymap_unref(new_keymap);
124 err_out:
125     return -1;
126 }
127
128 static int
129 init_kbd(struct keyboard *kbd, xcb_connection_t *conn, uint8_t first_xkb_event,
130          int32_t device_id, struct xkb_context *ctx)
131 {
132     int ret;
133
134     kbd->conn = conn;
135     kbd->first_xkb_event = first_xkb_event;
136     kbd->ctx = ctx;
137     kbd->keymap = NULL;
138     kbd->state = NULL;
139     kbd->device_id = device_id;
140
141     ret = update_keymap(kbd);
142     if (ret)
143         goto err_out;
144
145     ret = select_xkb_events_for_device(conn, device_id);
146     if (ret)
147         goto err_state;
148
149     return 0;
150
151 err_state:
152     xkb_state_unref(kbd->state);
153     xkb_keymap_unref(kbd->keymap);
154 err_out:
155     return -1;
156 }
157
158 static void
159 deinit_kbd(struct keyboard *kbd)
160 {
161     xkb_state_unref(kbd->state);
162     xkb_keymap_unref(kbd->keymap);
163 }
164
165 static void
166 process_xkb_event(xcb_generic_event_t *gevent, struct keyboard *kbd)
167 {
168     union xkb_event {
169         struct {
170             uint8_t response_type;
171             uint8_t xkbType;
172             uint16_t sequence;
173             xcb_timestamp_t time;
174             uint8_t deviceID;
175         } any;
176         xcb_xkb_new_keyboard_notify_event_t new_keyboard_notify;
177         xcb_xkb_map_notify_event_t map_notify;
178         xcb_xkb_state_notify_event_t state_notify;
179     } *event = (union xkb_event *) gevent;
180
181     if (event->any.deviceID != kbd->device_id)
182         return;
183
184     /*
185      * XkbNewKkdNotify and XkbMapNotify together capture all sorts of keymap
186      * updates (e.g. xmodmap, xkbcomp, setxkbmap), with minimal redundent
187      * recompilations.
188      */
189     switch (event->any.xkbType) {
190     case XCB_XKB_NEW_KEYBOARD_NOTIFY:
191         if (event->new_keyboard_notify.changed & XCB_XKB_NKN_DETAIL_KEYCODES)
192             update_keymap(kbd);
193         break;
194
195     case XCB_XKB_MAP_NOTIFY:
196         update_keymap(kbd);
197         break;
198
199     case XCB_XKB_STATE_NOTIFY:
200         xkb_state_update_mask(kbd->state,
201                               event->state_notify.baseMods,
202                               event->state_notify.latchedMods,
203                               event->state_notify.lockedMods,
204                               event->state_notify.baseGroup,
205                               event->state_notify.latchedGroup,
206                               event->state_notify.lockedGroup);
207         break;
208     }
209 }
210
211 static void
212 process_event(xcb_generic_event_t *gevent, struct keyboard *kbd)
213 {
214     switch (gevent->response_type) {
215     case XCB_KEY_PRESS: {
216         xcb_key_press_event_t *event = (xcb_key_press_event_t *) gevent;
217         xkb_keycode_t keycode = event->detail;
218
219         test_print_keycode_state(kbd->state, keycode);
220
221         /* Exit on ESC. */
222         if (keycode == 9)
223             terminate = true;
224         break;
225     }
226     default:
227         if (gevent->response_type == kbd->first_xkb_event)
228             process_xkb_event(gevent, kbd);
229         break;
230     }
231 }
232
233 static int
234 loop(xcb_connection_t *conn, struct keyboard *kbd)
235 {
236     while (!terminate) {
237         xcb_generic_event_t *event;
238
239         switch (xcb_connection_has_error(conn)) {
240         case 0:
241             break;
242         case XCB_CONN_ERROR:
243             fprintf(stderr,
244                     "Closed connection to X server: connection error\n");
245             return -1;
246         case XCB_CONN_CLOSED_EXT_NOTSUPPORTED:
247             fprintf(stderr,
248                     "Closed connection to X server: extension not supported\n");
249             return -1;
250         default:
251             fprintf(stderr,
252                     "Closed connection to X server: error code %d\n",
253                     xcb_connection_has_error(conn));
254             return -1;
255         }
256
257         event = xcb_wait_for_event(conn);
258         process_event(event, kbd);
259         free(event);
260     }
261
262     return 0;
263 }
264
265 static int
266 create_capture_window(xcb_connection_t *conn)
267 {
268     xcb_generic_error_t *error;
269     xcb_void_cookie_t cookie;
270     xcb_screen_t *screen =
271         xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
272     xcb_window_t window = xcb_generate_id(conn);
273     uint32_t values[2] = {
274         screen->white_pixel,
275         XCB_EVENT_MASK_KEY_PRESS,
276     };
277
278     cookie = xcb_create_window_checked(conn, XCB_COPY_FROM_PARENT,
279                                        window, screen->root,
280                                        10, 10, 100, 100, 1,
281                                        XCB_WINDOW_CLASS_INPUT_OUTPUT,
282                                        screen->root_visual,
283                                        XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
284                                        values);
285     if ((error = xcb_request_check(conn, cookie)) != NULL) {
286         free(error);
287         return -1;
288     }
289
290     cookie = xcb_map_window_checked(conn, window);
291     if ((error = xcb_request_check(conn, cookie)) != NULL) {
292         free(error);
293         return -1;
294     }
295
296     return 0;
297 }
298
299 int
300 main(int argc, char *argv[])
301 {
302     int ret;
303     xcb_connection_t *conn;
304     uint8_t first_xkb_event;
305     int32_t core_kbd_device_id;
306     struct xkb_context *ctx;
307     struct keyboard core_kbd;
308
309     setlocale(LC_ALL, "");
310
311     conn = xcb_connect(NULL, NULL);
312     if (!conn || xcb_connection_has_error(conn)) {
313         fprintf(stderr, "Couldn't connect to X server: error code %d\n",
314                 conn ? xcb_connection_has_error(conn) : -1);
315         ret = -1;
316         goto err_out;
317     }
318
319     ret = xkb_x11_setup_xkb_extension(conn,
320                                       XKB_X11_MIN_MAJOR_XKB_VERSION,
321                                       XKB_X11_MIN_MINOR_XKB_VERSION,
322                                       XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
323                                       NULL, NULL, &first_xkb_event, NULL);
324     if (!ret) {
325         fprintf(stderr, "Couldn't setup XKB extension\n");
326         goto err_conn;
327     }
328
329     ctx = test_get_context(0);
330     if (!ctx) {
331         ret = -1;
332         fprintf(stderr, "Couldn't create xkb context\n");
333         goto err_conn;
334     }
335
336     core_kbd_device_id = xkb_x11_get_core_keyboard_device_id(conn);
337     if (core_kbd_device_id == -1) {
338         ret = -1;
339         fprintf(stderr, "Couldn't find core keyboard device\n");
340         goto err_ctx;
341     }
342
343     ret = init_kbd(&core_kbd, conn, first_xkb_event, core_kbd_device_id, ctx);
344     if (ret) {
345         fprintf(stderr, "Couldn't initialize core keyboard device\n");
346         goto err_ctx;
347     }
348
349     ret = create_capture_window(conn);
350     if (ret) {
351         fprintf(stderr, "Couldn't create a capture window\n");
352         goto err_core_kbd;
353     }
354
355     system("stty -echo");
356     ret = loop(conn, &core_kbd);
357     system("stty echo");
358
359 err_core_kbd:
360     deinit_kbd(&core_kbd);
361 err_ctx:
362     xkb_context_unref(ctx);
363 err_conn:
364     xcb_disconnect(conn);
365 err_out:
366     exit(ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
367 }