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