/**
* @file evdev_trace.c
* Mode Control Entity - cli utility for inspecting evdev input devices
*
* Copyright (C) 2013-2019 Jolla Ltd.
*
* @author Simo Piiroinen
* @author Mikko Harju
*
* mce is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* mce 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 mce. If not, see .
*/
#include "../mce-log.h"
#include "../evdev.h"
#include "fileusers.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/** Flag for: emit event time stamps */
static bool emit_event_time = true;
/** Flag for: emit time of day (of event read time) */
static bool emit_time_of_day = false;
/** Read and show input events
*
* @param fd input device file descriptor to read from
* @param title text to print before event details
*
* @return positive value on success, 0 on eof, -1 on errors
*/
static
int
process_events(int fd, const char *title)
{
struct input_event eve[256];
char tod[64], toe[64];
errno = 0;
int n = read(fd, eve, sizeof eve);
if( n < 0 )
{
mce_log(LL_ERR, "%s: %m", title);
return -1;
}
if( n == 0 )
{
mce_log(LL_ERR, "%s: EOF", title);
return 0;
}
n /= sizeof *eve;
*tod = 0;
if( emit_time_of_day )
{
struct timeval tv;
struct tm tm;
memset(&tv, 0, sizeof tv);
memset(&tm, 0, sizeof tm);
/* Caveat emptor: time of day = event read time ... */
gettimeofday(&tv, 0);
localtime_r(&tv.tv_sec, &tm);
snprintf(tod, sizeof tod, "%04d-%02d-%02d %02d:%02d:%02d.%03ld - ",
tm.tm_year + 1900,
tm.tm_mon + 1,
tm.tm_mday,
tm.tm_hour,
tm.tm_min,
tm.tm_sec,
(long)(tv.tv_usec / 1000));
}
for( int i = 0; i < n; ++i )
{
struct input_event *e = &eve[i];
*toe = 0;
if( emit_event_time )
{
snprintf(toe, sizeof toe, "%ld.%03ld - ",
(long)e->input_event_sec,
(long)e->input_event_usec / 1000);
}
printf("%s: %s%s0x%02x/%s - 0x%03x/%s - %d\n",
title, tod, toe,
e->type,
evdev_get_event_type_name(e->type),
e->code,
evdev_get_event_code_name(e->type, e->code),
e->value);
}
return 1;
}
/** Mainloop for processing event input devices
*
* @param path vector of input device paths
* @param count number of paths in the path
* @param identify if nonzero print input device information
* @param trace stay in loop and print out events as they arrive
*/
static
void
mainloop(char **path, int count, int identify, int trace)
{
struct pollfd *pfd = calloc(count, sizeof *pfd);
int closed = 0;
for( int i = 0; i < count; ++i )
{
if( (pfd[i].fd = evdev_open_device(path[i])) == -1 )
{
++closed;
continue;
}
if( identify )
{
printf("----====( %s )====----\n", path[i]);
evdev_identify_device(pfd[i].fd);
GSList *list = fileusers_get(path[i]);
if( list ) {
printf("Readers:\n");
for( GSList *item = list; item; item = item->next )
{
fileuser_t *fu = item->data;
printf("\t%s(pid=%d,fd=%d)\n", fu->cmd, fu->pid, fu->fd);
}
g_slist_free(list);
}
printf("\n");
}
}
if( !trace )
{
goto cleanup;
}
while( closed < count )
{
for( int i = 0; i < count; ++i )
{
pfd[i].events = (pfd[i].fd < 0) ? 0 : POLLIN;
}
poll(pfd, count, -1);
for( int i = 0; i < count; ++i )
{
if( pfd[i].revents )
{
if( process_events(pfd[i].fd, path[i]) <= 0 )
{
close(pfd[i].fd);
pfd[i].fd = -1;
++closed;
}
}
}
}
cleanup:
for( int i = 0; i < count; ++i )
{
if( pfd[i].fd != -1 ) close(pfd[i].fd);
}
free(pfd);
}
/** Configuration table for long command line options */
static struct option optL[] =
{
{ "help", 0, 0, 'h' },
{ "trace", 0, 0, 't' },
{ "identify", 0, 0, 'i' },
{ "show-readers", 0, 0, 'I' },
{ "emit-also-tod", 0, 0, 'e' },
{ "emit-only-tod", 0, 0, 'E' },
{ 0,0,0,0 }
};
/** Configuration string for short command line options */
static const char optS[] =
"h" // --help
"t" // --trace
"i" // --identify
"I" // --show-readers
"e" // --emit-also-tod
"E" // --emit-only-tod
;
/** Program name string */
static const char *progname = 0;
/** Compatibility with mce-log.h
*/
void
mce_log_file(loglevel_t loglevel,
const char *const file,
const char *const function,
const char *const fmt, ...)
{
const char *lev = "?";
char *msg = 0;
va_list va;
(void)file, (void)function; // unused
switch( loglevel )
{
case LL_CRIT: lev = "C"; break;
case LL_ERR: lev = "E"; break;
case LL_WARN: lev = "W"; break;
case LL_NOTICE: lev = "N"; break;
case LL_INFO: lev = "I"; break;
case LL_DEBUG: lev = "D"; break;
default: break;
}
va_start(va, fmt);
if( vasprintf(&msg, fmt, va) < 0 )
{
msg = 0;
}
va_end(va);
fprintf(stderr, "%s: %s: %s\n", progname, lev, msg ?: "error");
free(msg);
}
/** Stub for compatibility with mce-log.h
*/
int mce_log_p_(const loglevel_t loglevel,
const char *const file,
const char *const func)
{
(void)loglevel;
(void)file;
(void)func;
return true;
}
/** Provide runtime usage information
*/
static void usage(void)
{
printf("USAGE\n"
" %s [options] [devicepath] ...\n"
"\n"
"OPTIONS\n"
" -h, --help -- this help text\n"
" -i, --identify -- identify input device\n"
" -t, --trace -- trace input events\n"
" -e, --emit-also-tod -- emit also time of day\n"
" -E, --emit-only-tod -- emit only time of day\n"
" -I, --show-readers -- identify processes using devices\n"
"\n"
"NOTES\n"
" If no device paths are given, /dev/input/event* is assumed.\n"
" \n"
" Full device path is not required, \"/dev/input/event1\" can\n"
" be shortened to \"event1\" or just \"1\".\n"
"\n",
progname);
}
/** Resolve device name given at command line to evdev path
*/
static char *get_device_path(const char *hint)
{
char temp[256];
// usable as is?
if( access(hint, F_OK) == 0 )
{
goto cleanup;
}
// try couple of prefixes
snprintf(temp, sizeof temp, "/dev/input/%s", hint);
if( access(temp, F_OK) == 0 )
{
hint = temp;
goto cleanup;
}
snprintf(temp, sizeof temp, "/dev/input/event%s", hint);
if( access(temp, F_OK) == 0 )
{
hint = temp;
goto cleanup;
}
// failure
mce_log(LL_WARN, "%s: device file not found", hint);
hint = 0;
cleanup:
return hint ? strdup(hint) : 0;
}
/** Main entry point
*/
int
main(int argc, char **argv)
{
int result = EXIT_FAILURE;
int f_trace = 0;
int f_identify = 0;
int f_readers = 0;
setlinebuf(stdout);
glob_t gb;
memset(&gb, 0, sizeof gb);
progname = basename(*argv);
for( ;; )
{
int opt = getopt_long(argc, argv, optS, optL, 0);
if( opt < 0 )
{
break;
}
switch( opt )
{
case 'h':
usage();
exit(EXIT_SUCCESS);
case 't':
f_trace = 1;
break;
case 'i':
f_identify = 1;
break;
case 'I':
f_identify = f_readers = 1;
break;
case 'e':
emit_time_of_day = true;
break;
case 'E':
emit_time_of_day = true;
emit_event_time = false;
break;
case '?':
case ':':
goto cleanup;
default:
fprintf(stderr, "getopt() -> %d\n", opt);
goto cleanup;
}
}
if( !f_identify && !f_trace )
{
f_identify = 1;
}
if( f_readers )
{
fileusers_init();
}
if( optind < argc )
{
argc = 0;
for( int i = optind; argv[i]; ++i )
{
char *path = get_device_path(argv[i]);
if( path ) argv[argc++] = path;
}
mainloop(argv, argc, f_identify, f_trace);
while( argc > 0 )
{
free(argv[--argc]);
}
}
else
{
static const char pattern[] = "/dev/input/event*";
if( glob(pattern, 0, 0, &gb) != 0 )
{
printf("%s: no matching files found\n", pattern);
goto cleanup;
}
mainloop(gb.gl_pathv, gb.gl_pathc, f_identify, f_trace);
}
result = EXIT_SUCCESS;
cleanup:
globfree(&gb);
fileusers_quit();
return result;
}