]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-terminal/evcat.c
treewide: another round of simplifications
[thirdparty/systemd.git] / src / libsystemd-terminal / evcat.c
CommitLineData
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
57typedef struct Evcat Evcat;
58
59struct 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
71static 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
89DEFINE_TRIVIAL_CLEANUP_FUNC(Evcat*, evcat_free);
90
91static 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
129static 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
191static 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
251static 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
262static 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
289static 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
376static 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
413out:
414 sysview_context_stop(e->sysview);
415 return r;
416}
417
418static 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
428static 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
468int 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
490finish:
491 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
492}