Permalink
Fetching contributors…
Cannot retrieve contributors at this time
1933 lines (1654 sloc) 47.4 KB
/*
* Copyright 2014-2018, Björn Ståhl
* License: 3-Clause BSD, see COPYING file in arcan source repository.
* Reference: http://arcan-fe.com
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <inttypes.h>
#include <stdio.h>
#include <errno.h>
#include <poll.h>
#include <glob.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/input.h>
#include "arcan_shmif.h"
#include "arcan_math.h"
#include "arcan_general.h"
#include "arcan_event.h"
#include "arcan_led.h"
#include "arcan_video.h"
#include "arcan_videoint.h"
#include "keycode_xlate.h"
#include <linux/kd.h>
#include <sys/inotify.h>
#ifdef HAVE_XKBCOMMON
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-keysyms.h>
#include <xkbcommon/xkbcommon-compose.h>
/*
* shared between all event queues
*/
static struct xkb_context* xkb_context;
#endif
#ifdef _DEBUG
#define DEBUG 1
#else
#define DEBUG 0
#endif
#define debug_print(fmt, ...) \
do { if (DEBUG) arcan_warning("%s:%d:%s(): " fmt "\n", \
"evdev:", __LINE__, __func__,##__VA_ARGS__); } while (0)
/* #define verbose_print */
#define verbose_print debug_print
/*
* scan / probe a node- dir (ENVV overridable)
*/
#ifndef NOTIFY_SCAN_DIR
#define NOTIFY_SCAN_DIR "/dev/input"
#endif
static char* notify_scan_dir;
/*
* In happy-fun everything is user-space land, we face the policy problem of
* device nodes not being created with the desired permissions in an atomic
* manner. Combine with inotify and we may beat some deaemon to the races and
* there we go. For EACCES we go the 'retry a little later' route.
*/
static const int default_eacces_tries = 8;
static const int default_eacces_delay = 1000;
static struct {
char* path;
int tries;
int64_t last_ts;
} pending[8];
static struct {
bool mute, init;
int tty, notify;
int pending;
}
gstate = {
.notify = -1,
};
static const char* envopts[] = {
"scandir=path/to/folder", "Directory to monitor for device node hotplug "
"(Default: "NOTIFY_SCAN_DIR")",
"disable_ttyswap", "Disable tty- swapping signal handler",
#ifdef HAVE_XKBCOMMON
"", "",
"[XKB-ARGUMENTS]", "[these are ENV- only (fwd to libxkbcommon)]",
"XKB_DEFAULT_LAYOUT=lang", "enable XKB translation maps for keyboards",
"XKB_DEFAULT_VARIANT=variant", "define XKB layout variant",
"XKB_DEFAULT_MODEL=pc101", "define XKB keyboard model",
#endif
NULL
};
/*
* need a reasonable limit on the amount of allowed devices, should this become
* a problem -- whitelist. See lookup_devnode for an explanation on the problem
* with devid-.
*/
#define MAX_DEVICES 256
struct devnode;
#include "device_db.h"
struct axis_opts {
/* none, avg, drop */
enum ARCAN_ANALOGFILTER_KIND mode;
enum ARCAN_ANALOGFILTER_KIND oldmode;
int lower, upper, deadzone;
/* we won't get access to a good range distribution if we don't emit the first
* / last sample that got into the drop range */
bool inlzone, inuzone, indzone;
int kernel_sz;
int kernel_ofs;
int32_t flt_kernel[64];
};
static struct {
size_t n_devs, sz_nodes;
/* repeat is currently enforced uniformly across all keyboards, might be
* usecases where this is not preferable but there is no higher-level api
* that provides this granularity. */
unsigned period, delay;
unsigned short mouseid;
struct devnode* nodes;
struct pollfd* pollset;
} iodev = {0};
struct devnode {
int handle;
/* NULL&size terminated, with chain-block set of the previous one could not
* handle. This is to cover devices that could expose themselves as being
* aggregated KEY/DEV/etc. */
struct evhandler hnd;
char label[256];
char* path;
unsigned short devnum;
size_t button_count;
enum devnode_type type;
union {
struct {
struct axis_opts data;
} sensor;
struct {
unsigned short axes;
unsigned short buttons;
unsigned short relofs;
char hats[16];
struct axis_opts* adata;
} game;
struct {
uint16_t mx;
uint16_t my;
struct axis_opts flt[2];
} cursor;
struct {
unsigned state;
#ifdef HAVE_XKBCOMMON
struct xkb_keymap* xkb_layout;
struct xkb_state* xkb_state;
#endif
} keyboard;
};
/* because in this universe, pretty much any normal input device can
* also have a touch display. */
struct {
bool active;
bool pending;
int x;
int y;
int pressure;
int size;
int ind;
} touch;
/* and also possible act as a LED controller */
struct {
bool gotled;
int ctrlid;
int ind;
int fds[2];
} led;
};
static void got_device(struct arcan_evctx* ctx, int fd, const char*);
/* for other platforms and legacy, devid used to be allocated sequentially
* and swept linear, even though this platform do not work like that and we
* have a dynamic set of devices. For this reason, we split the 16 bit space
* into < MAX_DEVICES and >= MAX_DEVICES and a device a can be accessed by
* either id */
static struct devnode* lookup_devnode(int devid)
{
if (devid <= 0)
devid = iodev.mouseid;
if (devid < iodev.sz_nodes){
verbose_print("%d => %"PRIxPTR, devid, &iodev.nodes[devid]);
return &iodev.nodes[devid];
}
for (size_t i = 0; i < iodev.sz_nodes; i++){
if (iodev.nodes[i].devnum == devid){
verbose_print("%d:%zu => %"PRIxPTR, devid, i, &iodev.nodes[i]);
return &iodev.nodes[i];
}
}
verbose_print("%zu => %d", devid, -1);
return NULL;
}
/* another option to this mess (as the hashing thing doesn't seem to work out
* is to move identification/etc. to another level and just let whatever device
* node generator is active populate with coherent names. and use a hash of that
* name as the ID */
static bool identify(int fd, const char* path,
char* label, size_t label_sz, unsigned short* dnum)
{
if (-1 == ioctl(fd, EVIOCGNAME(label_sz), label)){
debug_print("input/identify: bad EVIOCGNAME, setting unknown\n");
snprintf(label, label_sz, "unknown");
}
else
verbose_print(
"input/identify(%d): %s name resolved to %s", fd, path, label);
struct input_id nodeid;
if (-1 == ioctl(fd, EVIOCGID, &nodeid)){
debug_print(
"input/identify(%d): no EVIOCGID, reason:%s", fd, strerror(errno));
return false;
}
/*
* first, check if any other subsystem knows about this one and ignore if so
*/
if (arcan_led_known(nodeid.vendor, nodeid.product)){
debug_print(
"led subsys know %d, %d\n", (int)nodeid.vendor, (int)nodeid.product);
arcan_led_init();
return false;
}
/* didn't find much on how unique eviocguniq actually was, nor common lengths
* or what not so just mix them in a buffer, hash and let unsigned overflow
* modulo take us down to 16bit */
size_t bpl = sizeof(long) * 8;
size_t nbits = ((EV_MAX)-1) / bpl + 1;
char buf[12 + nbits * sizeof(long)];
char bbuf[sizeof(buf)];
memset(buf, '\0', sizeof(buf));
memset(bbuf, '\0', sizeof(bbuf));
/* some test devices here answered to the ioctl and returned full empty UNIQs,
* do something to lower the likelihood of collisions */
unsigned long hash = 5381;
if (-1 == ioctl(fd, EVIOCGUNIQ(sizeof(buf)), buf) ||
memcmp(buf, bbuf, sizeof(buf)) == 0){
size_t llen = strlen(label);
for (size_t i = 0; i < llen; i++)
hash = ((hash << 5) + hash) + label[i];
llen = strlen(path);
for (size_t i = 0; i < llen; i++)
hash = ((hash << 5) + hash) + path[i];
buf[11] ^= nodeid.vendor >> 8;
buf[10] ^= nodeid.vendor;
buf[9] ^= nodeid.product >> 8;
buf[8] ^= nodeid.product;
buf[7] ^= nodeid.version >> 8;
buf[6] ^= nodeid.version;
/* even this point has a few collisions, particularly some keyboards and mice
* that don't respond to CGUNIQ and expose multiple- subdevices but with
* different button/axis count */
ioctl(fd, EVIOCGBIT(0, EV_MAX), &buf);
}
for (size_t i = 0; i < sizeof(buf); i++)
hash = ((hash << 5) + hash) + buf[i];
/* scan for collisions, if there is one, random and repeat. We lose
* repeatability but don't risk collision-disconnect spam */
for (ssize_t i = 0; i < iodev.sz_nodes; i++)
if (hash == iodev.nodes[i].devnum){
uint16_t rv;
arcan_random((uint8_t*)&rv, 2);
hash = rv;
i = -1;
continue;
}
/* 16-bit clamp is legacy in the scripting layer, also leave the highest
* bit unset as that is reserved for synthetic devices */
unsigned short devnum = hash % ((uint16_t) 0xfffe);
if (devnum < MAX_DEVICES)
devnum += MAX_DEVICES;
*dnum = devnum;
return true;
}
static inline bool process_axis(struct arcan_evctx* ctx,
struct axis_opts* daxis, int16_t samplev, int16_t* outv)
{
if (daxis->mode == ARCAN_ANALOGFILTER_NONE)
return false;
if (daxis->mode == ARCAN_ANALOGFILTER_PASS)
goto accept_sample;
/* quickfilter deadzone */
if (abs(samplev) < daxis->deadzone){
if (!daxis->indzone){
samplev = 0;
daxis->indzone = true;
}
else
return false;
}
else
daxis->indzone = false;
/* quickfilter out controller edgenoise */
if (samplev < daxis->lower){
if (!daxis->inlzone){
samplev = daxis->lower;
daxis->inlzone = true;
daxis->inuzone = false;
}
else
return false;
}
else if (samplev > daxis->upper){
if (!daxis->inuzone){
samplev = daxis->upper;
daxis->inuzone = true;
daxis->inlzone = false;
}
else
return false;
}
else
daxis->inlzone = daxis->inuzone = false;
daxis->flt_kernel[ daxis->kernel_ofs++ ] = samplev;
/* don't proceed until the kernel is filled */
if (daxis->kernel_ofs < daxis->kernel_sz)
return false;
if (daxis->kernel_sz > 1){
int32_t tot = 0;
if (daxis->mode == ARCAN_ANALOGFILTER_ALAST){
samplev = daxis->flt_kernel[daxis->kernel_sz - 1];
}
else {
for (int i = 0; i < daxis->kernel_sz; i++)
tot += daxis->flt_kernel[i];
samplev = tot != 0 ? tot / daxis->kernel_sz : 0;
}
}
else;
daxis->kernel_ofs = 0;
accept_sample:
*outv = samplev;
return true;
}
static void set_analogstate(struct axis_opts* dst,
int lower_bound, int upper_bound, int deadzone,
int kernel_size, enum ARCAN_ANALOGFILTER_KIND mode)
{
dst->lower = lower_bound;
dst->upper = upper_bound;
dst->deadzone = deadzone;
dst->kernel_sz = kernel_size;
dst->mode = mode;
dst->kernel_ofs = 0;
}
static struct axis_opts* find_axis(int devid, unsigned axisid, bool* outn)
{
struct devnode* node = lookup_devnode(devid);
*outn = node != NULL;
if (!node)
return NULL;
switch(node->type){
case DEVNODE_SENSOR:
return axisid == 0 ? &node->sensor.data : NULL;
break;
case DEVNODE_GAME:
if (axisid < node->game.axes)
return &node->game.adata[axisid];
break;
case DEVNODE_MOUSE:
if (axisid == 0)
return &node->cursor.flt[0];
else if (axisid == 1)
return &node->cursor.flt[1];
break;
default:
break;
}
return NULL;
}
arcan_errc platform_event_analogstate(int devid, int axisid,
int* lower_bound, int* upper_bound, int* deadzone,
int* kernel_size, enum ARCAN_ANALOGFILTER_KIND* mode)
{
bool gotnode;
struct axis_opts* axis = find_axis(devid, axisid, &gotnode);
if (!axis)
return gotnode ?
ARCAN_ERRC_BAD_RESOURCE : ARCAN_ERRC_NO_SUCH_OBJECT;
*lower_bound = axis->lower;
*upper_bound = axis->upper;
*deadzone = axis->deadzone;
*kernel_size = axis->kernel_sz;
*mode = axis->mode;
return ARCAN_OK;
}
void platform_event_analogall(bool enable, bool mouse)
{
struct devnode* node = lookup_devnode(iodev.mouseid);
if (!node)
return;
/*
* FIXME sweep all devices and all axes (or just mouseid) if (enable) then set
* whatever the previous mode was, else store current mode and set NONE
*/
}
void platform_event_analogfilter(int devid,
int axisid, int lower_bound, int upper_bound, int deadzone,
int buffer_sz, enum ARCAN_ANALOGFILTER_KIND kind)
{
bool node;
struct axis_opts* axis = find_axis(devid, axisid, &node);
if (!axis)
return;
int kernel_lim = sizeof(axis->flt_kernel) / sizeof(axis->flt_kernel[0]);
if (buffer_sz > kernel_lim)
buffer_sz = kernel_lim;
if (buffer_sz <= 0)
buffer_sz = 1;
set_analogstate(axis,lower_bound, upper_bound, deadzone, buffer_sz, kind);
}
static bool discovered(struct arcan_evctx* ctx,
const char* name, size_t name_len, bool nopending)
{
char buffer[name_len+sizeof(notify_scan_dir)];
char outbuffer[MAXPATHLEN];
/* need to resolve a symlink if there is one as the platform_device_open
* has a whitelist that is rather picky about which devices it will open */
snprintf(buffer, sizeof(buffer), "%s/%.*s", notify_scan_dir, (int)name_len, name);
int fd = platform_device_open(
readlink(buffer, outbuffer, sizeof(outbuffer)) > 0 ?
outbuffer : buffer, O_NONBLOCK| O_RDWR);
verbose_print("input: trying to add %s/%.*s",
notify_scan_dir, (int)name_len, name);
if (-1 == fd && errno == EACCES){
if (gstate.pending >= COUNT_OF(pending)){
debug_print(
"input: pending queue limit exceeded, possibly something wrong"
" with monitored folder (%s) and permissions.", notify_scan_dir
);
return false;
}
/* already know about this one */
if (nopending)
return false;
/* sign that someone is impatient and plugging / unplugging while pending */
size_t i;
ssize_t j = -1;
for (i = 0; i < COUNT_OF(pending); i++){
if (!pending[i].path && j == -1)
j = i;
if (pending[i].path && strcmp(name, pending[i].path) == 0)
return false;
}
/* name comes from inotify which does not have to terminate */
gstate.pending++;
pending[j].path = malloc(name_len + 1);
sprintf(pending[j].path, "%.*s", (int)name_len, name);
pending[j].tries = default_eacces_tries;
pending[j].last_ts = arcan_frametime();
return false;
}
/* even if we can access it and it is of the right type, it is not certain
* that we can actually identify and use it according with evdev */
if (-1 != fd){
got_device(ctx, fd, name);
return true;
}
else
arcan_warning("input: couldn't open new device (%s), reason: %s\n",
name, strerror(errno));
return false;
}
static void process_pending(struct arcan_evctx* ctx)
{
for (size_t i = 0; i < COUNT_OF(pending); i++){
if (!pending[i].path)
continue;
/* wait a little longer for each failed attempt */
if (arcan_frametime() - pending[i].last_ts < (default_eacces_tries -
pending[i].tries + 1) * default_eacces_delay)
continue;
pending[i].last_ts = arcan_frametime();
if (discovered(ctx, pending[i].path, strlen(pending[i].path), true)){
free(pending[i].path);
pending[i].path = NULL;
gstate.pending--;
}
else{
pending[i].tries--;
if (pending[i].tries <= 0){
arcan_warning("input(eperm): device(%s) retry count"
"exceeded\n", pending[i].path);
free(pending[i].path);
pending[i].path = NULL;
gstate.pending--;
}
}
}
}
static void disconnect(struct arcan_evctx* ctx, struct devnode* node)
{
struct arcan_event addev = {
.category = EVENT_IO,
.io.kind = EVENT_IO_STATUS,
.io.devid = node->devnum,
.io.devkind = EVENT_IDEVKIND_STATUS,
.io.input.status.devkind = node->type,
.io.input.status.action = EVENT_IDEV_REMOVED
};
snprintf((char*) &addev.io.label, sizeof(addev.io.label) /
sizeof(addev.io.label[0]), "%s", node->label);
arcan_event_enqueue(ctx, &addev);
for (size_t i = 0; i < iodev.sz_nodes; i++)
if (node->devnum == iodev.nodes[i].devnum){
close(node->handle);
free(node->path);
node->path = NULL;
node->handle = -1;
iodev.pollset[i].events = iodev.pollset[i].revents = 0;
iodev.pollset[i].fd = -1;
if (node->led.gotled){
iodev.pollset[i+iodev.sz_nodes].fd = -1;
iodev.pollset[i+iodev.sz_nodes].events =
iodev.pollset[i+iodev.sz_nodes].revents = 0;
node->led.gotled = false;
arcan_led_remove(node->led.ctrlid);
close(node->led.fds[0]);
close(node->led.fds[1]);
node->led.fds[0] = -1;
node->led.fds[1] = -1;
}
#ifdef HAVE_XKBCOMMON
if (node->type == DEVNODE_KEYBOARD && node->keyboard.xkb_state){
xkb_state_unref(node->keyboard.xkb_state);
xkb_keymap_unref(node->keyboard.xkb_layout);
node->keyboard.xkb_state = NULL;
node->keyboard.xkb_layout = NULL;
}
#endif
iodev.n_devs--;
}
}
static void do_led(struct devnode* node)
{
if (!node->led.gotled){
arcan_warning("evdev(), pollset corruption? POLLIN on node without LED\n");
return;
}
uint8_t buf[2];
bool set = false;
while (2 == read(node->led.fds[0], buf, 2)){
switch (tolower(buf[0])){
case 'A': node->led.ind = -1; break;
case 'a': node->led.ind = buf[1]; break;
/* not registered as a RGB led */
case 'r': break;
case 'g': break;
case 'b': break;
case 'i': set = buf[1] > 0; break;
case 'c':
if (node->led.ind == -1){
for (size_t i = 0; i < LED_MAX; i++)
if (-1 == write(node->handle, &(struct input_event){.type = EV_LED,
.code = i, .value = set}, sizeof(struct input_event)))
arcan_warning("platform/evdev: failed to write to led device\n");
}
else {
if (-1 == write(node->handle, &(struct input_event){.type = EV_LED,
.code = node->led.ind, .value = set}, sizeof(struct input_event)))
arcan_warning("platform/evdev: failed to write to led device\n");
}
break;
}
}
}
void platform_event_process(struct arcan_evctx* ctx)
{
/* lovely little variable length field at end of struct here /sarcasm,
* could get away with running the notify polling less often than once
* every frame, somewhat excessive. */
if (-1 != gstate.notify){
char inbuf[1024];
ssize_t nr = read(gstate.notify, inbuf, sizeof(inbuf));
off_t ofs = 0;
if (-1 != nr)
while (nr - ofs > sizeof(struct inotify_event)){
struct inotify_event cur;
memcpy(&cur, &inbuf[ofs], sizeof(struct inotify_event));
ofs += sizeof(struct inotify_event);
if ((cur.mask & IN_CREATE) && !(cur.mask & IN_ISDIR)){
discovered(ctx, &inbuf[ofs], cur.len, false);
ofs += cur.len;
}
}
}
if (gstate.pending)
process_pending(ctx);
int nr = poll(iodev.pollset, iodev.sz_nodes * 2, 0);
if (nr <= 0)
return;
for (size_t i = 0; i < iodev.sz_nodes; i++){
/* recall, sz_nodes is half the count, i + sz_nodes = alt-dev index */
if (iodev.pollset[i+iodev.sz_nodes].revents & POLLIN){
do_led(&iodev.nodes[i]);
}
if (iodev.pollset[i].fd == -1 || 0 == iodev.pollset[i].revents)
continue;
/* !POLLIN, then something is wrong, remove the node */
if (0 == (iodev.pollset[i].revents & POLLIN)){
disconnect(ctx, &iodev.nodes[i]);
continue;
}
/* some nodes may get a null handler temporarily or permanently assiged,
* drain those for evdev structures */
else {
if (iodev.nodes[i].hnd.handler)
iodev.nodes[i].hnd.handler(ctx, &iodev.nodes[i]);
else{
char dump[256];
size_t nr __attribute__((unused));
nr = read(iodev.nodes[i].handle, dump, 256);
}
}
}
}
void platform_event_samplebase(int devid, float xyz[3])
{
struct devnode* node = lookup_devnode(devid);
if (!node || node->type != DEVNODE_MOUSE)
return;
node->cursor.mx = xyz[0];
node->cursor.my = xyz[1];
}
void platform_event_keyrepeat(struct arcan_evctx* ctx, int* period, int* delay)
{
bool upd = false;
if (*period < 0){
*period = iodev.period;
}
else{
int tmp = *period;
*period = iodev.period;
iodev.period = tmp;
upd = true;
}
if (*delay < 0){
*delay = iodev.delay;
}
else {
int tmp = *delay;
*delay = iodev.delay;
iodev.delay = tmp;
upd = true;
}
if (!upd)
return;
for (size_t i = 0; i < iodev.sz_nodes; i++)
if (iodev.nodes[i].type == DEVNODE_KEYBOARD){
struct input_event ev = {
.type = EV_REP,
.code = REP_DELAY,
.value = *delay
};
if (-1 == write(iodev.nodes[i].handle,&ev,sizeof(struct input_event)))
arcan_warning("linux/event: keydelay fail (%s)\n", strerror(errno));
ev.code = REP_PERIOD;
ev.value = *period;
if (-1 == write(iodev.nodes[i].handle,&ev,sizeof(struct input_event)))
arcan_warning("linux/event: keyrepeat fail (%s)\n", strerror(errno));
}
}
static const char* lookup_type(int val)
{
switch(val){
case DEVNODE_GAME:
return "game";
case DEVNODE_MOUSE:
return "mouse";
case DEVNODE_SENSOR:
return "sensor";
case DEVNODE_KEYBOARD:
return "keyboard";
break;
default:
return "unknown";
}
}
#define bit_longn(x) ( (x) / (sizeof(long)*8) )
#define bit_ofs(x) ( (x) % (sizeof(long)*8) )
#define bit_isset(ary, bit) (( ary[bit_longn(bit)] >> bit_ofs(bit)) & 1)
#define bit_count(x) ( ((x) - 1 ) / (sizeof(long) * 8 ) + 1 )
static size_t button_count(int fd, size_t bitn, bool* got_mouse, bool* got_joy)
{
size_t count = 0;
unsigned long bits[ bit_count(KEY_MAX) ];
if (-1 == ioctl(fd, EVIOCGBIT(bitn, KEY_MAX), bits))
return false;
for (size_t i = 0; i < KEY_MAX; i++){
if (bit_isset(bits, i)){
count++;
}
}
*got_mouse = (bit_isset(bits, BTN_MOUSE) || bit_isset(bits, BTN_LEFT) ||
bit_isset(bits, BTN_RIGHT) || bit_isset(bits, BTN_MIDDLE));
*got_joy = (bit_isset(bits, BTN_JOYSTICK) || bit_isset(bits, BTN_GAMEPAD) ||
bit_isset(bits, BTN_WHEEL));
return count;
}
static bool check_mouse_axis(int fd, size_t bitn)
{
unsigned long bits[ bit_count(KEY_MAX) ];
if (-1 == ioctl(fd, EVIOCGBIT(bitn, KEY_MAX), bits))
return false;
/* uncertain if other (REL_Z, REL_RX, REL_RY, REL_RZ, REL_DIAL, REL_MISC)
* should be used as a failing criteria */
return bit_isset(bits, REL_X) && bit_isset(bits, REL_Y);
}
static char* to_utf8(uint16_t utf16, uint8_t out[4])
{
int count = 1, ofs = 0;
uint32_t mask = 0x800;
if (utf16 >= 0x80)
count++;
for(size_t i=0; i < 5; i++){
if ( (uint32_t) utf16 >= mask )
count++;
mask <<= 5;
}
if (count == 1){
out[0] = (char) utf16;
out[1] = 0x00;
}
else {
for (int i = (count-1 > 4 ? 4 : count - 1); i >= 0; i--){
unsigned char ch = ( utf16 >> (6 * i)) & 0x3f;
ch |= 0x80;
if (i == count-1)
ch |= 0xff << (8-count);
out[ofs++] = ch;
}
out[ofs++] = 0x00;
}
return (char*) out;
}
static void map_axes(int fd, size_t bitn, struct devnode* node)
{
unsigned long bits[ bit_count(ABS_MAX) ];
unsigned long rel_bits[ bit_count(REL_MAX) ];
assert(node->type == DEVNODE_GAME);
if (node->game.adata)
return;
node->game.axes = 0;
if (-1 != ioctl(fd, EVIOCGBIT(bitn, ABS_MAX), bits)){
for (size_t i = 0; i < ABS_MAX; i++){
if (bit_isset(bits, i))
node->game.axes++;
}
}
node->game.relofs = node->game.axes;
if (-1 != ioctl(fd, EVIOCGBIT(bitn, REL_MAX), rel_bits)){
for (size_t i = 0; i < REL_MAX; i++){
if (bit_isset(bits, i)){
node->game.axes++;
}
}
}
if (node->game.axes == 0)
return;
node->game.adata = arcan_alloc_mem(
sizeof(struct axis_opts) * node->game.axes,
ARCAN_MEM_BINDING, ARCAN_MEM_BZERO, ARCAN_MEMALIGN_NATURAL
);
size_t ac = 0;
for (size_t i = 0; i < ABS_MAX; i++)
if (bit_isset(bits, i)){
struct input_absinfo ainf;
struct axis_opts* ax = &node->game.adata[ac++];
memset(ax, '\0', sizeof(struct axis_opts));
ax->mode = ax->oldmode = ARCAN_ANALOGFILTER_AVG;
ax->lower = -32768;
ax->upper = 32767;
if (-1 == ioctl(fd, EVIOCGABS(i), &ainf))
continue;
ax->upper = ainf.maximum;
ax->lower = ainf.minimum;
}
}
/*
* setup/register/prepare led- controller handler
*/
static void setup_led(struct devnode* dst, size_t bitn, int fd)
{
unsigned long bits[ bit_count(LED_MAX) ];
if (-1 == ioctl(fd, EVIOCGBIT(bitn, LED_MAX), bits))
return;
size_t count = 0;
for (size_t i = 0; i < LED_MAX; i++){
if (bit_isset(bits, i))
count++;
}
if (!count)
return;
if (pipe(dst->led.fds) == -1)
return;
for (size_t i = 0; i < 2; i++){
int flags = fcntl(dst->led.fds[i], F_GETFL);
if (-1 != flags)
fcntl(dst->led.fds[i], F_SETFL, flags | O_NONBLOCK);
flags = fcntl(dst->led.fds[i], F_GETFD);
if (-1 != flags)
fcntl(dst->led.fds[i], F_SETFD, flags | O_CLOEXEC);
}
char ledname[16];
snprintf(ledname, 16, "%d_led", dst->devnum);
dst->led.ctrlid = arcan_led_register(dst->led.fds[1], dst->devnum, ledname,
(struct led_capabilities){ .nleds = LED_MAX,
.variable_brightness = false, .rgb = false }
);
if (-1 == dst->led.ctrlid){
close(dst->led.fds[0]);
close(dst->led.fds[1]);
dst->led.fds[0] = dst->led.fds[1] = -1;
return;
}
dst->led.gotled = true;
/* reset */
for (size_t i = 0; i < LED_MAX; i++){
if (-1 == write(dst->handle, &(struct input_event){.type = EV_LED,
.code = i, .value = 0}, sizeof(struct input_event)))
arcan_warning("platform/evdev(), error sending reset to led device\n");
}
}
static int alloc_node_slot(const char* path)
{
/* pre-existing? close old node and replace with this one, happens
* when we race and the device appears and reappears and we just need
* to reference a new inode */
int hole = -1;
for (size_t i = 0; i < iodev.sz_nodes; i++){
if (-1 == hole && iodev.nodes[i].handle < 0){
hole = i;
continue;
}
/* or collision with existing? we use file-path for this. index
* stays the same and got_device will still register so don't have
* to consider leak for ledset */
if (iodev.nodes[i].path && strcmp(iodev.nodes[i].path, path) == 0){
close(iodev.nodes[i].handle);
iodev.n_devs--;
return i;
}
}
/* no empty slot, grow pollsets and node tracking */
if (hole == -1){
size_t new_cnt = iodev.sz_nodes + 8;
struct devnode* nn = realloc(
iodev.nodes, sizeof(struct devnode) * new_cnt);
if (!nn)
return -1;
iodev.nodes = nn;
memset(nn + iodev.sz_nodes, '\0', sizeof(struct devnode) * 8);
for (size_t i = iodev.sz_nodes; i < new_cnt; i++){
iodev.nodes[i].handle = BADFD;
iodev.nodes[i].led.fds[0] = iodev.nodes[i].led.fds[1] = BADFD;
}
/* pollset size is actually twice the number of nodes to allow a
* 'mirror address' for a possible led- or other special device ref.
* (say sound...) */
struct pollfd* newset = malloc(2 * sizeof(struct pollfd) * new_cnt);
if (!newset)
return -1;
free(iodev.pollset);
for (size_t i = 0; i < new_cnt; i++){
memset(&newset[i], '\0', sizeof(struct pollfd));
memset(&newset[i+new_cnt], '\0', sizeof(struct pollfd));
newset[i].events = POLLIN | POLLERR | POLLHUP;
newset[i].fd = iodev.nodes[i].handle;
newset[i+new_cnt].events = POLLIN;
newset[i+new_cnt].fd = iodev.nodes[i].led.fds[0];
}
/* update pointers, set hole to the first new entry */
iodev.pollset = newset;
hole = iodev.sz_nodes;
iodev.sz_nodes = new_cnt;
}
return hole;
}
static void got_device(struct arcan_evctx* ctx, int fd, const char* path)
{
struct devnode node = {
.handle = fd,
.led.fds = {BADFD, BADFD}
};
struct stat fdstat;
if (-1 == fstat(fd, &fdstat)){
verbose_print(
"input: couldn't stat node to identify (%s)", strerror(errno));
return;
}
if ((fdstat.st_mode & (S_IFCHR | S_IFBLK)) == 0){
verbose_print(
"input: ignoring %s, not a character or block device", path);
return;
}
if (!identify(fd, path, node.label, sizeof(node.label), &node.devnum)){
verbose_print(
"input: identify failed on %s, ignoring unknown.", path);
close(fd);
return;
}
if (iodev.n_devs >= MAX_DEVICES){
arcan_warning("input: device limit reached, ignoring %s.", path);
close(fd);
}
/* figure out what kind of a device this is from the exposed capabilities,
* heuristic nonsense rather than an interface exposing what the driver should
* know or decide, fantastic.
*
* keyboards typically have longer key masks (and we can check for a few common
* ones) no REL/ABS (don't know if those built-in trackball ones expose as two
* devices or not these days), but also a ton of .. keys
*/
struct evhandler eh = lookup_dev_handler(node.label);
/* [eh] may contain overrides, but we still need to probe the driver state for
* axes etc. and allocate accordingly */
node.type = DEVNODE_GAME;
bool mouse_ax = false;
bool mouse_btn = false;
bool joystick_btn = false;
int add_led = -1;
size_t bpl = sizeof(long) * 8;
size_t nbits = ((EV_MAX)-1) / bpl + 1;
long prop[ nbits ];
if (-1 == ioctl(fd, EVIOCGBIT(0, EV_MAX), &prop)){
verbose_print(
"input: probing %s failed, %s", path, strerror(errno));
close(fd);
return;
}
for (size_t bit = 0; bit < EV_MAX; bit++)
if ( 1ul & (prop[bit/bpl]) >> (bit & (bpl - 1)) )
switch(bit){
case EV_KEY:
node.button_count = button_count(fd, bit, &mouse_btn, &joystick_btn);
break;
case EV_REL:
mouse_ax = check_mouse_axis(fd, bit);
break;
case EV_ABS:
map_axes(fd, bit, &node);
break;
case EV_SW:
break;
/* useless for the time being */
case EV_MSC:
break;
case EV_SYN:
break;
case EV_LED:
add_led = bit;
break;
case EV_SND:
break;
case EV_REP:
break;
case EV_PWR:
break;
case EV_FF:
case EV_FF_STATUS:
break;
}
if (!eh.handler){
if (mouse_ax && mouse_btn){
node.type = DEVNODE_MOUSE;
node.cursor.flt[0].mode = ARCAN_ANALOGFILTER_PASS;
node.cursor.flt[1].mode = ARCAN_ANALOGFILTER_PASS;
if (!iodev.mouseid)
iodev.mouseid = node.devnum;
}
/* not particularly pretty and rather arbitrary */
else if (!mouse_btn && !joystick_btn && node.button_count > 84){
node.type = DEVNODE_KEYBOARD;
node.keyboard.state = 0;
#ifdef HAVE_XKBCOMMON
if (xkb_context){
node.keyboard.xkb_layout = xkb_keymap_new_from_names(
xkb_context, NULL, XKB_KEYMAP_COMPILE_NO_FLAGS);
if (node.keyboard.xkb_layout)
node.keyboard.xkb_state = xkb_state_new(node.keyboard.xkb_layout);
}
#endif
/* FIX: query current LED states and set corresponding states in the devnode */
struct kbd_repeat kbrv = {0};
ioctl(node.handle, KDKBDREP, &kbrv);
}
node.hnd.handler = defhandlers[node.type];
}
else{
node.hnd = eh;
node.type = eh.type;
}
/* finally added */
int hole = alloc_node_slot(path);
if (-1 == hole){
verbose_print(
"input: dropped %s due to errors during scan.", path);
close(fd);
return;
}
iodev.n_devs++;
node.path = strdup(path);
iodev.pollset[hole].fd = fd;
iodev.pollset[hole].events = POLLIN | POLLERR | POLLHUP;
iodev.pollset[hole + iodev.sz_nodes].fd = BADFD;
struct arcan_event addev = {
.category = EVENT_IO,
.io.kind = EVENT_IO_STATUS,
.io.devkind = EVENT_IDEVKIND_STATUS,
.io.devid = node.devnum,
.io.input.status.devkind = node.type,
.io.input.status.action = EVENT_IDEV_ADDED
};
snprintf((char*) &addev.io.label, sizeof(addev.io.label) /
sizeof(addev.io.label[0]), "%s", node.label);
arcan_event_enqueue(ctx, &addev);
/* had to defer led device creation until now because we didn't
* know if there's a slot for it or not, the pollset actually is
* twice the expected size, one for the main device and one for
* the possible led controller */
if (add_led != -1){
setup_led(&node, add_led, fd);
if (node.led.gotled){
iodev.pollset[hole+iodev.sz_nodes].fd = node.led.fds[0];
}
}
iodev.nodes[hole] = node;
verbose_print("input: (%s:%s) added as type: %s",
path, node.label, lookup_type(node.type));
return;
}
#undef bit_isset
#undef bit_ofs
#undef bit_longn
#undef bit_count
void platform_event_rescan_idev(struct arcan_evctx* ctx)
{
char ibuf [strlen(notify_scan_dir) + sizeof("/*")];
glob_t res = {0};
snprintf(ibuf, sizeof(ibuf), "%s/*", notify_scan_dir);
if (glob(ibuf, 0, NULL, &res) == 0){
char** beg = res.gl_pathv;
while(*beg){
int fd = platform_device_open(*beg, O_NONBLOCK | O_RDWR);
if (-1 != fd)
got_device(ctx, fd, *beg);
beg++;
}
globfree(&res);
}
verbose_print("input: couldn't scan %s", notify_scan_dir);
}
static void update_state(int code, bool state, unsigned* statev)
{
int modifier = 0;
switch (klut[code]){
case K_LSHIFT:
modifier = ARKMOD_LSHIFT;
break;
case K_RSHIFT:
modifier = ARKMOD_RSHIFT;
break;
case K_LALT:
modifier = ARKMOD_LALT;
break;
case K_RALT:
modifier = ARKMOD_RALT;
break;
case K_LCTRL:
modifier = ARKMOD_LCTRL;
break;
case K_RCTRL:
modifier = ARKMOD_RCTRL;
break;
case K_LMETA:
modifier = ARKMOD_LMETA;
break;
case K_RMETA:
modifier = ARKMOD_RMETA;
break;
case K_CAPSLOCK:
modifier = ARKMOD_CAPS;
break;
case K_COMPOSE:
modifier = ARKMOD_MODE;
break;
default:
return;
}
if (state)
*statev |= modifier;
else
*statev &= ~modifier;
}
static void defhandler_kbd(struct arcan_evctx* out,
struct devnode* node)
{
struct input_event inev[64];
ssize_t evs = read(node->handle, &inev, sizeof(inev));
if (-1 == evs){
if (errno != EINTR && errno != EAGAIN)
disconnect(out, node);
}
if (evs < 0 || evs < sizeof(struct input_event))
return;
arcan_event newev = {
.category = EVENT_IO,
.io = {
.kind = EVENT_IO_BUTTON,
.devid = node->devnum,
.datatype = EVENT_IDATATYPE_TRANSLATED,
.devkind = EVENT_IDEVKIND_KEYBOARD,
}
};
for (size_t i = 0; i < evs / sizeof(struct input_event); i++){
switch(inev[i].type){
case EV_KEY:
newev.io.input.translated.scancode = inev[i].code;
newev.io.input.translated.keysym = lookup_keycode(inev[i].code);
newev.io.input.translated.modifiers = node->keyboard.state;
update_state(inev[i].code, inev[i].value != 0, &node->keyboard.state);
/* possible checkpoint for adding other keyboard layout support here */
newev.io.subid = inev[i].code;
/* default 'fallback' translation */
uint16_t code = lookup_character(inev[i].code, node->keyboard.state, true);
if (code)
to_utf8(code, newev.io.input.translated.utf8);
/* virtual terminal switching for press on LCTRL+LALT+Fn. should possibly have
* more advanced config here to limit # of eligible devices and change
* combination, and option to disable the thing entirely because it is
* just terrible */
if ((node->keyboard.state == (ARKMOD_LALT | ARKMOD_LCTRL)) &&
inev[i].code >= KEY_F1 && inev[i].code <= KEY_F10 && inev[i].value != 0){
platform_device_release("TTY", inev[i].code - KEY_F1 + 1);
}
/* Feed layout statemachine, try to get a translation out of it. Since we
* don't have support for xkeyboard symbols, we either make due with the normal
* kernel keycode to SDL keysym or go with the keyboard layout and try to
* translate that into SDL. */
#ifdef HAVE_XKBCOMMON
if (node->keyboard.xkb_state){
memset(newev.io.input.translated.utf8, '\0', 5);
if (inev[i].value == 0){
xkb_state_update_key(
node->keyboard.xkb_state, inev[i].code + 8, XKB_KEY_UP);
}
else if (inev[i].value == 1 || (inev[i].value == 2 && xkb_keymap_key_repeats(
node->keyboard.xkb_layout, inev[i].code+8))){
xkb_state_update_key(
node->keyboard.xkb_state, inev[i].code + 8, XKB_KEY_DOWN);
xkb_state_key_get_utf8(node->keyboard.xkb_state,
inev[i].code + 8, (char*) newev.io.input.translated.utf8, 5);
}
}
#endif
/* auto-repeat, may get even if we are not in this state because of broken
* drivers or failed mode-setting. */
if (inev[i].value == 2){
if (iodev.period){
newev.io.input.translated.modifiers |= ARKMOD_REPEAT;
newev.io.input.translated.active = false;
arcan_event_enqueue(out, &newev);
newev.io.input.translated.active = true;
arcan_event_enqueue(out, &newev);
}
}
else{
newev.io.input.translated.active = inev[i].value != 0;
arcan_event_enqueue(out, &newev);
}
break;
default:
break;
}
}
}
static void flush_pending(struct arcan_evctx* ctx,
struct devnode* node)
{
arcan_event newev = {
.category = EVENT_IO,
.io = {
.label = "touch",
.devid = node->devnum,
.subid = node->touch.ind + 128,
.kind = EVENT_IO_TOUCH,
.devkind = EVENT_IDEVKIND_TOUCHDISP,
.datatype = EVENT_IDATATYPE_TOUCH
}
};
newev.io.input.touch.active = node->touch.active;
newev.io.input.touch.x = node->touch.x;
newev.io.input.touch.y = node->touch.y;
newev.io.input.touch.pressure = node->touch.pressure;
newev.io.input.touch.size = node->touch.size;
arcan_event_enqueue(ctx, &newev);
node->touch.pending = false;
node->touch.active = true;
}
static void decode_mt(struct arcan_evctx* ctx,
struct devnode* node, int code, int val)
{
/* there are multiple protocols and mappings for this that we don't
* account for here, move it to a toch event with the basic information
* and let higher layers deal with it */
int newind = -1;
switch(code){
case ABS_X:
if (node->touch.ind != 0 && node->touch.pending)
flush_pending(ctx, node);
node->touch.ind = 0;
node->touch.x = val;
node->touch.pending = true;
break;
case ABS_Y:
if (node->touch.ind != 0 && node->touch.pending)
flush_pending(ctx, node);
node->touch.ind = 0;
node->touch.y = val;
node->touch.pending = true;
break;
case ABS_MT_PRESSURE:
node->touch.pressure = val;
break;
case ABS_MT_POSITION_X:
node->touch.x = val;
node->touch.pending = true;
break;
case ABS_MT_POSITION_Y:
node->touch.y = val;
node->touch.pending = true;
break;
case ABS_DISTANCE:
node->touch.pressure = val;
break;
case ABS_MT_TRACKING_ID:
if (-1 == val){
node->touch.active = false;
node->touch.pending = true;
flush_pending(ctx, node);
}
else
; /* we don't distingush between IDs, only SLOTs */
break;
case ABS_MT_SLOT:
if (node->touch.pending && node->touch.ind != val)
flush_pending(ctx, node);
node->touch.ind = val;
break;
default:
break;
}
}
static void decode_hat(struct arcan_evctx* ctx,
struct devnode* node, int ind, int val)
{
arcan_event newev = {
.category = EVENT_IO,
.io = {
.label = "gamepad",
.kind = EVENT_IO_BUTTON,
.devkind = EVENT_IDEVKIND_GAMEDEV,
.datatype = EVENT_IDATATYPE_DIGITAL
}
};
ind *= 2;
const int base = 64;
newev.io.devid = node->devnum;
/* clamp */
if (val < 0)
val = -1;
else if (val > 0)
val = 1;
else {
/* which of the two possibilities was released? */
newev.io.input.digital.active = false;
if (node->game.hats[ind] != 0){
newev.io.subid = base + ind;
node->game.hats[ind] = 0;
arcan_event_enqueue(ctx, &newev);
}
if (node->game.hats[ind+1] != 0){
newev.io.subid = base + ind + 1;
node->game.hats[ind+1] = 0;
arcan_event_enqueue(ctx, &newev);
}
return;
}
if (val > 0)
ind++;
node->game.hats[ind] = val;
newev.io.input.digital.active = true;
newev.io.subid = base + ind;
arcan_event_enqueue(ctx, &newev);
}
static void defhandler_game(struct arcan_evctx* ctx,
struct devnode* node)
{
struct input_event inev[64];
ssize_t evs = read(node->handle, &inev, sizeof(inev));
if (-1 == evs){
if (errno != EINTR && errno != EAGAIN)
disconnect(ctx, node);
}
if (evs < 0 || evs < sizeof(struct input_event))
return;
arcan_event newev = {
.category = EVENT_IO,
.io = {
.label = "gamepad",
.devkind = EVENT_IDEVKIND_GAMEDEV
}
};
short samplev;
for (size_t i = 0; i < evs / sizeof(struct input_event); i++){
switch(inev[i].type){
case EV_KEY:
if (inev[i].code >= BTN_TOUCH)
inev[i].code -= BTN_TOUCH;
else if (inev[i].code >= BTN_JOYSTICK)
inev[i].code -= BTN_JOYSTICK;
else if (inev[i].code >= BTN_MOUSE)
inev[i].code -= BTN_MOUSE - 1;
if (node->hnd.button_mask && inev[i].code <= 64 &&
( (node->hnd.button_mask >> inev[i].code) & 1) )
continue;
newev.io.kind = EVENT_IO_BUTTON;
newev.io.datatype = EVENT_IDATATYPE_DIGITAL;
newev.io.input.digital.active = inev[i].value;
newev.io.subid = inev[i].code;
newev.io.devid = node->devnum;
arcan_event_enqueue(ctx, &newev);
break;
case EV_SW:
newev.io.kind = EVENT_IO_BUTTON;
newev.io.datatype = EVENT_IDATATYPE_DIGITAL;
newev.io.input.digital.active = inev[i].value;
newev.io.subid = inev[i].code;
newev.io.devid = node->devnum;
arcan_event_enqueue(ctx, &newev);
break;
case EV_REL:
case EV_ABS:
if (node->hnd.axis_mask && inev[i].code <= 64 &&
( (node->hnd.axis_mask >> inev[i].code) & 1) )
continue;
if (inev[i].code >= ABS_HAT0X && inev[i].code <= ABS_HAT3Y){
printf("not a hat\n");
decode_hat(ctx, node, inev[i].code - ABS_HAT0X, inev[i].value);
}
else if (inev[i].code < node->game.axes &&
process_axis(ctx,
&node->game.adata[inev[i].code], inev[i].value, &samplev)){
newev.io.kind = EVENT_IO_AXIS_MOVE;
newev.io.datatype = EVENT_IDATATYPE_ANALOG;
newev.io.input.analog.gotrel = inev[i].type == EV_REL;
newev.io.subid = inev[i].code;
newev.io.devid = node->devnum;
newev.io.input.analog.axisval[0] = samplev;
newev.io.input.analog.nvalues = 2;
arcan_event_enqueue(ctx, &newev);
}
else if ((inev[i].code >= ABS_X && inev[i].code <= ABS_Y) ||
(inev[i].code >= ABS_MT_SLOT && inev[i].code <= ABS_MT_TOOL_Y)){
decode_mt(ctx, node, inev[i].code, inev[i].value);
}
/* though we do reserve axis slots for the relative bits, there is no actual
* filter set to them other than the the mask used above */
else if (inev[i].code == REL_X ||
inev[i].code == REL_Y || inev[i].code == REL_DIAL){
newev.io.kind = EVENT_IO_AXIS_MOVE;
newev.io.datatype = EVENT_IDATATYPE_ANALOG;
newev.io.input.analog.gotrel = true;
newev.io.subid = inev[i].code;
newev.io.devid = node->devnum;
newev.io.input.analog.axisval[0] = inev[i].value;
newev.io.input.analog.nvalues = 1;
arcan_event_enqueue(ctx, &newev);
}
else
;
break;
case EV_SYN:
case EV_REP:
if (node->touch.pending)
flush_pending(ctx, node);
break;
default:
break;
}
}
}
static inline short code_to_mouse(int code)
{
return (code < BTN_MOUSE || code >= BTN_JOYSTICK) ?
-1 : (code - BTN_MOUSE + 1);
}
static void defhandler_mouse(struct arcan_evctx* ctx,
struct devnode* node)
{
struct input_event inev[64];
ssize_t evs = read(node->handle, &inev, sizeof(inev));
if (-1 == evs){
if (errno != EINTR && errno != EAGAIN)
disconnect(ctx, node);
}
if (evs < 0 || evs < sizeof(struct input_event))
return;
arcan_event newev = {
.category = EVENT_IO,
.io = {
.label = "mouse",
.devkind = EVENT_IDEVKIND_MOUSE,
}
};
short samplev;
newev.io.devid = node->devnum;
for (size_t i = 0; i < evs / sizeof(struct input_event); i++){
int vofs = 0;
switch(inev[i].type){
case EV_KEY:
samplev = code_to_mouse(inev[i].code);
if (samplev < 0)
continue;
newev.io.kind = EVENT_IO_BUTTON;
newev.io.datatype = EVENT_IDATATYPE_DIGITAL;
newev.io.input.digital.active = inev[i].value;
newev.io.subid = samplev;
arcan_event_enqueue(ctx, &newev);
break;
case EV_REL:
switch (inev[i].code){
case REL_HWHEEL:
vofs += 2;
case REL_WHEEL:
newev.io.kind = EVENT_IO_BUTTON;
newev.io.datatype = EVENT_IDATATYPE_DIGITAL;
newev.io.input.digital.active = 1;
newev.io.subid = vofs + (inev[i].value > 0 ? 256 : 257);
arcan_event_enqueue(ctx, &newev);
newev.io.input.digital.active = 0;
arcan_event_enqueue(ctx, &newev);
break;
case REL_X:
if (process_axis(ctx, &node->cursor.flt[0], inev[i].value, &samplev)){
samplev = inev[i].value;
node->cursor.mx = ((int)node->cursor.mx + samplev < 0) ?
0 : node->cursor.mx + samplev;
newev.io.kind = EVENT_IO_AXIS_MOVE;
newev.io.datatype = EVENT_IDATATYPE_ANALOG;
newev.io.input.analog.gotrel = true;
newev.io.subid = 0;
newev.io.input.analog.axisval[0] = samplev;
newev.io.input.analog.axisval[1] = node->cursor.mx;
newev.io.input.analog.nvalues = 2;
arcan_event_enqueue(ctx, &newev);
}
break;
case REL_Y:
if (process_axis(ctx, &node->cursor.flt[1], inev[i].value, &samplev)){
node->cursor.my = ((int)node->cursor.my + samplev < 0) ?
0 : node->cursor.my + samplev;
newev.io.kind = EVENT_IO_AXIS_MOVE;
newev.io.datatype = EVENT_IDATATYPE_ANALOG;
newev.io.input.analog.gotrel = true;
newev.io.subid = 1;
newev.io.input.analog.axisval[0] = samplev;
newev.io.input.analog.axisval[1] = node->cursor.my;
newev.io.input.analog.nvalues = 2;
arcan_event_enqueue(ctx, &newev);
}
break;
default:
break;
}
break;
case EV_ABS:
break;
}
}
}
static void defhandler_null(struct arcan_evctx* out,
struct devnode* node)
{
char nbuf[256];
ssize_t evs = read(node->handle, nbuf, sizeof(nbuf));
if (-1 == evs){
if (errno != EINTR && errno != EAGAIN)
disconnect(out, node);
}
}
const char* platform_event_devlabel(int devid)
{
struct devnode* node = lookup_devnode(devid);
if (!node)
return NULL;
return node->label;
}
/*
* note, this do not currently save/restore individual options between
* init/deinit sessions, which is needed for virtual terminal switching
* and external_launch.
*/
void platform_event_reset(struct arcan_evctx* ctx)
{
}
void platform_event_deinit(struct arcan_evctx* ctx)
{
platform_device_release("TTY", -1);
/* note, we purposely leak (let it disappear on close) to avoid the races and
* interactions that come from TTY switching -> deinit -> signal -> init */
if (gstate.notify != -1){
close(gstate.notify);
gstate.notify = -1;
}
/* note, for VT switching this means that the state of devices when it comes
* to filtering etc. do not persist between external launches, should rework
* this */
for (size_t i = 0; i < iodev.n_devs; i++){
if (iodev.nodes[i].handle > 0){
verbose_print("closing %zu:%d", i, iodev.nodes[i].handle);
close(iodev.nodes[i].handle);
memset(&iodev.nodes[i], '\0', sizeof(struct devnode));
iodev.nodes[i].handle = -1;
}
}
iodev.n_devs = 0;
gstate.init = false;
}
void platform_device_lock(int devind, bool state)
{
struct devnode* node = lookup_devnode(devind);
if (!node || !node->handle)
return;
ioctl(node->handle, EVIOCGRAB, state? 1 : 0);
/*
* doesn't make sense outside some window systems, might be useful to propagate
* further to device locking on systems that are less forgiving.
*/
}
enum PLATFORM_EVENT_CAPABILITIES platform_input_capabilities()
{
enum PLATFORM_EVENT_CAPABILITIES rv = 0;
for (size_t i = 0; i < iodev.n_devs; i++){
if (iodev.nodes[i].handle)
switch(iodev.nodes[i].type){
/* don't have better granularity in this step at the moment */
case DEVNODE_SENSOR:
rv |= ACAP_POSITION | ACAP_ORIENTATION;
break;
case DEVNODE_MOUSE:
rv |= ACAP_MOUSE;
break;
case DEVNODE_GAME:
rv |= ACAP_GAMING;
break;
case DEVNODE_KEYBOARD:
rv |= ACAP_TRANSLATED;
break;
case DEVNODE_TOUCH:
rv |= ACAP_TOUCH;
break;
default:
break;
}
}
return rv;
}
const char** platform_input_envopts()
{
return (const char**) envopts;
}
/*
* most of this is done priv-side, it is just the actual tty device to use and
* control that is of importance as the privilege side keeps track of the first
* TTY marked device that was opened as use that for the special 'TTY' name
* later on release requests.
*/
static void find_tty()
{
/* first, check if the env. defines a specific TTY device to use and try that */
const char* newtty = NULL;
int tty = -1;
bool scantty = true;
uintptr_t tag;
cfg_lookup_fun get_config = platform_config_lookup(&tag);
char* ttydev;
if (get_config("event_tty_override", 0, &ttydev, tag) && ttydev){
int fd = platform_device_open(ttydev, O_RDWR);
scantty = false;
if (-1 == fd)
arcan_warning("couldn't open TTYOVERRIDE %s, reason: %s\n",
newtty, strerror(errno));
else
tty = fd;
free(ttydev);
}
/* Failing that, try and find what tty we might be on -- some might redirect
* stdin to something else and then it is not a valid tty to work on. Which,
* of course, brings us back to the special kid in the class, sysfs. */
if (!isatty(tty) && scantty){
FILE* fpek = fopen("/sys/class/tty/tty0/active", "r");
if (fpek){
char line[32] = "/dev/";
if (fgets(line+5, 32-5, fpek)){
char* endl = strrchr(line, '\n');
if (endl)
*endl = '\0';
tty = platform_device_open(line, O_RDWR);
}
fclose(fpek);
}
}
}
void platform_event_preinit()
{
}
void platform_event_init(arcan_evctx* ctx)
{
uintptr_t tag;
cfg_lookup_fun get_config = platform_config_lookup(&tag);
gstate.notify = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
init_keyblut();
#ifdef HAVE_XKBCOMMON
if (!xkb_context && getenv("XKB_DEFAULT_LAYOUT"))
xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
#endif
if (!notify_scan_dir)
notify_scan_dir = strdup(NOTIFY_SCAN_DIR);
find_tty();
char* newsd;
if (get_config("event_scandir", 0, &newsd, tag) && newsd){
free(notify_scan_dir);
notify_scan_dir = newsd;
}
/* chances are the CREATE events are actually racey, but with the
* _device_open refactor this won't really matter as the suid part
* allows us access anyway */
if (-1 == gstate.notify || inotify_add_watch(
gstate.notify, notify_scan_dir, IN_CREATE) == -1){
arcan_warning("inotify initialization failure (%s),"
" device discovery disabled.", strerror(errno));
if (-1 != gstate.notify){
close(gstate.notify);
gstate.notify = -1;
}
}
platform_event_rescan_idev(ctx);
}