From 8013252035a63b6133cb6dc5b0c64d97bf39b96d Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 9 Mar 2018 16:25:38 +0100 Subject: Implement evdev device matching by name --- README.md | 16 ++++---- backends/evdev.c | 110 +++++++++++++++++++++++++++++++++++++++++++---------- backends/evdev.h | 6 +++ configs/evdev.conf | 2 +- monster.cfg | 8 +++- 5 files changed, 110 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 1f1979d..45f2267 100644 --- a/README.md +++ b/README.md @@ -259,11 +259,12 @@ This backend does not take any global configuration. #### Instance configuration -| Option | Example value | Default value | Description | -|---------------|-----------------------|---------------|-----------------------------------------------| -| `input` | `/dev/input/event1` | none | `evdev` device to use as input device | -| `exclusive` | `1` | `0` | Prevent other processes from using the device | -| `name` | `My Input Device` | none | Output device presentation name. Setting this option enables the instance for output | +| Option | Example value | Default value | Description | +|---------------|-----------------------|---------------|-------------------------------------------------------| +| `device` | `/dev/input/event1` | none | `evdev` device to use as input device | +| `input` | `Xbox Wireless` | none | Presentation name of evdev device to use as input (prefix-matched) | +| `output` | `My Input Device` | none | Output device presentation name. Setting this option enables the instance for output | +| `exclusive` | `1` | `0` | Prevent other processes from using the device | | `id` | `0x1 0x2 0x3` | none | Set output device bus identification (Vendor, Product and Version), optional | | `axis.AXISNAME`| `34300 0 65536 255 4095` | none | Specify absolute axis details (see below) for output. This is required for any absolute axis to be output. @@ -288,7 +289,7 @@ see the [kernel documentation](https://www.kernel.org/doc/html/v4.12/input/event * `EV_ABS` for absolute axes (such as Joysticks) * `EV_REL` for relative axes (such as Mouses) -The `evtest` tool is useful to gather information on devices active on the local system, including types, codes +The `evtest` tool is useful to gather information on devices active on the local system, including names, types, codes and configuration supported by these devices. Example mapping: @@ -318,9 +319,6 @@ than `0`, respectively. As for output, only the values `-1`, `0` and `1` are gen Extended event type values such as `EV_LED`, `EV_SND`, etc are recognized in the MIDIMonster configuration file but may or may not work with the internal channel mapping and normalization code. -Input devices can currently only be specified by device node directly. There may be a facility to open input -devices by presentation name in the future. - ### The `loopback` backend This backend allows the user to create logical mapping channels, for example to exchange triggering diff --git a/backends/evdev.c b/backends/evdev.c index ac63850..e26eb76 100644 --- a/backends/evdev.c +++ b/backends/evdev.c @@ -5,6 +5,10 @@ #include #include #include +#include +#include +#include +#include #ifndef EVDEV_NO_UINPUT #include #endif @@ -75,37 +79,103 @@ static instance* evdev_instance(){ return inst; } +static int evdev_attach(instance* inst, evdev_instance_data* data, char* node){ + if(data->input_fd >= 0){ + fprintf(stderr, "Instance %s already was assigned an input device\n", inst->name); + return 1; + } + + data->input_fd = open(node, O_RDONLY | O_NONBLOCK); + if(data->input_fd < 0){ + fprintf(stderr, "Failed to open evdev input device node %s: %s\n", node, strerror(errno)); + return 1; + } + + if(libevdev_new_from_fd(data->input_fd, &data->input_ev)){ + fprintf(stderr, "Failed to initialize libevdev for %s\n", node); + close(data->input_fd); + data->input_fd = -1; + return 1; + } + + if(data->exclusive && libevdev_grab(data->input_ev, LIBEVDEV_GRAB)){ + fprintf(stderr, "Failed to obtain exclusive device access on %s\n", node); + } + + return 0; +} + +static char* evdev_find(char* name){ + int fd = -1; + struct dirent* file = NULL; + char file_path[PATH_MAX * 2]; + DIR* nodes = opendir(INPUT_NODES); + char device_name[UINPUT_MAX_NAME_SIZE], *result = NULL; + + if(!nodes){ + fprintf(stderr, "Failed to query input device nodes in %s: %s", INPUT_NODES, strerror(errno)); + return NULL; + } + + for(file = readdir(nodes); file; file = readdir(nodes)){ + if(!strncmp(file->d_name, INPUT_PREFIX, strlen(INPUT_PREFIX)) && file->d_type == DT_CHR){ + snprintf(file_path, sizeof(file_path), "%s/%s", INPUT_NODES, file->d_name); + + fd = open(file_path, O_RDONLY); + if(fd < 0){ + fprintf(stderr, "Failed to access %s: %s\n", file_path, strerror(errno)); + continue; + } + + if(ioctl(fd, EVIOCGNAME(sizeof(device_name)), device_name) < 0){ + fprintf(stderr, "Failed to read name for %s: %s\n", file_path, strerror(errno)); + close(fd); + continue; + } + + close(fd); + + if(!strncmp(device_name, name, strlen(name))){ + fprintf(stderr, "Matched name %s for %s: %s\n", device_name, name, file_path); + break; + } + } + } + + if(file){ + result = calloc(strlen(file_path) + 1, sizeof(char)); + if(result){ + strncpy(result, file_path, strlen(file_path)); + } + } + + closedir(nodes); + return result; +} + static int evdev_configure_instance(instance* inst, char* option, char* value) { evdev_instance_data* data = (evdev_instance_data*) inst->impl; -#ifndef EVDEV_NO_UINPUT char* next_token = NULL; +#ifndef EVDEV_NO_UINPUT struct input_absinfo abs_info = { 0 }; #endif - if(!strcmp(option, "input")){ - if(data->input_fd >= 0){ - fprintf(stderr, "Instance %s already was assigned an input device\n", inst->name); - return 1; - } - - data->input_fd = open(value, O_RDONLY | O_NONBLOCK); - if(data->input_fd < 0){ - fprintf(stderr, "Failed to open evdev input device node %s: %s\n", value, strerror(errno)); + if(!strcmp(option, "device")){ + return evdev_attach(inst, data, value); + } + else if(!strcmp(option, "input")){ + next_token = evdev_find(value); + if(!next_token){ + fprintf(stderr, "Failed to find evdev input device with name %s for instance %s\n", value, inst->name); return 1; } - - if(libevdev_new_from_fd(data->input_fd, &data->input_ev)){ - fprintf(stderr, "Failed to initialize libevdev for %s\n", value); - close(data->input_fd); - data->input_fd = -1; + if(evdev_attach(inst, data, next_token)){ + free(next_token); return 1; } - - if(data->exclusive && libevdev_grab(data->input_ev, LIBEVDEV_GRAB)){ - fprintf(stderr, "Failed to obtain exclusive device access on %s\n", value); - } + free(next_token); } else if(!strcmp(option, "exclusive")){ if(data->input_fd >= 0 && libevdev_grab(data->input_ev, LIBEVDEV_GRAB)){ @@ -114,7 +184,7 @@ static int evdev_configure_instance(instance* inst, char* option, char* value) { data->exclusive = 1; } #ifndef EVDEV_NO_UINPUT - else if(!strcmp(option, "name")){ + else if(!strcmp(option, "output")){ data->output_enabled = 1; libevdev_set_name(data->output_proto, value); } diff --git a/backends/evdev.h b/backends/evdev.h index 89a08ae..c6e3a25 100644 --- a/backends/evdev.h +++ b/backends/evdev.h @@ -18,6 +18,12 @@ static int evdev_handle(size_t num, managed_fd* fds); static int evdev_start(); static int evdev_shutdown(); +#define INPUT_NODES "/dev/input" +#define INPUT_PREFIX "event" +#ifndef UINPUT_MAX_NAME_SIZE + #define UINPUT_MAX_NAME_SIZE 512 +#endif + typedef struct /*_evdev_instance_model*/ { int input_fd; struct libevdev* input_ev; diff --git a/configs/evdev.conf b/configs/evdev.conf index 3a0fa4f..386e154 100644 --- a/configs/evdev.conf +++ b/configs/evdev.conf @@ -3,7 +3,7 @@ bind = * 6454 net = 0 [evdev xbox] -input = /dev/input/event14 +device = /dev/input/event14 axis.ABS_X = 34300 0 65535 255 4095 axis.ABS_RZ = 34300 0 65535 255 4095 axis.ABS_Y = 34300 0 65535 255 4095 diff --git a/monster.cfg b/monster.cfg index 8412e8c..34acbce 100644 --- a/monster.cfg +++ b/monster.cfg @@ -10,10 +10,10 @@ bind = 0.0.0.0 [artnet art] universe = 1 -dest = 10.2.2.255 +dest = 129.13.215.0 [evdev in] -input = /dev/input/event14 +input = Xbox Wireless Controller [midi midi] @@ -28,3 +28,7 @@ in.EV_ABS.ABS_X > sacn.1+2 in.EV_ABS.ABS_Y > sacn.3 in.EV_ABS.ABS_X > art.1+2 in.EV_ABS.ABS_Y > art.3 +in.EV_KEY.BTN_THUMBL > sacn.4 +in.EV_KEY.BTN_THUMBR > sacn.5 +in.EV_ABS.ABS_GAS > sacn.6+7 +in.EV_ABS.ABS_BRAKE > sacn.8 -- cgit v1.2.3