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" :
206 /* Keycode that triggered the event */
207 printf(" | %5u", (unsigned)k->keycode);
209 /* Well-known name of the keycode */
210 printf(" | %-20s", libevdev_event_code_get_name(EV_KEY, k->keycode) ? : "<unknown>");
212 /* Well-known modifiers */
213 printf(" | %-5s", (k->mods & IDEV_KBDMOD_SHIFT) ? "SHIFT" : "");
214 printf(" %-4s", (k->mods & IDEV_KBDMOD_CTRL) ? "CTRL" : "");
215 printf(" %-3s", (k->mods & IDEV_KBDMOD_ALT) ? "ALT" : "");
216 printf(" %-5s", (k->mods & IDEV_KBDMOD_LINUX) ? "LINUX" : "");
217 printf(" %-4s", (k->mods & IDEV_KBDMOD_CAPS) ? "CAPS" : "");
219 /* Resolved symbols */
221 for (i = 0; i < k->n_syms; ++i) {
223 xkb_keysym_get_name(k->keysyms[i], buf, sizeof(buf));
225 if (is_locale_utf8()) {
226 c = k->codepoints[i];
227 if (c < 0x110000 && c > 0x20 && (c < 0x7f || c > 0x9f)) {
228 /* "%4lc" doesn't work well, so hard-code it */
229 cwidth = mk_wcwidth(c);
233 printf(" '%lc':", (wchar_t)c);
239 printf(" XKB_KEY_%-30s", buf);
245 static bool kdata_is_exit(idev_data *data) {
246 idev_data_keyboard *k = &data->keyboard;
253 return k->codepoints[0] == 'q';
256 static int evcat_idev_fn(idev_session *session, void *userdata, idev_event *ev) {
260 case IDEV_EVENT_DEVICE_ADD:
261 idev_device_enable(ev->device_add.device);
263 case IDEV_EVENT_DEVICE_REMOVE:
264 idev_device_disable(ev->device_remove.device);
266 case IDEV_EVENT_DEVICE_DATA:
267 switch (ev->device_data.data.type) {
268 case IDEV_DATA_KEYBOARD:
269 if (kdata_is_exit(&ev->device_data.data))
270 sd_event_exit(e->event, 0);
272 kdata_print(&ev->device_data.data);
283 static int evcat_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) {
284 unsigned int flags, type;
291 case SYSVIEW_EVENT_SESSION_FILTER:
292 if (streq_ptr(e->session, ev->session_filter.id))
296 case SYSVIEW_EVENT_SESSION_ADD:
297 assert(!e->idev_session);
299 name = sysview_session_get_name(ev->session_add.session);
303 flags |= IDEV_SESSION_MANAGED;
305 r = idev_session_new(&e->idev_session,
312 log_error("Cannot create idev session: %s", strerror(-r));
317 r = sysview_session_take_control(ev->session_add.session);
319 log_error("Cannot request session control: %s", strerror(-r));
324 idev_session_enable(e->idev_session);
327 case SYSVIEW_EVENT_SESSION_REMOVE:
328 idev_session_disable(e->idev_session);
329 e->idev_session = idev_session_free(e->idev_session);
330 sd_event_exit(e->event, 0);
332 case SYSVIEW_EVENT_SESSION_ATTACH:
333 d = ev->session_attach.device;
334 type = sysview_device_get_type(d);
335 if (type == SYSVIEW_DEVICE_EVDEV) {
336 r = idev_session_add_evdev(e->idev_session, sysview_device_get_ud(d));
338 log_error("Cannot add evdev device to idev: %s", strerror(-r));
344 case SYSVIEW_EVENT_SESSION_DETACH:
345 d = ev->session_detach.device;
346 type = sysview_device_get_type(d);
347 if (type == SYSVIEW_DEVICE_EVDEV) {
348 r = idev_session_remove_evdev(e->idev_session, sysview_device_get_ud(d));
350 log_error("Cannot remove evdev device from idev: %s", strerror(-r));
356 case SYSVIEW_EVENT_SESSION_CONTROL:
357 r = ev->session_control.error;
359 log_error("Cannot acquire session control: %s", strerror(-r));
363 r = ioctl(1, KDSKBMODE, K_UNICODE);
365 log_error("Cannot set K_UNICODE on stdout: %m");
369 r = ioctl(1, KDSETMODE, KD_TEXT);
371 log_error("Cannot set KD_TEXT on stdout: %m");
383 static int evcat_run(Evcat *e) {
384 struct termios in_attr, saved_attr;
389 if (!e->managed && geteuid() > 0)
390 log_warning("You run in unmanaged mode without being root. This is likely to produce no output..");
392 printf("evcat - Read and catenate events from selected input devices\n"
393 " Running on seat '%s' in user-session '%s'\n"
394 " Exit by pressing ^C or 'q'\n\n",
395 e->seat ? : "seat0", e->session ? : "<none>");
397 r = sysview_context_start(e->sysview, evcat_sysview_fn, e);
401 r = tcgetattr(0, &in_attr);
407 saved_attr = in_attr;
408 in_attr.c_lflag &= ~ECHO;
410 r = tcsetattr(0, TCSANOW, &in_attr);
416 r = sd_event_loop(e->event);
417 tcsetattr(0, TCSANOW, &saved_attr);
418 printf("exiting..\n");
421 sysview_context_stop(e->sysview);
425 static int help(void) {
426 printf("%s [OPTIONS...]\n\n"
427 "Read and catenate events from selected input devices.\n\n"
428 " -h --help Show this help\n"
429 " --version Show package version\n"
430 , program_invocation_short_name);
435 static int parse_argv(int argc, char *argv[]) {
439 static const struct option options[] = {
440 { "help", no_argument, NULL, 'h' },
441 { "version", no_argument, NULL, ARG_VERSION },
449 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
456 puts(PACKAGE_STRING);
457 puts(SYSTEMD_FEATURES);
464 assert_not_reached("Unhandled option");
468 log_error("Too many arguments");
475 int main(int argc, char *argv[]) {
476 _cleanup_(evcat_freep) Evcat *e = NULL;
479 log_set_target(LOG_TARGET_AUTO);
480 log_parse_environment();
483 setlocale(LC_ALL, "");
484 if (!is_locale_utf8())
485 log_warning("Locale is not set to UTF-8. Codepoints will not be printed!");
487 r = parse_argv(argc, argv);
498 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;