]>
Commit | Line | Data |
---|---|---|
8e937190 DH |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | ||
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> | |
7 | ||
8 | systemd is free software; you can redistribute it and/or modify it | |
9 | under the terms of the GNU Lesser General Public License as published by | |
10 | the Free Software Foundation; either version 2.1 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | systemd is distributed in the hope that it will be useful, but | |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | Lesser General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU Lesser General Public License | |
19 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
20 | ***/ | |
21 | ||
22 | /* | |
23 | * Event Catenation | |
24 | * The evcat tool catenates input events of all requested devices and prints | |
25 | * them to standard-output. It's only meant for debugging of input-related | |
26 | * problems. | |
27 | */ | |
28 | ||
29 | #include <assert.h> | |
30 | #include <errno.h> | |
31 | #include <getopt.h> | |
32 | #include <libevdev/libevdev.h> | |
33 | #include <linux/kd.h> | |
34 | #include <linux/vt.h> | |
35 | #include <stdarg.h> | |
36 | #include <stdbool.h> | |
37 | #include <stdio.h> | |
38 | #include <stdlib.h> | |
39 | #include <string.h> | |
40 | #include <sys/ioctl.h> | |
41 | #include <sys/stat.h> | |
42 | #include <systemd/sd-bus.h> | |
43 | #include <systemd/sd-event.h> | |
44 | #include <systemd/sd-login.h> | |
45 | #include <termios.h> | |
46 | #include <unistd.h> | |
47 | #include <xkbcommon/xkbcommon.h> | |
48 | #include "build.h" | |
49 | #include "bus-util.h" | |
50 | #include "event-util.h" | |
51 | #include "idev.h" | |
52 | #include "macro.h" | |
53 | #include "sysview.h" | |
54 | #include "term-internal.h" | |
55 | #include "util.h" | |
56 | ||
57 | typedef struct Evcat Evcat; | |
58 | ||
59 | struct Evcat { | |
60 | char *session; | |
61 | char *seat; | |
62 | sd_event *event; | |
63 | sd_bus *bus; | |
64 | sysview_context *sysview; | |
65 | idev_context *idev; | |
66 | idev_session *idev_session; | |
67 | ||
68 | bool managed : 1; | |
69 | }; | |
70 | ||
71 | static Evcat *evcat_free(Evcat *e) { | |
72 | if (!e) | |
73 | return NULL; | |
74 | ||
75 | e->idev_session = idev_session_free(e->idev_session); | |
76 | e->idev = idev_context_unref(e->idev); | |
77 | e->sysview = sysview_context_free(e->sysview); | |
78 | e->bus = sd_bus_unref(e->bus); | |
79 | e->event = sd_event_unref(e->event); | |
80 | free(e->seat); | |
81 | free(e->session); | |
82 | free(e); | |
83 | ||
84 | tcflush(0, TCIOFLUSH); | |
85 | ||
86 | return NULL; | |
87 | } | |
88 | ||
89 | DEFINE_TRIVIAL_CLEANUP_FUNC(Evcat*, evcat_free); | |
90 | ||
91 | static bool is_managed(const char *session) { | |
92 | unsigned int vtnr; | |
93 | struct stat st; | |
94 | long mode; | |
95 | int r; | |
96 | ||
97 | /* Using logind's Controller API is highly fragile if there is already | |
98 | * a session controller running. If it is registered as controller | |
99 | * itself, TakeControl will simply fail. But if its a legacy controller | |
100 | * that does not use logind's controller API, we must never register | |
101 | * our own controller. Otherwise, we really mess up the VT. Therefore, | |
102 | * only run in managed mode if there's no-one else. */ | |
103 | ||
104 | if (geteuid() == 0) | |
105 | return false; | |
106 | ||
107 | if (!isatty(1)) | |
108 | return false; | |
109 | ||
110 | if (!session) | |
111 | return false; | |
112 | ||
113 | r = sd_session_get_vt(session, &vtnr); | |
114 | if (r < 0 || vtnr < 1 || vtnr > 63) | |
115 | return false; | |
116 | ||
117 | mode = 0; | |
118 | r = ioctl(1, KDGETMODE, &mode); | |
119 | if (r < 0 || mode != KD_TEXT) | |
120 | return false; | |
121 | ||
122 | r = fstat(1, &st); | |
123 | if (r < 0 || minor(st.st_rdev) != vtnr) | |
124 | return false; | |
125 | ||
126 | return true; | |
127 | } | |
128 | ||
129 | static int evcat_new(Evcat **out) { | |
130 | _cleanup_(evcat_freep) Evcat *e = NULL; | |
131 | int r; | |
132 | ||
133 | assert(out); | |
134 | ||
135 | e = new0(Evcat, 1); | |
136 | if (!e) | |
137 | return log_oom(); | |
138 | ||
139 | r = sd_pid_get_session(getpid(), &e->session); | |
23bbb0de MS |
140 | if (r < 0) |
141 | return log_error_errno(r, "Cannot retrieve logind session: %m"); | |
8e937190 DH |
142 | |
143 | r = sd_session_get_seat(e->session, &e->seat); | |
23bbb0de MS |
144 | if (r < 0) |
145 | return log_error_errno(r, "Cannot retrieve seat of logind session: %m"); | |
8e937190 DH |
146 | |
147 | e->managed = is_managed(e->session); | |
148 | ||
149 | r = sd_event_default(&e->event); | |
150 | if (r < 0) | |
151 | return r; | |
152 | ||
153 | r = sd_bus_open_system(&e->bus); | |
154 | if (r < 0) | |
155 | return r; | |
156 | ||
157 | r = sd_bus_attach_event(e->bus, e->event, SD_EVENT_PRIORITY_NORMAL); | |
158 | if (r < 0) | |
159 | return r; | |
160 | ||
161 | r = sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1); | |
162 | if (r < 0) | |
163 | return r; | |
164 | ||
165 | r = sd_event_add_signal(e->event, NULL, SIGTERM, NULL, NULL); | |
166 | if (r < 0) | |
167 | return r; | |
168 | ||
169 | r = sd_event_add_signal(e->event, NULL, SIGINT, NULL, NULL); | |
170 | if (r < 0) | |
171 | return r; | |
172 | ||
173 | r = sysview_context_new(&e->sysview, | |
174 | SYSVIEW_CONTEXT_SCAN_LOGIND | | |
175 | SYSVIEW_CONTEXT_SCAN_EVDEV, | |
176 | e->event, | |
177 | e->bus, | |
178 | NULL); | |
179 | if (r < 0) | |
180 | return r; | |
181 | ||
182 | r = idev_context_new(&e->idev, e->event, e->bus); | |
183 | if (r < 0) | |
184 | return r; | |
185 | ||
186 | *out = e; | |
187 | e = NULL; | |
188 | return 0; | |
189 | } | |
190 | ||
191 | static void kdata_print(idev_data *data) { | |
192 | idev_data_keyboard *k = &data->keyboard; | |
193 | char buf[128]; | |
194 | uint32_t i, c; | |
195 | int cwidth; | |
196 | ||
197 | /* Key-press state: UP/DOWN/REPEAT */ | |
198 | printf(" %-6s", k->value == 0 ? "UP" : | |
199 | k->value == 1 ? "DOWN" : | |
200 | "REPEAT"); | |
201 | ||
4c4e4128 DH |
202 | /* Resync state */ |
203 | printf(" | %-6s", data->resync ? "RESYNC" : ""); | |
204 | ||
8e937190 DH |
205 | /* Keycode that triggered the event */ |
206 | printf(" | %5u", (unsigned)k->keycode); | |
207 | ||
208 | /* Well-known name of the keycode */ | |
209 | printf(" | %-20s", libevdev_event_code_get_name(EV_KEY, k->keycode) ? : "<unknown>"); | |
210 | ||
211 | /* Well-known modifiers */ | |
212 | printf(" | %-5s", (k->mods & IDEV_KBDMOD_SHIFT) ? "SHIFT" : ""); | |
213 | printf(" %-4s", (k->mods & IDEV_KBDMOD_CTRL) ? "CTRL" : ""); | |
214 | printf(" %-3s", (k->mods & IDEV_KBDMOD_ALT) ? "ALT" : ""); | |
215 | printf(" %-5s", (k->mods & IDEV_KBDMOD_LINUX) ? "LINUX" : ""); | |
216 | printf(" %-4s", (k->mods & IDEV_KBDMOD_CAPS) ? "CAPS" : ""); | |
217 | ||
62d5068d DH |
218 | /* Consumed modifiers */ |
219 | printf(" | %-5s", (k->consumed_mods & IDEV_KBDMOD_SHIFT) ? "SHIFT" : ""); | |
220 | printf(" %-4s", (k->consumed_mods & IDEV_KBDMOD_CTRL) ? "CTRL" : ""); | |
221 | printf(" %-3s", (k->consumed_mods & IDEV_KBDMOD_ALT) ? "ALT" : ""); | |
222 | printf(" %-5s", (k->consumed_mods & IDEV_KBDMOD_LINUX) ? "LINUX" : ""); | |
223 | printf(" %-4s", (k->consumed_mods & IDEV_KBDMOD_CAPS) ? "CAPS" : ""); | |
224 | ||
8e937190 DH |
225 | /* Resolved symbols */ |
226 | printf(" |"); | |
227 | for (i = 0; i < k->n_syms; ++i) { | |
228 | buf[0] = 0; | |
229 | xkb_keysym_get_name(k->keysyms[i], buf, sizeof(buf)); | |
230 | ||
231 | if (is_locale_utf8()) { | |
232 | c = k->codepoints[i]; | |
233 | if (c < 0x110000 && c > 0x20 && (c < 0x7f || c > 0x9f)) { | |
234 | /* "%4lc" doesn't work well, so hard-code it */ | |
235 | cwidth = mk_wcwidth(c); | |
236 | while (cwidth++ < 2) | |
237 | printf(" "); | |
238 | ||
239 | printf(" '%lc':", (wchar_t)c); | |
240 | } else { | |
241 | printf(" "); | |
242 | } | |
243 | } | |
244 | ||
245 | printf(" XKB_KEY_%-30s", buf); | |
246 | } | |
247 | ||
248 | printf("\n"); | |
249 | } | |
250 | ||
251 | static bool kdata_is_exit(idev_data *data) { | |
252 | idev_data_keyboard *k = &data->keyboard; | |
253 | ||
254 | if (k->value != 1) | |
255 | return false; | |
256 | if (k->n_syms != 1) | |
257 | return false; | |
258 | ||
259 | return k->codepoints[0] == 'q'; | |
260 | } | |
261 | ||
262 | static int evcat_idev_fn(idev_session *session, void *userdata, idev_event *ev) { | |
263 | Evcat *e = userdata; | |
264 | ||
265 | switch (ev->type) { | |
266 | case IDEV_EVENT_DEVICE_ADD: | |
267 | idev_device_enable(ev->device_add.device); | |
268 | break; | |
269 | case IDEV_EVENT_DEVICE_REMOVE: | |
270 | idev_device_disable(ev->device_remove.device); | |
271 | break; | |
272 | case IDEV_EVENT_DEVICE_DATA: | |
273 | switch (ev->device_data.data.type) { | |
274 | case IDEV_DATA_KEYBOARD: | |
275 | if (kdata_is_exit(&ev->device_data.data)) | |
276 | sd_event_exit(e->event, 0); | |
277 | else | |
278 | kdata_print(&ev->device_data.data); | |
279 | ||
280 | break; | |
281 | } | |
282 | ||
283 | break; | |
284 | } | |
285 | ||
286 | return 0; | |
287 | } | |
288 | ||
289 | static int evcat_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) { | |
290 | unsigned int flags, type; | |
291 | Evcat *e = userdata; | |
292 | sysview_device *d; | |
293 | const char *name; | |
294 | int r; | |
295 | ||
296 | switch (ev->type) { | |
297 | case SYSVIEW_EVENT_SESSION_FILTER: | |
298 | if (streq_ptr(e->session, ev->session_filter.id)) | |
299 | return 1; | |
300 | ||
301 | break; | |
302 | case SYSVIEW_EVENT_SESSION_ADD: | |
303 | assert(!e->idev_session); | |
304 | ||
305 | name = sysview_session_get_name(ev->session_add.session); | |
306 | flags = 0; | |
307 | ||
308 | if (e->managed) | |
309 | flags |= IDEV_SESSION_MANAGED; | |
310 | ||
311 | r = idev_session_new(&e->idev_session, | |
312 | e->idev, | |
313 | flags, | |
314 | name, | |
315 | evcat_idev_fn, | |
316 | e); | |
23bbb0de MS |
317 | if (r < 0) |
318 | return log_error_errno(r, "Cannot create idev session: %m"); | |
8e937190 | 319 | |
8e937190 DH |
320 | if (e->managed) { |
321 | r = sysview_session_take_control(ev->session_add.session); | |
23bbb0de MS |
322 | if (r < 0) |
323 | return log_error_errno(r, "Cannot request session control: %m"); | |
8e937190 DH |
324 | } |
325 | ||
c6000223 DH |
326 | idev_session_enable(e->idev_session); |
327 | ||
8e937190 DH |
328 | break; |
329 | case SYSVIEW_EVENT_SESSION_REMOVE: | |
330 | idev_session_disable(e->idev_session); | |
331 | e->idev_session = idev_session_free(e->idev_session); | |
c17091b7 DH |
332 | if (sd_event_get_exit_code(e->event, &r) == -ENODATA) |
333 | sd_event_exit(e->event, 0); | |
8e937190 DH |
334 | break; |
335 | case SYSVIEW_EVENT_SESSION_ATTACH: | |
336 | d = ev->session_attach.device; | |
337 | type = sysview_device_get_type(d); | |
338 | if (type == SYSVIEW_DEVICE_EVDEV) { | |
339 | r = idev_session_add_evdev(e->idev_session, sysview_device_get_ud(d)); | |
23bbb0de MS |
340 | if (r < 0) |
341 | return log_error_errno(r, "Cannot add evdev device to idev: %m"); | |
8e937190 DH |
342 | } |
343 | ||
344 | break; | |
345 | case SYSVIEW_EVENT_SESSION_DETACH: | |
346 | d = ev->session_detach.device; | |
347 | type = sysview_device_get_type(d); | |
348 | if (type == SYSVIEW_DEVICE_EVDEV) { | |
349 | r = idev_session_remove_evdev(e->idev_session, sysview_device_get_ud(d)); | |
23bbb0de MS |
350 | if (r < 0) |
351 | return log_error_errno(r, "Cannot remove evdev device from idev: %m"); | |
8e937190 DH |
352 | } |
353 | ||
354 | break; | |
355 | case SYSVIEW_EVENT_SESSION_CONTROL: | |
356 | r = ev->session_control.error; | |
23bbb0de MS |
357 | if (r < 0) |
358 | return log_error_errno(r, "Cannot acquire session control: %m"); | |
8e937190 DH |
359 | |
360 | r = ioctl(1, KDSKBMODE, K_UNICODE); | |
4a62c710 MS |
361 | if (r < 0) |
362 | return log_error_errno(errno, "Cannot set K_UNICODE on stdout: %m"); | |
8e937190 DH |
363 | |
364 | r = ioctl(1, KDSETMODE, KD_TEXT); | |
4a62c710 MS |
365 | if (r < 0) |
366 | return log_error_errno(errno, "Cannot set KD_TEXT on stdout: %m"); | |
8e937190 DH |
367 | |
368 | printf("\n"); | |
369 | ||
370 | break; | |
371 | } | |
372 | ||
373 | return 0; | |
374 | } | |
375 | ||
376 | static int evcat_run(Evcat *e) { | |
377 | struct termios in_attr, saved_attr; | |
378 | int r; | |
379 | ||
380 | assert(e); | |
381 | ||
382 | if (!e->managed && geteuid() > 0) | |
383 | log_warning("You run in unmanaged mode without being root. This is likely to produce no output.."); | |
384 | ||
385 | printf("evcat - Read and catenate events from selected input devices\n" | |
386 | " Running on seat '%s' in user-session '%s'\n" | |
387 | " Exit by pressing ^C or 'q'\n\n", | |
388 | e->seat ? : "seat0", e->session ? : "<none>"); | |
389 | ||
390 | r = sysview_context_start(e->sysview, evcat_sysview_fn, e); | |
391 | if (r < 0) | |
392 | goto out; | |
393 | ||
394 | r = tcgetattr(0, &in_attr); | |
395 | if (r < 0) { | |
396 | r = -errno; | |
397 | goto out; | |
398 | } | |
399 | ||
400 | saved_attr = in_attr; | |
401 | in_attr.c_lflag &= ~ECHO; | |
402 | ||
403 | r = tcsetattr(0, TCSANOW, &in_attr); | |
404 | if (r < 0) { | |
405 | r = -errno; | |
406 | goto out; | |
407 | } | |
408 | ||
409 | r = sd_event_loop(e->event); | |
410 | tcsetattr(0, TCSANOW, &saved_attr); | |
411 | printf("exiting..\n"); | |
412 | ||
413 | out: | |
414 | sysview_context_stop(e->sysview); | |
415 | return r; | |
416 | } | |
417 | ||
418 | static int help(void) { | |
419 | printf("%s [OPTIONS...]\n\n" | |
420 | "Read and catenate events from selected input devices.\n\n" | |
421 | " -h --help Show this help\n" | |
422 | " --version Show package version\n" | |
423 | , program_invocation_short_name); | |
424 | ||
425 | return 0; | |
426 | } | |
427 | ||
428 | static int parse_argv(int argc, char *argv[]) { | |
429 | enum { | |
430 | ARG_VERSION = 0x100, | |
431 | }; | |
432 | static const struct option options[] = { | |
433 | { "help", no_argument, NULL, 'h' }, | |
434 | { "version", no_argument, NULL, ARG_VERSION }, | |
435 | {}, | |
436 | }; | |
437 | int c; | |
438 | ||
439 | assert(argc >= 0); | |
440 | assert(argv); | |
441 | ||
442 | while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) | |
443 | switch (c) { | |
444 | case 'h': | |
445 | help(); | |
446 | return 0; | |
447 | ||
448 | case ARG_VERSION: | |
449 | puts(PACKAGE_STRING); | |
450 | puts(SYSTEMD_FEATURES); | |
451 | return 0; | |
452 | ||
453 | case '?': | |
454 | return -EINVAL; | |
455 | ||
456 | default: | |
457 | assert_not_reached("Unhandled option"); | |
458 | } | |
459 | ||
460 | if (argc > optind) { | |
461 | log_error("Too many arguments"); | |
462 | return -EINVAL; | |
463 | } | |
464 | ||
465 | return 1; | |
466 | } | |
467 | ||
468 | int main(int argc, char *argv[]) { | |
469 | _cleanup_(evcat_freep) Evcat *e = NULL; | |
470 | int r; | |
471 | ||
472 | log_set_target(LOG_TARGET_AUTO); | |
473 | log_parse_environment(); | |
474 | log_open(); | |
475 | ||
476 | setlocale(LC_ALL, ""); | |
477 | if (!is_locale_utf8()) | |
478 | log_warning("Locale is not set to UTF-8. Codepoints will not be printed!"); | |
479 | ||
480 | r = parse_argv(argc, argv); | |
481 | if (r <= 0) | |
482 | goto finish; | |
483 | ||
484 | r = evcat_new(&e); | |
485 | if (r < 0) | |
486 | goto finish; | |
487 | ||
488 | r = evcat_run(e); | |
489 | ||
490 | finish: | |
491 | return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; | |
492 | } |