chiark / gitweb /
terminal/idev: don't remove consumed-mods from kbd-matches
[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         /* 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("Cannot create idev session: %s", strerror(-r));
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("Cannot request session control: %s", strerror(-r));
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("Cannot add evdev device to idev: %s", strerror(-r));
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("Cannot remove evdev device from idev: %s", strerror(-r));
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("Cannot acquire session control: %s", strerror(-r));
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 }