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 /* Resolved symbols */
224 for (i = 0; i < k->n_syms; ++i) {
226 xkb_keysym_get_name(k->keysyms[i], buf, sizeof(buf));
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);
236 printf(" '%lc':", (wchar_t)c);
242 printf(" XKB_KEY_%-30s", buf);
248 static bool kdata_is_exit(idev_data *data) {
249 idev_data_keyboard *k = &data->keyboard;
256 return k->codepoints[0] == 'q';
259 static int evcat_idev_fn(idev_session *session, void *userdata, idev_event *ev) {
263 case IDEV_EVENT_DEVICE_ADD:
264 idev_device_enable(ev->device_add.device);
266 case IDEV_EVENT_DEVICE_REMOVE:
267 idev_device_disable(ev->device_remove.device);
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);
275 kdata_print(&ev->device_data.data);
286 static int evcat_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) {
287 unsigned int flags, type;
294 case SYSVIEW_EVENT_SESSION_FILTER:
295 if (streq_ptr(e->session, ev->session_filter.id))
299 case SYSVIEW_EVENT_SESSION_ADD:
300 assert(!e->idev_session);
302 name = sysview_session_get_name(ev->session_add.session);
306 flags |= IDEV_SESSION_MANAGED;
308 r = idev_session_new(&e->idev_session,
315 log_error("Cannot create idev session: %s", strerror(-r));
320 r = sysview_session_take_control(ev->session_add.session);
322 log_error("Cannot request session control: %s", strerror(-r));
327 idev_session_enable(e->idev_session);
330 case SYSVIEW_EVENT_SESSION_REMOVE:
331 idev_session_disable(e->idev_session);
332 e->idev_session = idev_session_free(e->idev_session);
333 sd_event_exit(e->event, 0);
335 case SYSVIEW_EVENT_SESSION_ATTACH:
336 d = ev->session_attach.device;
337 type = sysview_device_get_type(d);
338 if (type == SYSVIEW_DEVICE_EVDEV) {
339 r = idev_session_add_evdev(e->idev_session, sysview_device_get_ud(d));
341 log_error("Cannot add evdev device to idev: %s", strerror(-r));
347 case SYSVIEW_EVENT_SESSION_DETACH:
348 d = ev->session_detach.device;
349 type = sysview_device_get_type(d);
350 if (type == SYSVIEW_DEVICE_EVDEV) {
351 r = idev_session_remove_evdev(e->idev_session, sysview_device_get_ud(d));
353 log_error("Cannot remove evdev device from idev: %s", strerror(-r));
359 case SYSVIEW_EVENT_SESSION_CONTROL:
360 r = ev->session_control.error;
362 log_error("Cannot acquire session control: %s", strerror(-r));
366 r = ioctl(1, KDSKBMODE, K_UNICODE);
368 log_error("Cannot set K_UNICODE on stdout: %m");
372 r = ioctl(1, KDSETMODE, KD_TEXT);
374 log_error("Cannot set KD_TEXT on stdout: %m");
386 static int evcat_run(Evcat *e) {
387 struct termios in_attr, saved_attr;
392 if (!e->managed && geteuid() > 0)
393 log_warning("You run in unmanaged mode without being root. This is likely to produce no output..");
395 printf("evcat - Read and catenate events from selected input devices\n"
396 " Running on seat '%s' in user-session '%s'\n"
397 " Exit by pressing ^C or 'q'\n\n",
398 e->seat ? : "seat0", e->session ? : "<none>");
400 r = sysview_context_start(e->sysview, evcat_sysview_fn, e);
404 r = tcgetattr(0, &in_attr);
410 saved_attr = in_attr;
411 in_attr.c_lflag &= ~ECHO;
413 r = tcsetattr(0, TCSANOW, &in_attr);
419 r = sd_event_loop(e->event);
420 tcsetattr(0, TCSANOW, &saved_attr);
421 printf("exiting..\n");
424 sysview_context_stop(e->sysview);
428 static int help(void) {
429 printf("%s [OPTIONS...]\n\n"
430 "Read and catenate events from selected input devices.\n\n"
431 " -h --help Show this help\n"
432 " --version Show package version\n"
433 , program_invocation_short_name);
438 static int parse_argv(int argc, char *argv[]) {
442 static const struct option options[] = {
443 { "help", no_argument, NULL, 'h' },
444 { "version", no_argument, NULL, ARG_VERSION },
452 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
459 puts(PACKAGE_STRING);
460 puts(SYSTEMD_FEATURES);
467 assert_not_reached("Unhandled option");
471 log_error("Too many arguments");
478 int main(int argc, char *argv[]) {
479 _cleanup_(evcat_freep) Evcat *e = NULL;
482 log_set_target(LOG_TARGET_AUTO);
483 log_parse_environment();
486 setlocale(LC_ALL, "");
487 if (!is_locale_utf8())
488 log_warning("Locale is not set to UTF-8. Codepoints will not be printed!");
490 r = parse_argv(argc, argv);
501 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;