quakeforge/libs/input/evdev/inputlib.c
Bill Currie 12ecc81470 [input] Use non-blocking IO for reading events
evdev can send multiple event packets for a single "event", but QF was
reading them one per frame, thus the feeling of buffered input at lower
frame rates (because they were buffered in the kernel). This also takes
care of most of the jerky motion with my 3d mouse, though there is still
a weird snap every second or so when rotating and translating at the
same time.
2024-02-02 08:41:26 +09:00

518 lines
11 KiB
C

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <linux/input.h>
#include <linux/joystick.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <string.h>
#include "QF/dstring.h"
#include "QF/sys.h"
#include "evdev/hotplug.h"
#include "evdev/inputlib.h"
static const char *devinput_path = "/dev/input";
static device_t *devices;
void (*device_add) (device_t *);
void (*device_remove) (device_t *);
static void
setup_buttons (device_t *dev)
{
int i, j, len;
unsigned char buf[1024];
button_t *button;
dev->max_button = -1;
dev->num_buttons = 0;
dev->button_map = 0;
dev->buttons = 0;
len = ioctl (dev->fd, EVIOCGBIT (EV_KEY, sizeof (buf)), buf);
for (i = 0; i < len; i++) {
//Sys_Printf("%c%02x", !(i % 16) ? '\n': !(i % 8) ? '-' : ' ', buf[i]);
for (j = 0; j < 8; j++) {
if (buf[i] & (1 << j)) {
dev->num_buttons++;
dev->max_button = i * 8 + j;
}
}
}
//Sys_Printf("\n");
dev->button_map = malloc ((dev->max_button + 1) * sizeof (int));
dev->buttons = malloc (dev->num_buttons * sizeof (button_t));
for (i = 0, button = dev->buttons; i < len; i++) {
for (j = 0; j < 8; j++) {
int button_ind = i * 8 + j;
if (buf[i] & (1 << j)) {
button->num = button - dev->buttons;
button->evnum = button_ind;
button->state = 0;
dev->button_map[button_ind] = button->num;
button++;
} else {
if (button_ind <= dev->max_button) {
dev->button_map[button_ind] = -1;
}
}
}
}
len = ioctl (dev->fd, EVIOCGKEY (sizeof (buf)), buf);
for (i = 0; i < dev->num_buttons; i++) {
int key = dev->buttons[i].evnum;
dev->buttons[i].state = !!(buf[key / 8] & (1 << (key % 8)));
}
}
static int
count_axes (const unsigned char *buf, int len, int *max_axis)
{
int count = 0;
int i, j;
for (i = 0; i < len; i++) {
for (j = 0; j < 8; j++) {
if (buf[i] & (1 << j)) {
count++;
*max_axis = i * 8 + j;
}
}
}
return count;
}
static void
abs_info (device_t *dev, int axis_ind, axis_t *axis)
{
struct input_absinfo absinfo;
ioctl (dev->fd, EVIOCGABS(axis_ind), &absinfo);
axis->value = absinfo.value;
axis->min = absinfo.minimum;
axis->max = absinfo.maximum;
}
static void
rel_info (device_t *dev, int axis_ind, axis_t *axis)
{
// relative axes are marked by having 0 min/max
axis->value = 0;
axis->min = 0;
axis->max = 0;
}
static void
map_axes (const unsigned char *buf, int len, device_t *dev,
int max_axis, int *axis_map, axis_t *first_axis,
void (*info)(device_t*, int, axis_t *))
{
int i, j;
axis_t *axis;
for (i = 0, axis = first_axis; i < len; i++) {
for (j = 0; j < 8; j++) {
int axis_ind = i * 8 + j;
if (buf[i] & (1 << j)) {
axis->num = axis - dev->axes;
axis->evnum = axis_ind;
axis_map[axis_ind] = axis->num;
info (dev, axis_ind, axis);
axis++;
} else {
if (axis_ind <= max_axis) {
axis_map[axis_ind] = -1;
}
}
}
}
}
static void
setup_axes (device_t *dev)
{
int alen, rlen;
unsigned char abuf[1024];
unsigned char rbuf[1024];
dev->max_abs_axis = -1;
dev->max_rel_axis = -1;
dev->num_axes = 0;
dev->abs_axis_map = 0;
dev->rel_axis_map = 0;
dev->axes = 0;
alen = ioctl (dev->fd, EVIOCGBIT (EV_ABS, sizeof (abuf)), abuf);
rlen = ioctl (dev->fd, EVIOCGBIT (EV_REL, sizeof (rbuf)), rbuf);
dev->num_abs_axes = count_axes (abuf, alen, &dev->max_abs_axis);
dev->num_rel_axes = count_axes (rbuf, alen, &dev->max_rel_axis);
dev->num_axes = dev->num_abs_axes + dev->num_rel_axes;
dev->abs_axis_map = malloc ((dev->max_abs_axis + 1) * sizeof (int));
dev->rel_axis_map = malloc ((dev->max_rel_axis + 1) * sizeof (int));
dev->axes = malloc (dev->num_axes * sizeof (axis_t));
map_axes (abuf, alen, dev, dev->max_abs_axis, dev->abs_axis_map,
dev->axes, abs_info);
map_axes (rbuf, rlen, dev, dev->max_rel_axis, dev->rel_axis_map,
dev->axes + dev->num_abs_axes, rel_info);
}
static void device_created (const char *name);
static void device_deleted (const char *name);
#define get_string(fd, ioctlid, dstr) \
({ \
int size; \
while ((size = ioctl (fd, ioctlid (dstr->truesize), dstr->str)) \
== (int) dstr->truesize) { \
dstr->size = dstr->truesize + 1024; \
dstring_adjust (dstr); \
} \
dstr->size = size <= 0 ? 1 : size; \
dstr->str[dstr->size - 1] = 0; \
dstr->str; \
})
static int
check_device (const char *path)
{
device_t *dev;
int fd;
fd = open (path, O_RDWR | O_NONBLOCK);
if (fd == -1)
return -1;
dev = malloc (sizeof (device_t));
dev->next = devices;
dev->prev = &devices;
if (devices) {
devices->prev = &dev->next;
}
devices = dev;
dev->path = strdup (path);
dev->fd = fd;
dstring_t *buff = dstring_newstr ();
dev->name = strdup (get_string (fd, EVIOCGNAME, buff));
dev->phys = strdup (get_string (fd, EVIOCGPHYS, buff));
dev->uniq = strdup (get_string (fd, EVIOCGUNIQ, buff));
dstring_delete (buff);
setup_buttons(dev);
setup_axes(dev);
dev->event_count = 0;
dev->data = 0;
dev->axis_event = 0;
dev->button_event = 0;
//Sys_Printf ("%s:\n", path);
//Sys_Printf ("\tname: %s\n", dev->name);
//Sys_Printf ("\tbuttons: %d\n", dev->num_buttons);
//Sys_Printf ("\taxes: %d\n", dev->num_axes);
if (device_add) {
device_add (dev);
}
return fd;
}
static const char *event_codes[] = {
"EV_SYN",
"EV_KEY",
"EV_REL",
"EV_ABS",
"EV_MSC",
"EV_SW",
0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
"EV_LED",
"EV_SND",
"EV_REP",
"EV_FF",
"EV_PWR",
"EV_FF_STATUS",
};
static void
read_device_input (device_t *dev)
{
struct input_event event;
button_t *button;
axis_t *axis;
//int i;
// zero motion counters for relative axes
//for (i = dev->num_abs_axes; i < dev->num_axes; i++) {
// dev->axes[i].value = 0;
//}
while (1) {
if (read (dev->fd, &event, sizeof (event)) < 0) {
if (errno != EWOULDBLOCK) {
perror(dev->name);
dev->fd = -1;
}
return;
}
if (0) {
const char *ev = event_codes[event.type];
Sys_Printf ("%6d(%s) %6d %6x\n", event.type, ev ? ev : "?",
event.code, event.value);
}
switch (event.type) {
case EV_SYN:
dev->event_count++;
break;
case EV_KEY:
button = &dev->buttons[dev->button_map[event.code]];
button->state = event.value;
if (dev->button_event) {
dev->button_event (button, dev->data);
}
break;
case EV_ABS:
axis = &dev->axes[dev->abs_axis_map[event.code]];
axis->value = event.value;
if (dev->axis_event) {
dev->axis_event (axis, dev->data);
}
break;
case EV_MSC:
break;
case EV_REL:
axis = &dev->axes[dev->rel_axis_map[event.code]];
//Sys_Printf ("EV_REL %6d %6x %6d %p\n", event.code, event.value,
// dev->rel_axis_map[event.code], axis);
axis->value = event.value;
if (dev->axis_event) {
dev->axis_event (axis, dev->data);
}
break;
case EV_SW:
case EV_LED:
case EV_SND:
case EV_REP:
case EV_FF:
case EV_PWR:
case EV_FF_STATUS:
//Sys_Printf ("%6d %6d %6x\n", event.type, event.code, event.value);
break;
}
}
}
void
inputlib_add_select (fd_set *fdset, int *maxfd)
{
inputlib_hotplug_add_select (fdset, maxfd);
for (device_t *dev = devices; dev; dev = dev->next) {
if (dev->fd < 0) {
continue;
}
FD_SET (dev->fd, fdset);
if (dev->fd > *maxfd) {
*maxfd = dev->fd;
}
}
}
void
inputlib_check_select (fd_set *fdset)
{
inputlib_hotplug_check_select (fdset);
for (device_t *dev = devices; dev; dev = dev->next) {
if (dev->fd < 0) {
continue;
}
if (FD_ISSET (dev->fd, fdset)) {
read_device_input (dev);
}
}
}
int
inputlib_check_input (void)
{
fd_set fdset;
struct timeval _timeout;
struct timeval *timeout = &_timeout;
int res;
int maxfd = -1;
_timeout.tv_sec = 0;
_timeout.tv_usec = 0;
FD_ZERO (&fdset);
inputlib_add_select (&fdset, &maxfd);
if (maxfd < 0) {
return 0;
}
res = select (maxfd + 1, &fdset, NULL, NULL, timeout);
if (res <= 0) {
return 0;
}
inputlib_check_select (&fdset);
return 1;
}
static void
close_device (device_t *dev)
{
if (dev->next) {
dev->next->prev = dev->prev;
}
*dev->prev = dev->next;
if (device_remove) {
device_remove (dev);
}
close (dev->fd);
free (dev->button_map);
if (dev->buttons) {
free (dev->buttons);
}
free (dev->abs_axis_map);
free (dev->rel_axis_map);
if (dev->axes) {
free (dev->axes);
}
free (dev->phys);
free (dev->uniq);
free (dev->name);
free (dev->path);
free (dev);
}
static char *
make_devname (const char *path, const char *name)
{
int plen = strlen (path);
int nlen = strlen (name);
char *devname = malloc (plen + nlen + 2);
strcpy (devname, path);
devname[plen] = '/';
strcpy (devname + plen + 1, name);
return devname;
}
static int
check_input_device (const char *path, const char *name)
{
int ret;
char *devname = make_devname (path, name);
//puts (devname);
ret = check_device (devname);
free (devname);
return ret;
}
static void
device_created (const char *name)
{
char *devname = make_devname (devinput_path, name);
device_t *dev;
int olddev = 0;
for (dev = devices; dev; dev = dev->next) {
if (strcmp (dev->path, devname) == 0) {
// already have this device open
olddev = 1;
break;
}
}
if (!olddev && check_device (devname) >= 0) {
//Sys_Printf ("found device %s\n", devname);
}
free (devname);
}
static void
device_deleted (const char *name)
{
char *devname = make_devname (devinput_path, name);
device_t **dev;
for (dev = &devices; *dev; dev = &(*dev)->next) {
if (strcmp ((*dev)->path, devname) == 0) {
//Sys_Printf ("lost device %s\n", (*dev)->path);
close_device (*dev);
break;
}
}
free (devname);
}
static int
scan_devices (void)
{
struct dirent *dirent;
DIR *dir;
dir = opendir (devinput_path);
if (!dir) {
return -1;
}
while ((dirent = readdir (dir))) {
if (dirent->d_type != DT_CHR) {
continue;
}
if (strncmp (dirent->d_name, "event", 5)) {
continue;
}
if (check_input_device (devinput_path, dirent->d_name) < 0) {
continue;
}
//Sys_Printf("%s\n", dirent->d_name);
}
closedir (dir);
return 0;
}
int
inputlib_init (void (*dev_add) (device_t *), void (*dev_rem) (device_t *))
{
device_add = dev_add;
device_remove = dev_rem;
if (scan_devices () != -1) {
inputlib_hotplug_init (devinput_path, device_created, device_deleted);
return 0;
}
return -1;
}
void
inputlib_close (void)
{
inputlib_hotplug_close ();
while (devices) {
close_device (devices);
}
}