]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-terminal/evcat.c
treewide: no need to negate errno for log_*_errno()
[thirdparty/systemd.git] / src / libsystemd-terminal / evcat.c
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);
140 if (r < 0) {
141 log_error_errno(r, "Cannot retrieve logind session: %m");
142 return r;
143 }
144
145 r = sd_session_get_seat(e->session, &e->seat);
146 if (r < 0) {
147 log_error_errno(r, "Cannot retrieve seat of logind session: %m");
148 return r;
149 }
150
151 e->managed = is_managed(e->session);
152
153 r = sd_event_default(&e->event);
154 if (r < 0)
155 return r;
156
157 r = sd_bus_open_system(&e->bus);
158 if (r < 0)
159 return r;
160
161 r = sd_bus_attach_event(e->bus, e->event, SD_EVENT_PRIORITY_NORMAL);
162 if (r < 0)
163 return r;
164
165 r = sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1);
166 if (r < 0)
167 return r;
168
169 r = sd_event_add_signal(e->event, NULL, SIGTERM, NULL, NULL);
170 if (r < 0)
171 return r;
172
173 r = sd_event_add_signal(e->event, NULL, SIGINT, NULL, NULL);
174 if (r < 0)
175 return r;
176
177 r = sysview_context_new(&e->sysview,
178 SYSVIEW_CONTEXT_SCAN_LOGIND |
179 SYSVIEW_CONTEXT_SCAN_EVDEV,
180 e->event,
181 e->bus,
182 NULL);
183 if (r < 0)
184 return r;
185
186 r = idev_context_new(&e->idev, e->event, e->bus);
187 if (r < 0)
188 return r;
189
190 *out = e;
191 e = NULL;
192 return 0;
193 }
194
195 static void kdata_print(idev_data *data) {
196 idev_data_keyboard *k = &data->keyboard;
197 char buf[128];
198 uint32_t i, c;
199 int cwidth;
200
201 /* Key-press state: UP/DOWN/REPEAT */
202 printf(" %-6s", k->value == 0 ? "UP" :
203 k->value == 1 ? "DOWN" :
204 "REPEAT");
205
206 /* Resync state */
207 printf(" | %-6s", data->resync ? "RESYNC" : "");
208
209 /* Keycode that triggered the event */
210 printf(" | %5u", (unsigned)k->keycode);
211
212 /* Well-known name of the keycode */
213 printf(" | %-20s", libevdev_event_code_get_name(EV_KEY, k->keycode) ? : "<unknown>");
214
215 /* Well-known modifiers */
216 printf(" | %-5s", (k->mods & IDEV_KBDMOD_SHIFT) ? "SHIFT" : "");
217 printf(" %-4s", (k->mods & IDEV_KBDMOD_CTRL) ? "CTRL" : "");
218 printf(" %-3s", (k->mods & IDEV_KBDMOD_ALT) ? "ALT" : "");
219 printf(" %-5s", (k->mods & IDEV_KBDMOD_LINUX) ? "LINUX" : "");
220 printf(" %-4s", (k->mods & IDEV_KBDMOD_CAPS) ? "CAPS" : "");
221
222 /* Consumed modifiers */
223 printf(" | %-5s", (k->consumed_mods & IDEV_KBDMOD_SHIFT) ? "SHIFT" : "");
224 printf(" %-4s", (k->consumed_mods & IDEV_KBDMOD_CTRL) ? "CTRL" : "");
225 printf(" %-3s", (k->consumed_mods & IDEV_KBDMOD_ALT) ? "ALT" : "");
226 printf(" %-5s", (k->consumed_mods & IDEV_KBDMOD_LINUX) ? "LINUX" : "");
227 printf(" %-4s", (k->consumed_mods & IDEV_KBDMOD_CAPS) ? "CAPS" : "");
228
229 /* Resolved symbols */
230 printf(" |");
231 for (i = 0; i < k->n_syms; ++i) {
232 buf[0] = 0;
233 xkb_keysym_get_name(k->keysyms[i], buf, sizeof(buf));
234
235 if (is_locale_utf8()) {
236 c = k->codepoints[i];
237 if (c < 0x110000 && c > 0x20 && (c < 0x7f || c > 0x9f)) {
238 /* "%4lc" doesn't work well, so hard-code it */
239 cwidth = mk_wcwidth(c);
240 while (cwidth++ < 2)
241 printf(" ");
242
243 printf(" '%lc':", (wchar_t)c);
244 } else {
245 printf(" ");
246 }
247 }
248
249 printf(" XKB_KEY_%-30s", buf);
250 }
251
252 printf("\n");
253 }
254
255 static bool kdata_is_exit(idev_data *data) {
256 idev_data_keyboard *k = &data->keyboard;
257
258 if (k->value != 1)
259 return false;
260 if (k->n_syms != 1)
261 return false;
262
263 return k->codepoints[0] == 'q';
264 }
265
266 static int evcat_idev_fn(idev_session *session, void *userdata, idev_event *ev) {
267 Evcat *e = userdata;
268
269 switch (ev->type) {
270 case IDEV_EVENT_DEVICE_ADD:
271 idev_device_enable(ev->device_add.device);
272 break;
273 case IDEV_EVENT_DEVICE_REMOVE:
274 idev_device_disable(ev->device_remove.device);
275 break;
276 case IDEV_EVENT_DEVICE_DATA:
277 switch (ev->device_data.data.type) {
278 case IDEV_DATA_KEYBOARD:
279 if (kdata_is_exit(&ev->device_data.data))
280 sd_event_exit(e->event, 0);
281 else
282 kdata_print(&ev->device_data.data);
283
284 break;
285 }
286
287 break;
288 }
289
290 return 0;
291 }
292
293 static int evcat_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) {
294 unsigned int flags, type;
295 Evcat *e = userdata;
296 sysview_device *d;
297 const char *name;
298 int r;
299
300 switch (ev->type) {
301 case SYSVIEW_EVENT_SESSION_FILTER:
302 if (streq_ptr(e->session, ev->session_filter.id))
303 return 1;
304
305 break;
306 case SYSVIEW_EVENT_SESSION_ADD:
307 assert(!e->idev_session);
308
309 name = sysview_session_get_name(ev->session_add.session);
310 flags = 0;
311
312 if (e->managed)
313 flags |= IDEV_SESSION_MANAGED;
314
315 r = idev_session_new(&e->idev_session,
316 e->idev,
317 flags,
318 name,
319 evcat_idev_fn,
320 e);
321 if (r < 0) {
322 log_error_errno(r, "Cannot create idev session: %m");
323 return r;
324 }
325
326 if (e->managed) {
327 r = sysview_session_take_control(ev->session_add.session);
328 if (r < 0) {
329 log_error_errno(r, "Cannot request session control: %m");
330 return r;
331 }
332 }
333
334 idev_session_enable(e->idev_session);
335
336 break;
337 case SYSVIEW_EVENT_SESSION_REMOVE:
338 idev_session_disable(e->idev_session);
339 e->idev_session = idev_session_free(e->idev_session);
340 if (sd_event_get_exit_code(e->event, &r) == -ENODATA)
341 sd_event_exit(e->event, 0);
342 break;
343 case SYSVIEW_EVENT_SESSION_ATTACH:
344 d = ev->session_attach.device;
345 type = sysview_device_get_type(d);
346 if (type == SYSVIEW_DEVICE_EVDEV) {
347 r = idev_session_add_evdev(e->idev_session, sysview_device_get_ud(d));
348 if (r < 0) {
349 log_error_errno(r, "Cannot add evdev device to idev: %m");
350 return r;
351 }
352 }
353
354 break;
355 case SYSVIEW_EVENT_SESSION_DETACH:
356 d = ev->session_detach.device;
357 type = sysview_device_get_type(d);
358 if (type == SYSVIEW_DEVICE_EVDEV) {
359 r = idev_session_remove_evdev(e->idev_session, sysview_device_get_ud(d));
360 if (r < 0) {
361 log_error_errno(r, "Cannot remove evdev device from idev: %m");
362 return r;
363 }
364 }
365
366 break;
367 case SYSVIEW_EVENT_SESSION_CONTROL:
368 r = ev->session_control.error;
369 if (r < 0) {
370 log_error_errno(r, "Cannot acquire session control: %m");
371 return r;
372 }
373
374 r = ioctl(1, KDSKBMODE, K_UNICODE);
375 if (r < 0) {
376 log_error("Cannot set K_UNICODE on stdout: %m");
377 return -errno;
378 }
379
380 r = ioctl(1, KDSETMODE, KD_TEXT);
381 if (r < 0) {
382 log_error("Cannot set KD_TEXT on stdout: %m");
383 return -errno;
384 }
385
386 printf("\n");
387
388 break;
389 }
390
391 return 0;
392 }
393
394 static int evcat_run(Evcat *e) {
395 struct termios in_attr, saved_attr;
396 int r;
397
398 assert(e);
399
400 if (!e->managed && geteuid() > 0)
401 log_warning("You run in unmanaged mode without being root. This is likely to produce no output..");
402
403 printf("evcat - Read and catenate events from selected input devices\n"
404 " Running on seat '%s' in user-session '%s'\n"
405 " Exit by pressing ^C or 'q'\n\n",
406 e->seat ? : "seat0", e->session ? : "<none>");
407
408 r = sysview_context_start(e->sysview, evcat_sysview_fn, e);
409 if (r < 0)
410 goto out;
411
412 r = tcgetattr(0, &in_attr);
413 if (r < 0) {
414 r = -errno;
415 goto out;
416 }
417
418 saved_attr = in_attr;
419 in_attr.c_lflag &= ~ECHO;
420
421 r = tcsetattr(0, TCSANOW, &in_attr);
422 if (r < 0) {
423 r = -errno;
424 goto out;
425 }
426
427 r = sd_event_loop(e->event);
428 tcsetattr(0, TCSANOW, &saved_attr);
429 printf("exiting..\n");
430
431 out:
432 sysview_context_stop(e->sysview);
433 return r;
434 }
435
436 static int help(void) {
437 printf("%s [OPTIONS...]\n\n"
438 "Read and catenate events from selected input devices.\n\n"
439 " -h --help Show this help\n"
440 " --version Show package version\n"
441 , program_invocation_short_name);
442
443 return 0;
444 }
445
446 static int parse_argv(int argc, char *argv[]) {
447 enum {
448 ARG_VERSION = 0x100,
449 };
450 static const struct option options[] = {
451 { "help", no_argument, NULL, 'h' },
452 { "version", no_argument, NULL, ARG_VERSION },
453 {},
454 };
455 int c;
456
457 assert(argc >= 0);
458 assert(argv);
459
460 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
461 switch (c) {
462 case 'h':
463 help();
464 return 0;
465
466 case ARG_VERSION:
467 puts(PACKAGE_STRING);
468 puts(SYSTEMD_FEATURES);
469 return 0;
470
471 case '?':
472 return -EINVAL;
473
474 default:
475 assert_not_reached("Unhandled option");
476 }
477
478 if (argc > optind) {
479 log_error("Too many arguments");
480 return -EINVAL;
481 }
482
483 return 1;
484 }
485
486 int main(int argc, char *argv[]) {
487 _cleanup_(evcat_freep) Evcat *e = NULL;
488 int r;
489
490 log_set_target(LOG_TARGET_AUTO);
491 log_parse_environment();
492 log_open();
493
494 setlocale(LC_ALL, "");
495 if (!is_locale_utf8())
496 log_warning("Locale is not set to UTF-8. Codepoints will not be printed!");
497
498 r = parse_argv(argc, argv);
499 if (r <= 0)
500 goto finish;
501
502 r = evcat_new(&e);
503 if (r < 0)
504 goto finish;
505
506 r = evcat_run(e);
507
508 finish:
509 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
510 }