1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
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.
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.
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/>.
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
32 #include <libevdev/libevdev.h>
40 #include <sys/ioctl.h>
42 #include <systemd/sd-bus.h>
43 #include <systemd/sd-event.h>
44 #include <systemd/sd-login.h>
47 #include <xkbcommon/xkbcommon.h>
50 #include "event-util.h"
54 #include "term-internal.h"
57 typedef struct Evcat Evcat;
64 sysview_context *sysview;
66 idev_session *idev_session;
71 static Evcat *evcat_free(Evcat *e) {
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);
84 tcflush(0, TCIOFLUSH);
89 DEFINE_TRIVIAL_CLEANUP_FUNC(Evcat*, evcat_free);
91 static bool is_managed(const char *session) {
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. */
113 r = sd_session_get_vt(session, &vtnr);
114 if (r < 0 || vtnr < 1 || vtnr > 63)
118 r = ioctl(1, KDGETMODE, &mode);
119 if (r < 0 || mode != KD_TEXT)
123 if (r < 0 || minor(st.st_rdev) != vtnr)
129 static int evcat_new(Evcat **out) {
130 _cleanup_(evcat_freep) Evcat *e = NULL;
139 r = sd_pid_get_session(getpid(), &e->session);
141 log_error("Cannot retrieve logind session: %s", strerror(-r));
145 r = sd_session_get_seat(e->session, &e->seat);
147 log_error("Cannot retrieve seat of logind session: %s", strerror(-r));
151 e->managed = is_managed(e->session);
153 r = sd_event_default(&e->event);
157 r = sd_bus_open_system(&e->bus);
161 r = sd_bus_attach_event(e->bus, e->event, SD_EVENT_PRIORITY_NORMAL);
165 r = sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1);
169 r = sd_event_add_signal(e->event, NULL, SIGTERM, NULL, NULL);
173 r = sd_event_add_signal(e->event, NULL, SIGINT, NULL, NULL);
177 r = sysview_context_new(&e->sysview,
178 SYSVIEW_CONTEXT_SCAN_LOGIND |
179 SYSVIEW_CONTEXT_SCAN_EVDEV,
186 r = idev_context_new(&e->idev, e->event, e->bus);
195 static void kdata_print(idev_data *data) {
196 idev_data_keyboard *k = &data->keyboard;
201 /* Key-press state: UP/DOWN/REPEAT */
202 printf(" %-6s", k->value == 0 ? "UP" :
203 k->value == 1 ? "DOWN" :
207 printf(" | %-6s", data->resync ? "RESYNC" : "");
209 /* Keycode that triggered the event */
210 printf(" | %5u", (unsigned)k->keycode);
212 /* Well-known name of the keycode */
213 printf(" | %-20s", libevdev_event_code_get_name(EV_KEY, k->keycode) ? : "<unknown>");
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" : "");
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" : "");
229 /* Resolved symbols */
231 for (i = 0; i < k->n_syms; ++i) {
233 xkb_keysym_get_name(k->keysyms[i], buf, sizeof(buf));
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);
243 printf(" '%lc':", (wchar_t)c);
249 printf(" XKB_KEY_%-30s", buf);
255 static bool kdata_is_exit(idev_data *data) {
256 idev_data_keyboard *k = &data->keyboard;
263 return k->codepoints[0] == 'q';
266 static int evcat_idev_fn(idev_session *session, void *userdata, idev_event *ev) {
270 case IDEV_EVENT_DEVICE_ADD:
271 idev_device_enable(ev->device_add.device);
273 case IDEV_EVENT_DEVICE_REMOVE:
274 idev_device_disable(ev->device_remove.device);
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);
282 kdata_print(&ev->device_data.data);
293 static int evcat_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) {
294 unsigned int flags, type;
301 case SYSVIEW_EVENT_SESSION_FILTER:
302 if (streq_ptr(e->session, ev->session_filter.id))
306 case SYSVIEW_EVENT_SESSION_ADD:
307 assert(!e->idev_session);
309 name = sysview_session_get_name(ev->session_add.session);
313 flags |= IDEV_SESSION_MANAGED;
315 r = idev_session_new(&e->idev_session,
322 log_error("Cannot create idev session: %s", strerror(-r));
327 r = sysview_session_take_control(ev->session_add.session);
329 log_error("Cannot request session control: %s", strerror(-r));
334 idev_session_enable(e->idev_session);
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);
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));
349 log_error("Cannot add evdev device to idev: %s", strerror(-r));
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));
361 log_error("Cannot remove evdev device from idev: %s", strerror(-r));
367 case SYSVIEW_EVENT_SESSION_CONTROL:
368 r = ev->session_control.error;
370 log_error("Cannot acquire session control: %s", strerror(-r));
374 r = ioctl(1, KDSKBMODE, K_UNICODE);
376 log_error("Cannot set K_UNICODE on stdout: %m");
380 r = ioctl(1, KDSETMODE, KD_TEXT);
382 log_error("Cannot set KD_TEXT on stdout: %m");
394 static int evcat_run(Evcat *e) {
395 struct termios in_attr, saved_attr;
400 if (!e->managed && geteuid() > 0)
401 log_warning("You run in unmanaged mode without being root. This is likely to produce no output..");
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>");
408 r = sysview_context_start(e->sysview, evcat_sysview_fn, e);
412 r = tcgetattr(0, &in_attr);
418 saved_attr = in_attr;
419 in_attr.c_lflag &= ~ECHO;
421 r = tcsetattr(0, TCSANOW, &in_attr);
427 r = sd_event_loop(e->event);
428 tcsetattr(0, TCSANOW, &saved_attr);
429 printf("exiting..\n");
432 sysview_context_stop(e->sysview);
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);
446 static int parse_argv(int argc, char *argv[]) {
450 static const struct option options[] = {
451 { "help", no_argument, NULL, 'h' },
452 { "version", no_argument, NULL, ARG_VERSION },
460 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
467 puts(PACKAGE_STRING);
468 puts(SYSTEMD_FEATURES);
475 assert_not_reached("Unhandled option");
479 log_error("Too many arguments");
486 int main(int argc, char *argv[]) {
487 _cleanup_(evcat_freep) Evcat *e = NULL;
490 log_set_target(LOG_TARGET_AUTO);
491 log_parse_environment();
494 setlocale(LC_ALL, "");
495 if (!is_locale_utf8())
496 log_warning("Locale is not set to UTF-8. Codepoints will not be printed!");
498 r = parse_argv(argc, argv);
509 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;