chiark / gitweb /
terminal/subterm: skip setting parent's cursor
[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                 if (sd_event_get_exit_code(e->event, &r) == -ENODATA)
334                         sd_event_exit(e->event, 0);
335                 break;
336         case SYSVIEW_EVENT_SESSION_ATTACH:
337                 d = ev->session_attach.device;
338                 type = sysview_device_get_type(d);
339                 if (type == SYSVIEW_DEVICE_EVDEV) {
340                         r = idev_session_add_evdev(e->idev_session, sysview_device_get_ud(d));
341                         if (r < 0) {
342                                 log_error("Cannot add evdev device to idev: %s", strerror(-r));
343                                 return r;
344                         }
345                 }
346
347                 break;
348         case SYSVIEW_EVENT_SESSION_DETACH:
349                 d = ev->session_detach.device;
350                 type = sysview_device_get_type(d);
351                 if (type == SYSVIEW_DEVICE_EVDEV) {
352                         r = idev_session_remove_evdev(e->idev_session, sysview_device_get_ud(d));
353                         if (r < 0) {
354                                 log_error("Cannot remove evdev device from idev: %s", strerror(-r));
355                                 return r;
356                         }
357                 }
358
359                 break;
360         case SYSVIEW_EVENT_SESSION_CONTROL:
361                 r = ev->session_control.error;
362                 if (r < 0) {
363                         log_error("Cannot acquire session control: %s", strerror(-r));
364                         return r;
365                 }
366
367                 r = ioctl(1, KDSKBMODE, K_UNICODE);
368                 if (r < 0) {
369                         log_error("Cannot set K_UNICODE on stdout: %m");
370                         return -errno;
371                 }
372
373                 r = ioctl(1, KDSETMODE, KD_TEXT);
374                 if (r < 0) {
375                         log_error("Cannot set KD_TEXT on stdout: %m");
376                         return -errno;
377                 }
378
379                 printf("\n");
380
381                 break;
382         }
383
384         return 0;
385 }
386
387 static int evcat_run(Evcat *e) {
388         struct termios in_attr, saved_attr;
389         int r;
390
391         assert(e);
392
393         if (!e->managed && geteuid() > 0)
394                 log_warning("You run in unmanaged mode without being root. This is likely to produce no output..");
395
396         printf("evcat - Read and catenate events from selected input devices\n"
397                "        Running on seat '%s' in user-session '%s'\n"
398                "        Exit by pressing ^C or 'q'\n\n",
399                e->seat ? : "seat0", e->session ? : "<none>");
400
401         r = sysview_context_start(e->sysview, evcat_sysview_fn, e);
402         if (r < 0)
403                 goto out;
404
405         r = tcgetattr(0, &in_attr);
406         if (r < 0) {
407                 r = -errno;
408                 goto out;
409         }
410
411         saved_attr = in_attr;
412         in_attr.c_lflag &= ~ECHO;
413
414         r = tcsetattr(0, TCSANOW, &in_attr);
415         if (r < 0) {
416                 r = -errno;
417                 goto out;
418         }
419
420         r = sd_event_loop(e->event);
421         tcsetattr(0, TCSANOW, &saved_attr);
422         printf("exiting..\n");
423
424 out:
425         sysview_context_stop(e->sysview);
426         return r;
427 }
428
429 static int help(void) {
430         printf("%s [OPTIONS...]\n\n"
431                "Read and catenate events from selected input devices.\n\n"
432                "  -h --help               Show this help\n"
433                "     --version            Show package version\n"
434                , program_invocation_short_name);
435
436         return 0;
437 }
438
439 static int parse_argv(int argc, char *argv[]) {
440         enum {
441                 ARG_VERSION = 0x100,
442         };
443         static const struct option options[] = {
444                 { "help",       no_argument,    NULL, 'h'               },
445                 { "version",    no_argument,    NULL, ARG_VERSION       },
446                 {},
447         };
448         int c;
449
450         assert(argc >= 0);
451         assert(argv);
452
453         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
454                 switch (c) {
455                 case 'h':
456                         help();
457                         return 0;
458
459                 case ARG_VERSION:
460                         puts(PACKAGE_STRING);
461                         puts(SYSTEMD_FEATURES);
462                         return 0;
463
464                 case '?':
465                         return -EINVAL;
466
467                 default:
468                         assert_not_reached("Unhandled option");
469                 }
470
471         if (argc > optind) {
472                 log_error("Too many arguments");
473                 return -EINVAL;
474         }
475
476         return 1;
477 }
478
479 int main(int argc, char *argv[]) {
480         _cleanup_(evcat_freep) Evcat *e = NULL;
481         int r;
482
483         log_set_target(LOG_TARGET_AUTO);
484         log_parse_environment();
485         log_open();
486
487         setlocale(LC_ALL, "");
488         if (!is_locale_utf8())
489                 log_warning("Locale is not set to UTF-8. Codepoints will not be printed!");
490
491         r = parse_argv(argc, argv);
492         if (r <= 0)
493                 goto finish;
494
495         r = evcat_new(&e);
496         if (r < 0)
497                 goto finish;
498
499         r = evcat_run(e);
500
501 finish:
502         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
503 }