/**
* @file evdev.c
* Mode Control Entity - evdev input device handling
*
* Copyright (c) 2012 - 2020 Jolla Ltd.
* Copyright (c) 2020 Open Mobile Platform LLC.
*
* @author Simo Piiroinen
*
* 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 "evdev.h"
#include "mce-log.h"
#include
#include
#include
#include
#include
#include
#include
#include
/** Number of elements in array of fixed size */
#define numof(a) (sizeof(a)/sizeof*(a))
/* Autogenerated lookup tables */
#include "evdev.inc"
/** Number to string lookup with bounds checking
*/
static const char *lookup(const char * const *lut, size_t cnt, int val)
{
return ((size_t)val < cnt) ? lut[val] : 0;
}
/** Helper for reporting unknown events without using dynamic memory
*
*
* Uses small statically allocated ringbuffer so that few invocations
* can be made before the previously returned strings get overwritten.
*
* This allows correct behavior in cases where more than one event
* name lookup fails within construction of one diagnostic message.
* For example something like this:
*
* printf("event %s - code %s\n",
* evdev_get_event_type_name(etype),
* evdev_get_event_code_name(etype, ecode));
*/
static const char *unknown(const char *fmt, ...)
__attribute__((format(printf, 1, 2)));
static const char *unknown(const char *fmt, ...)
{
static char buff[4][16];
static unsigned slot = 0;
slot = (slot + 1) & 3;
va_list va;
va_start(va, fmt);
vsnprintf(buff[slot], sizeof *buff, fmt, va);
va_end(va);
return buff[slot];
}
/* Internal helper functions for looking up event codes by type */
static const char * syn_name(int ecode)
{
return lookup(lut_syn, numof(lut_syn), ecode) ?: unknown("SYN_%04x", ecode);
}
static const char * key_name(int ecode)
{
return lookup(lut_key, numof(lut_key), ecode) ?: unknown("KEY_%04x", ecode);
}
static const char * rel_name(int ecode)
{
return lookup(lut_rel, numof(lut_rel), ecode) ?: unknown("REL_%04x", ecode);
}
static const char * abs_name(int ecode)
{
return lookup(lut_abs, numof(lut_abs), ecode) ?: unknown("ABS_%04x", ecode);
}
static const char * msc_name(int ecode)
{
return lookup(lut_msc, numof(lut_msc), ecode) ?: unknown("MSC_%04x", ecode);
}
static const char * led_name(int ecode)
{
return lookup(lut_led, numof(lut_led), ecode) ?: unknown("LED_%04x", ecode);
}
static const char * rep_name(int ecode)
{
return lookup(lut_rep, numof(lut_rep), ecode) ?: unknown("REP_%04x", ecode);
}
static const char * snd_name(int ecode)
{
return lookup(lut_snd, numof(lut_snd), ecode) ?: unknown("SND_%04x", ecode);
}
static const char * sw_name(int ecode)
{
return lookup(lut_sw, numof(lut_sw), ecode) ?: unknown("SW_%04x", ecode);
}
static const char * ff_name(int ecode)
{
return lookup(lut_ff, numof(lut_ff), ecode) ?: unknown("FF_%04x", ecode);
}
static const char * pwr_name(int ecode)
{
return unknown("PWR_%04x", ecode);
}
/** How many "unsigned long" elements bitmap needs to cover bc bits */
#define BMAP_SIZE(bc) (((bc)+LONG_BIT-1)/LONG_BIT)
/** Test a bit in array of unsigned longs
*
* @param bmap array of longs
* @param bi bit index
*
* @return 1 if bit is set, 0 if not
*/
static int bit_is_set(unsigned long *bmap, size_t bi)
{
size_t i = bi / LONG_BIT;
unsigned long m = 1ul << (bi % LONG_BIT);
return (bmap[i] & m) != 0;
}
/** Helper for getting terminal width
*
* @return guestimate of number of characters per line
*/
static int get_terminal_width(void)
{
int res;
struct winsize ws;
const char *env;
if( (env = getenv("COLUMNS")) )
{
if( (res = strtol(env, 0, 10)) > 10 )
{
return res;
}
}
res = 80;
if( ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1 ||
ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) != -1 ||
ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) != -1 )
{
res = ws.ws_col;
}
return res;
}
/** Lookup human readable name for input event code
*
* @param etype input event type
* @param ecode input event code
*
* @return Name of input event code as string
*/
const char *evdev_get_event_code_name(int etype, int ecode)
{
typedef const char *(*lookup_fn)(int);
static const lookup_fn lut[] =
{
[EV_SYN] = syn_name,
[EV_KEY] = key_name,
[EV_REL] = rel_name,
[EV_ABS] = abs_name,
[EV_MSC] = msc_name,
[EV_SW] = sw_name,
[EV_LED] = led_name,
[EV_SND] = snd_name,
[EV_REP] = rep_name,
[EV_FF] = ff_name,
[EV_PWR] = pwr_name,
};
if( (size_t)etype < numof(lut) && lut[etype] )
{
return lut[etype](ecode);
}
return unknown("%02x_%04x", etype, ecode);
}
/** Lookup human readable name for input event type
*
* @param etype input event type
*
* @return Name of input event type as string
*/
const char * evdev_get_event_type_name(int etype)
{
return lookup(lut_ev, numof(lut_ev), etype) ?: unknown("EV_%02x", etype);
}
/** Open input device in read only mode
*
* @param path input device path to open
*
* @returns file descriptor on success, or
* -1 if the file can't be opened / it is not an input device
*/
int evdev_open_device(const char *path)
{
int fd;
if( (fd = open(path, O_RDONLY)) != -1 )
{
int vers = 0;
if( ioctl(fd, EVIOCGVERSION, &vers) == -1)
{
// assume this is not an input device node then
close(fd), fd = -1;
}
}
return fd;
}
/** Write input device information to stdout
*
* @param fd file descriptor
*
* @return 0 on success, or -1 in case of errors
*/
int evdev_identify_device(int fd)
{
int err = -1;
int vers;
int cols;
unsigned long bmap_type[BMAP_SIZE(EV_CNT)];
unsigned long bmap_code[BMAP_SIZE(KEY_CNT)];
unsigned long bmap_stat[BMAP_SIZE(KEY_CNT)];
char linkpath[PATH_MAX];
char path[PATH_MAX];
int n;
if( fd < 0 )
{
goto cleanup;
}
snprintf(linkpath, sizeof linkpath, "/proc/self/fd/%d", fd);
if( (n = readlink(linkpath, path, sizeof path - 1)) <= 0 )
{
strcpy(path, "unknown");
}
else
{
path[n] = 0;
}
// is it evdev file descriptor?
if( ioctl(fd, EVIOCGVERSION, &vers) == -1)
{
goto cleanup;
}
// device name
{
char name[256];;
if( ioctl(fd, EVIOCGNAME(sizeof(name)), name) == -1 )
{
mce_log(LL_WARN, "%s: EVIOCGNAME: %m", path);
strcpy(name, "Unknown");
}
printf("Name: \"%s\"\n", name);
}
// device id
{
unsigned short id[4];
if( ioctl(fd, EVIOCGID, id) == -1 )
{
mce_log(LL_WARN, "%s: EVIOCGID: %m", path);
memset(id, 0, sizeof id);
}
printf("ID: bus 0x%x, vendor, 0x%x, product 0x%x, version 0x%x\n",
id[ID_BUS], id[ID_VENDOR], id[ID_PRODUCT], id[ID_VERSION]);
}
// get bitmask of supported event types
memset(bmap_type, 0, sizeof(bmap_type));
if( ioctl(fd, EVIOCGBIT(0, EV_CNT), bmap_type) == -1 )
{
mce_log(LL_WARN, "%s: EVIOCGBIT(0): %m", path );
goto cleanup;
}
// guestimate how wide event code listing we can make
cols = get_terminal_width() - 10;
if( cols < 32 ) cols = 72;
// list supported event types and codes
for( int etype = 0; etype < EV_CNT; ++etype )
{
if( !bit_is_set(bmap_type, etype) )
{
continue;
}
printf("Type 0x%02x (%s)\n", etype, evdev_get_event_type_name(etype));
if( etype == EV_SYN || etype == EV_REP )
{
// EVIOCGBIT(0, n) returns event types supported, not
// what SYN_xxx codes are supported ... skip it
continue;
}
memset(bmap_code, 0, sizeof(bmap_code));
if( ioctl(fd, EVIOCGBIT(etype, KEY_CNT), bmap_code) == -1 )
{
mce_log(LL_WARN, "%s: EVIOCGBIT(%s): %m", path,
evdev_get_event_type_name(etype));
continue;
}
memset(bmap_stat, 0, sizeof(bmap_stat));
switch( etype )
{
case EV_KEY: ioctl(fd, EVIOCGKEY(KEY_CNT), bmap_stat); break;
case EV_LED: ioctl(fd, EVIOCGLED(LED_CNT), bmap_stat); break;
case EV_SND: ioctl(fd, EVIOCGSND(SND_CNT), bmap_stat); break;
case EV_SW: ioctl(fd, EVIOCGSW(SW_CNT), bmap_stat); break;
default: break;
}
int len = 0;
for( int ecode = 0; ecode < KEY_CNT; ++ecode )
{
if( bit_is_set(bmap_code, ecode) )
{
const char *tag = evdev_get_event_code_name(etype, ecode);
int set = bit_is_set(bmap_stat, ecode);
int add = strlen(tag) + 1 + set;
char val[32] = "";
if( etype == EV_ABS )
{
struct input_absinfo info;
memset(&info, 0, sizeof info);
if( ioctl(fd, EVIOCGABS(ecode), &info) == -1 )
{
mce_log(LL_ERR, "EVIOCGABS(%s): %m", tag);
}
else
{
snprintf(val, sizeof val, "=%d [%d,%d]", info.value, info.minimum, info.maximum);
add += strlen(val);
}
}
if( len == 0 ) printf("\t");
else if( len+add > cols ) printf("\n\t"), len = 0;
len += add, printf(" %s%s%s", tag, set ? "*" : "", val);
}
}
if( len ) printf("\n");
}
err = 0;
cleanup:
return err;
}
static int rlookup(const char * const *lut, size_t cnt, const char *name)
{
int val = -1;
for( size_t i = 0; i < cnt; ++i )
{
if( lut[i] && !strcmp(lut[i], name) )
{
val = (int)i;
break;
}
}
return val;
}
/* Internal helper functions for looking up event codes by type */
static int syn_code(const char *ename)
{
return rlookup(lut_syn, numof(lut_syn), ename);
}
static int key_code(const char *ename)
{
return rlookup(lut_key, numof(lut_key), ename);
}
static int rel_code(const char *ename)
{
return rlookup(lut_rel, numof(lut_rel), ename);
}
static int abs_code(const char *ename)
{
return rlookup(lut_abs, numof(lut_abs), ename);
}
static int msc_code(const char *ename)
{
return rlookup(lut_msc, numof(lut_msc), ename);
}
static int led_code(const char *ename)
{
return rlookup(lut_led, numof(lut_led), ename);
}
static int rep_code(const char *ename)
{
return rlookup(lut_rep, numof(lut_rep), ename);
}
static int snd_code(const char *ename)
{
return rlookup(lut_snd, numof(lut_snd), ename);
}
static int sw_code(const char *ename)
{
return rlookup(lut_sw, numof(lut_sw), ename);
}
static int ff_code(const char *ename)
{
return rlookup(lut_ff, numof(lut_ff), ename);
}
static int pwr_code(const char *ename)
{
(void)ename; // not used
return -1;
}
/** Lookup input event code by name
*
* @param etype input event type
* @param ename input event name
*
* @return input event code, or -1 in case of errors
*/
int evdev_lookup_event_code(int etype, const char *ename)
{
typedef int (*lookup_fn)(const char *);
static const lookup_fn lut[] =
{
[EV_SYN] = syn_code,
[EV_KEY] = key_code,
[EV_REL] = rel_code,
[EV_ABS] = abs_code,
[EV_MSC] = msc_code,
[EV_SW] = sw_code,
[EV_LED] = led_code,
[EV_SND] = snd_code,
[EV_REP] = rep_code,
[EV_FF] = ff_code,
[EV_PWR] = pwr_code,
};
if( (size_t)etype < numof(lut) && lut[etype] )
{
return lut[etype](ename);
}
return -1;
}