state: allow different modes for calculating consumed modifiers
[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     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
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
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
179 deinit_kbd(struct keyboard *kbd)
180 {
181     xkb_state_unref(kbd->state);
182     xkb_keymap_unref(kbd->keymap);
183 }
184
185 static void
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
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                                  XKB_CONSUMED_MODE_XKB);
241
242         /* Exit on ESC. */
243         if (keycode == 9)
244             terminate = true;
245         break;
246     }
247     default:
248         if (gevent->response_type == kbd->first_xkb_event)
249             process_xkb_event(gevent, kbd);
250         break;
251     }
252 }
253
254 static int
255 loop(xcb_connection_t *conn, struct keyboard *kbd)
256 {
257     while (!terminate) {
258         xcb_generic_event_t *event;
259
260         switch (xcb_connection_has_error(conn)) {
261         case 0:
262             break;
263         case XCB_CONN_ERROR:
264             fprintf(stderr,
265                     "Closed connection to X server: connection error\n");
266             return -1;
267         case XCB_CONN_CLOSED_EXT_NOTSUPPORTED:
268             fprintf(stderr,
269                     "Closed connection to X server: extension not supported\n");
270             return -1;
271         default:
272             fprintf(stderr,
273                     "Closed connection to X server: error code %d\n",
274                     xcb_connection_has_error(conn));
275             return -1;
276         }
277
278         event = xcb_wait_for_event(conn);
279         process_event(event, kbd);
280         free(event);
281     }
282
283     return 0;
284 }
285
286 static int
287 create_capture_window(xcb_connection_t *conn)
288 {
289     xcb_generic_error_t *error;
290     xcb_void_cookie_t cookie;
291     xcb_screen_t *screen =
292         xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
293     xcb_window_t window = xcb_generate_id(conn);
294     uint32_t values[2] = {
295         screen->white_pixel,
296         XCB_EVENT_MASK_KEY_PRESS,
297     };
298
299     cookie = xcb_create_window_checked(conn, XCB_COPY_FROM_PARENT,
300                                        window, screen->root,
301                                        10, 10, 100, 100, 1,
302                                        XCB_WINDOW_CLASS_INPUT_OUTPUT,
303                                        screen->root_visual,
304                                        XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
305                                        values);
306     if ((error = xcb_request_check(conn, cookie)) != NULL) {
307         free(error);
308         return -1;
309     }
310
311     cookie = xcb_map_window_checked(conn, window);
312     if ((error = xcb_request_check(conn, cookie)) != NULL) {
313         free(error);
314         return -1;
315     }
316
317     return 0;
318 }
319
320 int
321 main(int argc, char *argv[])
322 {
323     int ret;
324     xcb_connection_t *conn;
325     uint8_t first_xkb_event;
326     int32_t core_kbd_device_id;
327     struct xkb_context *ctx;
328     struct keyboard core_kbd;
329
330     setlocale(LC_ALL, "");
331
332     conn = xcb_connect(NULL, NULL);
333     if (!conn || xcb_connection_has_error(conn)) {
334         fprintf(stderr, "Couldn't connect to X server: error code %d\n",
335                 conn ? xcb_connection_has_error(conn) : -1);
336         ret = -1;
337         goto err_out;
338     }
339
340     ret = xkb_x11_setup_xkb_extension(conn,
341                                       XKB_X11_MIN_MAJOR_XKB_VERSION,
342                                       XKB_X11_MIN_MINOR_XKB_VERSION,
343                                       XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
344                                       NULL, NULL, &first_xkb_event, NULL);
345     if (!ret) {
346         fprintf(stderr, "Couldn't setup XKB extension\n");
347         goto err_conn;
348     }
349
350     ctx = test_get_context(0);
351     if (!ctx) {
352         ret = -1;
353         fprintf(stderr, "Couldn't create xkb context\n");
354         goto err_conn;
355     }
356
357     core_kbd_device_id = xkb_x11_get_core_keyboard_device_id(conn);
358     if (core_kbd_device_id == -1) {
359         ret = -1;
360         fprintf(stderr, "Couldn't find core keyboard device\n");
361         goto err_ctx;
362     }
363
364     ret = init_kbd(&core_kbd, conn, first_xkb_event, core_kbd_device_id, ctx);
365     if (ret) {
366         fprintf(stderr, "Couldn't initialize core keyboard device\n");
367         goto err_ctx;
368     }
369
370     ret = create_capture_window(conn);
371     if (ret) {
372         fprintf(stderr, "Couldn't create a capture window\n");
373         goto err_core_kbd;
374     }
375
376     test_disable_stdin_echo();
377     ret = loop(conn, &core_kbd);
378     test_enable_stdin_echo();
379
380 err_core_kbd:
381     deinit_kbd(&core_kbd);
382 err_ctx:
383     xkb_context_unref(ctx);
384 err_conn:
385     xcb_disconnect(conn);
386 err_out:
387     exit(ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
388 }