/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ /*** This file is part of systemd. Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> systemd is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. systemd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ /* * Event Catenation * The evcat tool catenates input events of all requested devices and prints * them to standard-output. It's only meant for debugging of input-related * problems. */ #include <errno.h> #include <getopt.h> #include <libevdev/libevdev.h> #include <linux/kd.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <systemd/sd-bus.h> #include <systemd/sd-event.h> #include <systemd/sd-login.h> #include <termios.h> #include <unistd.h> #include <xkbcommon/xkbcommon.h> #include "build.h" #include "event-util.h" #include "idev.h" #include "macro.h" #include "sysview.h" #include "term-internal.h" #include "util.h" typedef struct Evcat Evcat; struct Evcat { char *session; char *seat; sd_event *event; sd_bus *bus; sysview_context *sysview; idev_context *idev; idev_session *idev_session; bool managed : 1; }; static Evcat *evcat_free(Evcat *e) { if (!e) return NULL; e->idev_session = idev_session_free(e->idev_session); e->idev = idev_context_unref(e->idev); e->sysview = sysview_context_free(e->sysview); e->bus = sd_bus_unref(e->bus); e->event = sd_event_unref(e->event); free(e->seat); free(e->session); free(e); tcflush(0, TCIOFLUSH); return NULL; } DEFINE_TRIVIAL_CLEANUP_FUNC(Evcat*, evcat_free); static bool is_managed(const char *session) { unsigned int vtnr; struct stat st; long mode; int r; /* Using logind's Controller API is highly fragile if there is already * a session controller running. If it is registered as controller * itself, TakeControl will simply fail. But if its a legacy controller * that does not use logind's controller API, we must never register * our own controller. Otherwise, we really mess up the VT. Therefore, * only run in managed mode if there's no-one else. */ if (geteuid() == 0) return false; if (!isatty(1)) return false; if (!session) return false; r = sd_session_get_vt(session, &vtnr); if (r < 0 || vtnr < 1 || vtnr > 63) return false; mode = 0; r = ioctl(1, KDGETMODE, &mode); if (r < 0 || mode != KD_TEXT) return false; r = fstat(1, &st); if (r < 0 || minor(st.st_rdev) != vtnr) return false; return true; } static int evcat_new(Evcat **out) { _cleanup_(evcat_freep) Evcat *e = NULL; int r; assert(out); e = new0(Evcat, 1); if (!e) return log_oom(); r = sd_pid_get_session(getpid(), &e->session); if (r < 0) return log_error_errno(r, "Cannot retrieve logind session: %m"); r = sd_session_get_seat(e->session, &e->seat); if (r < 0) return log_error_errno(r, "Cannot retrieve seat of logind session: %m"); e->managed = is_managed(e->session); r = sd_event_default(&e->event); if (r < 0) return r; r = sd_bus_open_system(&e->bus); if (r < 0) return r; r = sd_bus_attach_event(e->bus, e->event, SD_EVENT_PRIORITY_NORMAL); if (r < 0) return r; r = sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1); if (r < 0) return r; r = sd_event_add_signal(e->event, NULL, SIGTERM, NULL, NULL); if (r < 0) return r; r = sd_event_add_signal(e->event, NULL, SIGINT, NULL, NULL); if (r < 0) return r; r = sysview_context_new(&e->sysview, SYSVIEW_CONTEXT_SCAN_LOGIND | SYSVIEW_CONTEXT_SCAN_EVDEV, e->event, e->bus, NULL); if (r < 0) return r; r = idev_context_new(&e->idev, e->event, e->bus); if (r < 0) return r; *out = e; e = NULL; return 0; } static void kdata_print(idev_data *data) { idev_data_keyboard *k = &data->keyboard; char buf[128]; uint32_t i, c; int cwidth; /* Key-press state: UP/DOWN/REPEAT */ printf(" %-6s", k->value == 0 ? "UP" : k->value == 1 ? "DOWN" : "REPEAT"); /* Resync state */ printf(" | %-6s", data->resync ? "RESYNC" : ""); /* Keycode that triggered the event */ printf(" | %5u", (unsigned)k->keycode); /* Well-known name of the keycode */ printf(" | %-20s", libevdev_event_code_get_name(EV_KEY, k->keycode) ? : "<unknown>"); /* Well-known modifiers */ printf(" | %-5s", (k->mods & IDEV_KBDMOD_SHIFT) ? "SHIFT" : ""); printf(" %-4s", (k->mods & IDEV_KBDMOD_CTRL) ? "CTRL" : ""); printf(" %-3s", (k->mods & IDEV_KBDMOD_ALT) ? "ALT" : ""); printf(" %-5s", (k->mods & IDEV_KBDMOD_LINUX) ? "LINUX" : ""); printf(" %-4s", (k->mods & IDEV_KBDMOD_CAPS) ? "CAPS" : ""); /* Consumed modifiers */ printf(" | %-5s", (k->consumed_mods & IDEV_KBDMOD_SHIFT) ? "SHIFT" : ""); printf(" %-4s", (k->consumed_mods & IDEV_KBDMOD_CTRL) ? "CTRL" : ""); printf(" %-3s", (k->consumed_mods & IDEV_KBDMOD_ALT) ? "ALT" : ""); printf(" %-5s", (k->consumed_mods & IDEV_KBDMOD_LINUX) ? "LINUX" : ""); printf(" %-4s", (k->consumed_mods & IDEV_KBDMOD_CAPS) ? "CAPS" : ""); /* Resolved symbols */ printf(" |"); for (i = 0; i < k->n_syms; ++i) { buf[0] = 0; xkb_keysym_get_name(k->keysyms[i], buf, sizeof(buf)); if (is_locale_utf8()) { c = k->codepoints[i]; if (c < 0x110000 && c > 0x20 && (c < 0x7f || c > 0x9f)) { /* "%4lc" doesn't work well, so hard-code it */ cwidth = mk_wcwidth(c); while (cwidth++ < 2) printf(" "); printf(" '%lc':", (wchar_t)c); } else { printf(" "); } } printf(" XKB_KEY_%-30s", buf); } printf("\n"); } static bool kdata_is_exit(idev_data *data) { idev_data_keyboard *k = &data->keyboard; if (k->value != 1) return false; if (k->n_syms != 1) return false; return k->codepoints[0] == 'q'; } static int evcat_idev_fn(idev_session *session, void *userdata, idev_event *ev) { Evcat *e = userdata; switch (ev->type) { case IDEV_EVENT_DEVICE_ADD: idev_device_enable(ev->device_add.device); break; case IDEV_EVENT_DEVICE_REMOVE: idev_device_disable(ev->device_remove.device); break; case IDEV_EVENT_DEVICE_DATA: switch (ev->device_data.data.type) { case IDEV_DATA_KEYBOARD: if (kdata_is_exit(&ev->device_data.data)) sd_event_exit(e->event, 0); else kdata_print(&ev->device_data.data); break; } break; } return 0; } static int evcat_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) { unsigned int flags, type; Evcat *e = userdata; sysview_device *d; const char *name; int r; switch (ev->type) { case SYSVIEW_EVENT_SESSION_FILTER: if (streq_ptr(e->session, ev->session_filter.id)) return 1; break; case SYSVIEW_EVENT_SESSION_ADD: assert(!e->idev_session); name = sysview_session_get_name(ev->session_add.session); flags = 0; if (e->managed) flags |= IDEV_SESSION_MANAGED; r = idev_session_new(&e->idev_session, e->idev, flags, name, evcat_idev_fn, e); if (r < 0) return log_error_errno(r, "Cannot create idev session: %m"); if (e->managed) { r = sysview_session_take_control(ev->session_add.session); if (r < 0) return log_error_errno(r, "Cannot request session control: %m"); } idev_session_enable(e->idev_session); break; case SYSVIEW_EVENT_SESSION_REMOVE: idev_session_disable(e->idev_session); e->idev_session = idev_session_free(e->idev_session); if (sd_event_get_exit_code(e->event, &r) == -ENODATA) sd_event_exit(e->event, 0); break; case SYSVIEW_EVENT_SESSION_ATTACH: d = ev->session_attach.device; type = sysview_device_get_type(d); if (type == SYSVIEW_DEVICE_EVDEV) { r = idev_session_add_evdev(e->idev_session, sysview_device_get_ud(d)); if (r < 0) return log_error_errno(r, "Cannot add evdev device to idev: %m"); } break; case SYSVIEW_EVENT_SESSION_DETACH: d = ev->session_detach.device; type = sysview_device_get_type(d); if (type == SYSVIEW_DEVICE_EVDEV) { r = idev_session_remove_evdev(e->idev_session, sysview_device_get_ud(d)); if (r < 0) return log_error_errno(r, "Cannot remove evdev device from idev: %m"); } break; case SYSVIEW_EVENT_SESSION_CONTROL: r = ev->session_control.error; if (r < 0) return log_error_errno(r, "Cannot acquire session control: %m"); r = ioctl(1, KDSKBMODE, K_UNICODE); if (r < 0) return log_error_errno(errno, "Cannot set K_UNICODE on stdout: %m"); r = ioctl(1, KDSETMODE, KD_TEXT); if (r < 0) return log_error_errno(errno, "Cannot set KD_TEXT on stdout: %m"); printf("\n"); break; } return 0; } static int evcat_run(Evcat *e) { struct termios in_attr, saved_attr; int r; assert(e); if (!e->managed && geteuid() > 0) log_warning("You run in unmanaged mode without being root. This is likely to produce no output.."); printf("evcat - Read and catenate events from selected input devices\n" " Running on seat '%s' in user-session '%s'\n" " Exit by pressing ^C or 'q'\n\n", e->seat ? : "seat0", e->session ? : "<none>"); r = sysview_context_start(e->sysview, evcat_sysview_fn, e); if (r < 0) goto out; r = tcgetattr(0, &in_attr); if (r < 0) { r = -errno; goto out; } saved_attr = in_attr; in_attr.c_lflag &= ~ECHO; r = tcsetattr(0, TCSANOW, &in_attr); if (r < 0) { r = -errno; goto out; } r = sd_event_loop(e->event); tcsetattr(0, TCSANOW, &saved_attr); printf("exiting..\n"); out: sysview_context_stop(e->sysview); return r; } static int help(void) { printf("%s [OPTIONS...]\n\n" "Read and catenate events from selected input devices.\n\n" " -h --help Show this help\n" " --version Show package version\n" , program_invocation_short_name); return 0; } static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, }; static const struct option options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, ARG_VERSION }, {}, }; int c; assert(argc >= 0); assert(argv); while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) switch (c) { case 'h': help(); return 0; case ARG_VERSION: puts(PACKAGE_STRING); puts(SYSTEMD_FEATURES); return 0; case '?': return -EINVAL; default: assert_not_reached("Unhandled option"); } if (argc > optind) { log_error("Too many arguments"); return -EINVAL; } return 1; } int main(int argc, char *argv[]) { _cleanup_(evcat_freep) Evcat *e = NULL; int r; log_set_target(LOG_TARGET_AUTO); log_parse_environment(); log_open(); setlocale(LC_ALL, ""); if (!is_locale_utf8()) log_warning("Locale is not set to UTF-8. Codepoints will not be printed!"); r = parse_argv(argc, argv); if (r <= 0) goto finish; r = evcat_new(&e); if (r < 0) goto finish; r = evcat_run(e); finish: return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; }