chiark / gitweb /
b3f08e60bf8614819bc467637a035aea008145a8
[elogind.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("Cannot retrieve logind session: %s", strerror(-r));
142                 return r;
143         }
144
145         r = sd_session_get_seat(e->session, &e->seat);
146         if (r < 0) {
147                 log_error("Cannot retrieve seat of logind session: %s", strerror(-r));
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         /* Resolved symbols */
223         printf(" |");
224         for (i = 0; i < k->n_syms; ++i) {
225                 buf[0] = 0;
226                 xkb_keysym_get_name(k->keysyms[i], buf, sizeof(buf));
227
228                 if (is_locale_utf8()) {
229                         c = k->codepoints[i];
230                         if (c < 0x110000 && c > 0x20 && (c < 0x7f || c > 0x9f)) {
231                                 /* "%4lc" doesn't work well, so hard-code it */
232                                 cwidth = mk_wcwidth(c);
233                                 while (cwidth++ < 2)
234                                         printf(" ");
235
236                                 printf(" '%lc':", (wchar_t)c);
237                         } else {
238                                 printf("      ");
239                         }
240                 }
241
242                 printf(" XKB_KEY_%-30s", buf);
243         }
244
245         printf("\n");
246 }
247
248 static bool kdata_is_exit(idev_data *data) {
249         idev_data_keyboard *k = &data->keyboard;
250
251         if (k->value != 1)
252                 return false;
253         if (k->n_syms != 1)
254                 return false;
255
256         return k->codepoints[0] == 'q';
257 }
258
259 static int evcat_idev_fn(idev_session *session, void *userdata, idev_event *ev) {
260         Evcat *e = userdata;
261
262         switch (ev->type) {
263         case IDEV_EVENT_DEVICE_ADD:
264                 idev_device_enable(ev->device_add.device);
265                 break;
266         case IDEV_EVENT_DEVICE_REMOVE:
267                 idev_device_disable(ev->device_remove.device);
268                 break;
269         case IDEV_EVENT_DEVICE_DATA:
270                 switch (ev->device_data.data.type) {
271                 case IDEV_DATA_KEYBOARD:
272                         if (kdata_is_exit(&ev->device_data.data))
273                                 sd_event_exit(e->event, 0);
274                         else
275                                 kdata_print(&ev->device_data.data);
276
277                         break;
278                 }
279
280                 break;
281         }
282
283         return 0;
284 }
285
286 static int evcat_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) {
287         unsigned int flags, type;
288         Evcat *e = userdata;
289         sysview_device *d;
290         const char *name;
291         int r;
292
293         switch (ev->type) {
294         case SYSVIEW_EVENT_SESSION_FILTER:
295                 if (streq_ptr(e->session, ev->session_filter.id))
296                         return 1;
297
298                 break;
299         case SYSVIEW_EVENT_SESSION_ADD:
300                 assert(!e->idev_session);
301
302                 name = sysview_session_get_name(ev->session_add.session);
303                 flags = 0;
304
305                 if (e->managed)
306                         flags |= IDEV_SESSION_MANAGED;
307
308                 r = idev_session_new(&e->idev_session,
309                                      e->idev,
310                                      flags,
311                                      name,
312                                      evcat_idev_fn,
313                                      e);
314                 if (r < 0) {
315                         log_error("Cannot create idev session: %s", strerror(-r));
316                         return r;
317                 }
318
319                 if (e->managed) {
320                         r = sysview_session_take_control(ev->session_add.session);
321                         if (r < 0) {
322                                 log_error("Cannot request session control: %s", strerror(-r));
323                                 return r;
324                         }
325                 }
326
327                 idev_session_enable(e->idev_session);
328
329                 break;
330         case SYSVIEW_EVENT_SESSION_REMOVE:
331                 idev_session_disable(e->idev_session);
332                 e->idev_session = idev_session_free(e->idev_session);
333                 sd_event_exit(e->event, 0);
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));
340                         if (r < 0) {
341                                 log_error("Cannot add evdev device to idev: %s", strerror(-r));
342                                 return r;
343                         }
344                 }
345
346                 break;
347         case SYSVIEW_EVENT_SESSION_DETACH:
348                 d = ev->session_detach.device;
349                 type = sysview_device_get_type(d);
350                 if (type == SYSVIEW_DEVICE_EVDEV) {
351                         r = idev_session_remove_evdev(e->idev_session, sysview_device_get_ud(d));
352                         if (r < 0) {
353                                 log_error("Cannot remove evdev device from idev: %s", strerror(-r));
354                                 return r;
355                         }
356                 }
357
358                 break;
359         case SYSVIEW_EVENT_SESSION_CONTROL:
360                 r = ev->session_control.error;
361                 if (r < 0) {
362                         log_error("Cannot acquire session control: %s", strerror(-r));
363                         return r;
364                 }
365
366                 r = ioctl(1, KDSKBMODE, K_UNICODE);
367                 if (r < 0) {
368                         log_error("Cannot set K_UNICODE on stdout: %m");
369                         return -errno;
370                 }
371
372                 r = ioctl(1, KDSETMODE, KD_TEXT);
373                 if (r < 0) {
374                         log_error("Cannot set KD_TEXT on stdout: %m");
375                         return -errno;
376                 }
377
378                 printf("\n");
379
380                 break;
381         }
382
383         return 0;
384 }
385
386 static int evcat_run(Evcat *e) {
387         struct termios in_attr, saved_attr;
388         int r;
389
390         assert(e);
391
392         if (!e->managed && geteuid() > 0)
393                 log_warning("You run in unmanaged mode without being root. This is likely to produce no output..");
394
395         printf("evcat - Read and catenate events from selected input devices\n"
396                "        Running on seat '%s' in user-session '%s'\n"
397                "        Exit by pressing ^C or 'q'\n\n",
398                e->seat ? : "seat0", e->session ? : "<none>");
399
400         r = sysview_context_start(e->sysview, evcat_sysview_fn, e);
401         if (r < 0)
402                 goto out;
403
404         r = tcgetattr(0, &in_attr);
405         if (r < 0) {
406                 r = -errno;
407                 goto out;
408         }
409
410         saved_attr = in_attr;
411         in_attr.c_lflag &= ~ECHO;
412
413         r = tcsetattr(0, TCSANOW, &in_attr);
414         if (r < 0) {
415                 r = -errno;
416                 goto out;
417         }
418
419         r = sd_event_loop(e->event);
420         tcsetattr(0, TCSANOW, &saved_attr);
421         printf("exiting..\n");
422
423 out:
424         sysview_context_stop(e->sysview);
425         return r;
426 }
427
428 static int help(void) {
429         printf("%s [OPTIONS...]\n\n"
430                "Read and catenate events from selected input devices.\n\n"
431                "  -h --help               Show this help\n"
432                "     --version            Show package version\n"
433                , program_invocation_short_name);
434
435         return 0;
436 }
437
438 static int parse_argv(int argc, char *argv[]) {
439         enum {
440                 ARG_VERSION = 0x100,
441         };
442         static const struct option options[] = {
443                 { "help",       no_argument,    NULL, 'h'               },
444                 { "version",    no_argument,    NULL, ARG_VERSION       },
445                 {},
446         };
447         int c;
448
449         assert(argc >= 0);
450         assert(argv);
451
452         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
453                 switch (c) {
454                 case 'h':
455                         help();
456                         return 0;
457
458                 case ARG_VERSION:
459                         puts(PACKAGE_STRING);
460                         puts(SYSTEMD_FEATURES);
461                         return 0;
462
463                 case '?':
464                         return -EINVAL;
465
466                 default:
467                         assert_not_reached("Unhandled option");
468                 }
469
470         if (argc > optind) {
471                 log_error("Too many arguments");
472                 return -EINVAL;
473         }
474
475         return 1;
476 }
477
478 int main(int argc, char *argv[]) {
479         _cleanup_(evcat_freep) Evcat *e = NULL;
480         int r;
481
482         log_set_target(LOG_TARGET_AUTO);
483         log_parse_environment();
484         log_open();
485
486         setlocale(LC_ALL, "");
487         if (!is_locale_utf8())
488                 log_warning("Locale is not set to UTF-8. Codepoints will not be printed!");
489
490         r = parse_argv(argc, argv);
491         if (r <= 0)
492                 goto finish;
493
494         r = evcat_new(&e);
495         if (r < 0)
496                 goto finish;
497
498         r = evcat_run(e);
499
500 finish:
501         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
502 }