chiark / gitweb /
f4c25b555658e8b84bbf9c7ea3ea53b252422c9c
[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                 return log_error_errno(r, "Cannot retrieve logind session: %m");
142
143         r = sd_session_get_seat(e->session, &e->seat);
144         if (r < 0)
145                 return log_error_errno(r, "Cannot retrieve seat of logind session: %m");
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
202         /* Resync state */
203         printf(" | %-6s", data->resync ? "RESYNC" : "");
204
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
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
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);
317                 if (r < 0)
318                         return log_error_errno(r, "Cannot create idev session: %m");
319
320                 if (e->managed) {
321                         r = sysview_session_take_control(ev->session_add.session);
322                         if (r < 0)
323                                 return log_error_errno(r, "Cannot request session control: %m");
324                 }
325
326                 idev_session_enable(e->idev_session);
327
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);
332                 if (sd_event_get_exit_code(e->event, &r) == -ENODATA)
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                                 return log_error_errno(r, "Cannot add evdev device to idev: %m");
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));
350                         if (r < 0)
351                                 return log_error_errno(r, "Cannot remove evdev device from idev: %m");
352                 }
353
354                 break;
355         case SYSVIEW_EVENT_SESSION_CONTROL:
356                 r = ev->session_control.error;
357                 if (r < 0)
358                         return log_error_errno(r, "Cannot acquire session control: %m");
359
360                 r = ioctl(1, KDSKBMODE, K_UNICODE);
361                 if (r < 0)
362                         return log_error_errno(errno, "Cannot set K_UNICODE on stdout: %m");
363
364                 r = ioctl(1, KDSETMODE, KD_TEXT);
365                 if (r < 0)
366                         return log_error_errno(errno, "Cannot set KD_TEXT on stdout: %m");
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 }