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