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