aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--README.md76
-rw-r--r--configs/uinput-midi.cfg24
-rw-r--r--evdev.c379
-rw-r--r--evdev.h24
-rw-r--r--makefile5
-rw-r--r--monster.cfg28
-rw-r--r--plugin.c2
7 files changed, 518 insertions, 20 deletions
diff --git a/README.md b/README.md
index c79ac8e..da972ea 100644
--- a/README.md
+++ b/README.md
@@ -143,6 +143,81 @@ NRPNs are not yet fully implemented, though rudimentary support is in the codeba
The channel specification syntax is currently a bit clunky.
+### The `evdev` backend
+
+This backend allows using Linux `evdev` devices such as mouses, keyboards, gamepads and joysticks
+as input and output devices. All buttons and axes available to the Linux system are mappable.
+Output is provided by the `uinput` kernel module, which allows creation of virtual input devices.
+This functionality may require elevated privileges (such as special group membership or root access).
+
+#### Global configuration
+
+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 |
+| `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.
+
+The absolute axis details configuration is required for any absolute axis on output-enabled instances.
+The configuration value contains, space-separated, the following values:
+
+* `value`: The value to assume for the axis until an event is received
+* `minimum`: The axis minimum value
+* `maximum`: The axis maximum value
+* `fuzz`: A value used for filtering the input stream
+* `flat`: An offset, below which all deviations will be ignored
+* `resolution`: Axis resolution in units per millimeter (or units per radian for rotational axes)
+
+For real devices, all of these parameters for every axis can be found by running `evtest` on the device.
+
+#### Channel specification
+
+A channel is specified by its event type and event code, separated by `.`. For a complete list of event types and codes
+see the kernel sources. The most interesting event types are
+
+* `EV_KEY` for keys and buttons
+* `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
+and configuration supported by these devices.
+
+Example mapping:
+```
+ev1.EV_KEY.KEY_A > ev1.EV_ABS.ABS_X
+```
+
+Note that to map an absolute axis on an output-enabled instance, additional information such as the axis minimum
+and maximum are required. These must be specified in the instance configuration. When only mapping the instance
+as a channel input, this is not required.
+
+#### Known bugs/problems
+
+Creating an `evdev` output device requires elevated privileges, namely, write access to the system's
+`/dev/uinput`. Usually, this is granted for users in the `input` group and the `root` user.
+
+Input devices may synchronize logically connected event types (for example, X and Y axes) via `EV_SYN`-type
+events. The MIDIMonster also generates these events after processing channel events, but may not keep the original
+event grouping.
+
+Relative axes (`EV_REL`-type events), such as generated by mouses, are currently handled in a very basic fashion,
+generating only the normalized channel values of `0`, `0.5` and `1` for any input less than, equal to and greater
+than `0`, respectively. As for output, only the values `-1`, `0` and `1` are generated for the same interval.
+
+`EV_KEY` key-down events are sent for normalized channel values over `0.9`.
+
+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
@@ -265,6 +340,7 @@ In order to build the MIDIMonster, you'll need some libraries that provide
support for the protocols to translate.
* libasound2-dev (for the MIDI backend)
+* libevdev-dev (for the evdev backend)
* A C compiler
* GNUmake
diff --git a/configs/uinput-midi.cfg b/configs/uinput-midi.cfg
new file mode 100644
index 0000000..d33e4df
--- /dev/null
+++ b/configs/uinput-midi.cfg
@@ -0,0 +1,24 @@
+; Note that this configuration file was originally written with
+; an older syntax and thus only contains right-to-left mappings
+
+[backend midi]
+name = MIDIMonster
+
+[uinput mouse]
+device = /dev/input/by-id/usb-Logitech_USB-PS_2_Optical_Mouse-event-mouse
+
+; MIDI input devices
+[midi lc1]
+read = Launch
+write = Launch
+
+[map]
+; LC Button
+mouse.1.272 > lc1.note0.0
+mouse.1.273 > lc1.note0.1
+;out.note0.50 > lc1.note0.2
+;out.note0.51 > lc1.note0.3
+;out.note0.52 > lc1.note0.4
+;out.note0.53 > lc1.note0.5
+;out.note0.54 > lc1.note0.6
+;out.note0.55 > lc1.note0.7
diff --git a/evdev.c b/evdev.c
new file mode 100644
index 0000000..c739137
--- /dev/null
+++ b/evdev.c
@@ -0,0 +1,379 @@
+#include <fcntl.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <libevdev/libevdev.h>
+#include <libevdev/libevdev-uinput.h>
+
+#include "midimonster.h"
+#include "evdev.h"
+
+#define BACKEND_NAME "evdev"
+
+typedef union {
+ struct {
+ uint32_t pad;
+ uint16_t type;
+ uint16_t code;
+ } fields;
+ uint64_t label;
+} evdev_channel_ident;
+
+int init(){
+ backend evdev = {
+ .name = BACKEND_NAME,
+ .conf = evdev_configure,
+ .create = evdev_instance,
+ .conf_instance = evdev_configure_instance,
+ .channel = evdev_channel,
+ .handle = evdev_set,
+ .process = evdev_handle,
+ .start = evdev_start,
+ .shutdown = evdev_shutdown
+ };
+
+ if(mm_backend_register(evdev)){
+ fprintf(stderr, "Failed to register evdev backend\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+static int evdev_configure(char* option, char* value) {
+ fprintf(stderr, "The evdev backend does not take any global configuration\n");
+ return 1;
+}
+
+static instance* evdev_instance(){
+ instance* inst = mm_instance();
+ if(!inst){
+ return NULL;
+ }
+
+ evdev_instance_data* data = calloc(1, sizeof(evdev_instance_data));
+ if(!data){
+ fprintf(stderr, "Failed to allocate memory\n");
+ return NULL;
+ }
+
+ data->input_fd = -1;
+ data->output_proto = libevdev_new();
+ if(!data->output_proto){
+ fprintf(stderr, "Failed to initialize libevdev output prototype device\n");
+ free(data);
+ return NULL;
+ }
+
+ inst->impl = data;
+ return inst;
+}
+
+static int evdev_configure_instance(instance* inst, char* option, char* value) {
+ evdev_instance_data* data = (evdev_instance_data*) inst->impl;
+ char* next_token = NULL;
+ struct input_absinfo abs_info = {
+ 0
+ };
+
+ 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));
+ 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;
+ return 1;
+ }
+
+ if(data->exclusive && libevdev_grab(data->input_ev, LIBEVDEV_GRAB)){
+ fprintf(stderr, "Failed to obtain exclusive device access on %s\n", value);
+ }
+ }
+ else if(!strcmp(option, "exclusive")){
+ if(data->input_fd >= 0 && libevdev_grab(data->input_ev, LIBEVDEV_GRAB)){
+ fprintf(stderr, "Failed to obtain exclusive device access on %s\n", inst->name);
+ }
+ data->exclusive = 1;
+ }
+ else if(!strcmp(option, "name")){
+ data->output_enabled = 1;
+ libevdev_set_name(data->output_proto, value);
+ }
+ else if(!strcmp(option, "id")){
+ next_token = value;
+ libevdev_set_id_vendor(data->output_proto, strtol(next_token, &next_token, 0));
+ libevdev_set_id_product(data->output_proto, strtol(next_token, &next_token, 0));
+ libevdev_set_id_version(data->output_proto, strtol(next_token, &next_token, 0));
+ }
+ else if(!strncmp(option, "axis.", 5)){
+ //value minimum maximum fuzz flat resolution
+ next_token = value;
+ abs_info.value = strtol(next_token, &next_token, 0);
+ abs_info.minimum = strtol(next_token, &next_token, 0);
+ abs_info.maximum = strtol(next_token, &next_token, 0);
+ abs_info.fuzz = strtol(next_token, &next_token, 0);
+ abs_info.flat = strtol(next_token, &next_token, 0);
+ abs_info.resolution = strtol(next_token, &next_token, 0);
+ if(libevdev_enable_event_code(data->output_proto, EV_ABS, libevdev_event_code_from_name(EV_ABS, option + 5), &abs_info)){
+ fprintf(stderr, "Failed to enable absolute axis %s for output\n", option + 5);
+ return 1;
+ }
+ }
+ else{
+ fprintf(stderr, "Unknown configuration parameter %s for evdev backend\n", option);
+ return 1;
+ }
+ return 0;
+}
+
+static channel* evdev_channel(instance* inst, char* spec){
+ evdev_instance_data* data = (evdev_instance_data*) inst->impl;
+ char* separator = strchr(spec, '.');
+ evdev_channel_ident ident = {
+ .label = 0
+ };
+
+ if(!separator){
+ fprintf(stderr, "Invalid evdev channel specification %s\n", spec);
+ return NULL;
+ }
+
+ *(separator++) = 0;
+
+ if(libevdev_event_type_from_name(spec) < 0){
+ fprintf(stderr, "Invalid evdev type specification: %s", spec);
+ return NULL;
+ }
+ ident.fields.type = libevdev_event_type_from_name(spec);
+
+ if(libevdev_event_code_from_name(ident.fields.type, separator) >= 0){
+ ident.fields.code = libevdev_event_code_from_name(ident.fields.type, separator);
+ }
+ else{
+ fprintf(stderr, "evdev Code name not recognized, using as number: %s\n", separator);
+ ident.fields.code = strtoul(separator, NULL, 10);
+ }
+
+ if(data->output_enabled){
+ if(!libevdev_has_event_code(data->output_proto, ident.fields.type, ident.fields.code)){
+ //enable the event on the device
+ //the previous check is necessary to not fail while enabling axes, which require additional information
+ if(libevdev_enable_event_code(data->output_proto, ident.fields.type, ident.fields.code, NULL)){
+ fprintf(stderr, "Failed to enable output event %s.%s%s\n",
+ libevdev_event_type_get_name(ident.fields.type),
+ libevdev_event_code_get_name(ident.fields.type, ident.fields.code),
+ (ident.fields.type == EV_ABS) ? ": To output absolute axes, specify their details in the configuration":"");
+ return NULL;
+ }
+ }
+ }
+
+ return mm_channel(inst, ident.label, 1);
+}
+
+static int evdev_push_event(instance* inst, evdev_instance_data* data, struct input_event event){
+ uint64_t range = 0;
+ channel_value val;
+ evdev_channel_ident ident = {
+ .fields.type = event.type,
+ .fields.code = event.code
+ };
+ channel* chan = mm_channel(inst, ident.label, 0);
+
+ if(chan){
+ val.raw.u64 = event.value;
+ switch(event.type){
+ case EV_REL:
+ val.normalised = 0.5 + ((event.value < 0) ? 0.5 : -0.5);
+ break;
+ case EV_ABS:
+ range = libevdev_get_abs_maximum(data->input_ev, event.code) - libevdev_get_abs_minimum(data->input_ev, event.code);
+ val.normalised = (event.value - libevdev_get_abs_minimum(data->input_ev, event.code)) / (double) range;
+ break;
+ case EV_KEY:
+ case EV_SW:
+ default:
+ val.normalised = 1.0 * event.value;
+ break;
+ }
+
+ if(mm_channel_event(chan, val)){
+ fprintf(stderr, "Failed to push evdev channel event to core\n");
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int evdev_handle(size_t num, managed_fd* fds){
+ instance* inst = NULL;
+ evdev_instance_data* data = NULL;
+ size_t fd;
+ unsigned int read_flags = LIBEVDEV_READ_FLAG_NORMAL;
+ int read_status;
+ struct input_event ev;
+
+ if(!num){
+ return 0;
+ }
+
+ for(fd = 0; fd < num; fd++){
+ inst = (instance*) fds[fd].impl;
+ if(!inst){
+ fprintf(stderr, "evdev backend signaled for unknown fd\n");
+ continue;
+ }
+
+ data = (evdev_instance_data*) inst->impl;
+
+ for(read_status = libevdev_next_event(data->input_ev, read_flags, &ev); read_status >= 0; read_status = libevdev_next_event(data->input_ev, read_flags, &ev)){
+ read_flags = LIBEVDEV_READ_FLAG_NORMAL;
+ if(read_status == LIBEVDEV_READ_STATUS_SYNC){
+ read_flags = LIBEVDEV_READ_FLAG_SYNC;
+ }
+
+ //handle event
+ if(evdev_push_event(inst, data, ev)){
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int evdev_start(){
+ size_t n, u, fds = 0;
+ instance** inst = NULL;
+ evdev_instance_data* data = NULL;
+
+ if(mm_backend_instances(BACKEND_NAME, &n, &inst)){
+ fprintf(stderr, "Failed to fetch instance list\n");
+ return 1;
+ }
+
+ if(!n){
+ free(inst);
+ return 0;
+ }
+
+ for(u = 0; u < n; u++){
+ data = (evdev_instance_data*) inst[u]->impl;
+
+ if(data->output_enabled){
+ if(libevdev_uinput_create_from_device(data->output_proto, LIBEVDEV_UINPUT_OPEN_MANAGED, &data->output_ev)){
+ fprintf(stderr, "Failed to create evdev output device: %s\n", strerror(errno));
+ return 1;
+ }
+ fprintf(stderr, "Created device node %s for instance %s\n", libevdev_uinput_get_devnode(data->output_ev), inst[u]->name);
+ }
+
+ inst[u]->ident = data->input_fd;
+ if(data->input_fd >= 0){
+ if(mm_manage_fd(data->input_fd, BACKEND_NAME, 1, inst[u])){
+ fprintf(stderr, "Failed to register event input descriptor for instance %s\n", inst[u]->name);
+ free(inst);
+ return 1;
+ }
+ fds++;
+ }
+
+ }
+
+ fprintf(stderr, "evdev backend registered %zu descriptors to core\n", fds);
+ free(inst);
+ return 0;
+}
+
+static int evdev_set(instance* inst, size_t num, channel** c, channel_value* v) {
+ size_t evt = 0;
+ evdev_instance_data* data = (evdev_instance_data*) inst->impl;
+ evdev_channel_ident ident = {
+ .label = 0
+ };
+ int32_t value = 0;
+ uint64_t range = 0;
+
+ if(!num){
+ return 0;
+ }
+
+ if(!data->output_enabled){
+ fprintf(stderr, "Instance %s not enabled for output\n", inst->name);
+ return 0;
+ }
+
+ for(evt = 0; evt < num; evt++){
+ ident.label = c[evt]->ident;
+
+ switch(ident.fields.type){
+ case EV_REL:
+ value = (v[evt].normalised < 0.5) ? -1 : ((v[evt].normalised > 0.5) ? 1 : 0);
+ break;
+ case EV_ABS:
+ range = libevdev_get_abs_maximum(data->output_proto, ident.fields.code) - libevdev_get_abs_minimum(data->output_proto, ident.fields.code);
+ value = (range * v[evt].normalised) + libevdev_get_abs_minimum(data->output_proto, ident.fields.code);
+ break;
+ case EV_KEY:
+ case EV_SW:
+ default:
+ value = (v[evt].normalised > 0.9) ? 1 : 0;
+ break;
+ }
+
+ if(libevdev_uinput_write_event(data->output_ev, ident.fields.type, ident.fields.code, value)){
+ fprintf(stderr, "Failed to output event on instance %s\n", inst->name);
+ return 1;
+ }
+ }
+
+ //send syn event to publish all events
+ if(libevdev_uinput_write_event(data->output_ev, EV_SYN, SYN_REPORT, 0)){
+ fprintf(stderr, "Failed to output sync event on instance %s\n", inst->name);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int evdev_shutdown(){
+ evdev_instance_data* data = NULL;
+ instance** instances = NULL;
+ size_t n, u;
+
+ if(mm_backend_instances(BACKEND_NAME, &n, &instances)){
+ fprintf(stderr, "Failed to fetch instance list\n");
+ return 1;
+ }
+
+ for(u = 0; u < n; u++){
+ data = (evdev_instance_data*) instances[u]->impl;
+
+ if(data->input_fd >= 0){
+ libevdev_free(data->input_ev);
+ close(data->input_fd);
+ }
+
+ if(data->output_enabled){
+ libevdev_uinput_destroy(data->output_ev);
+ }
+
+ libevdev_free(data->output_proto);
+ }
+
+ free(instances);
+ return 0;
+}
diff --git a/evdev.h b/evdev.h
new file mode 100644
index 0000000..dccc641
--- /dev/null
+++ b/evdev.h
@@ -0,0 +1,24 @@
+#include <sys/types.h>
+#include <linux/input.h>
+
+#include "midimonster.h"
+
+int init();
+static int evdev_configure(char* option, char* value);
+static int evdev_configure_instance(instance* instance, char* option, char* value);
+static instance* evdev_instance();
+static channel* evdev_channel(instance* instance, char* spec);
+static int evdev_set(instance* inst, size_t num, channel** c, channel_value* v);
+static int evdev_handle(size_t num, managed_fd* fds);
+static int evdev_start();
+static int evdev_shutdown();
+
+typedef struct /*_evdev_instance_model*/ {
+ int input_fd;
+ struct libevdev* input_ev;
+ int exclusive;
+
+ int output_enabled;
+ struct libevdev* output_proto;
+ struct libevdev_uinput* output_ev;
+} evdev_instance_data;
diff --git a/makefile b/makefile
index f87005e..3ab55a1 100644
--- a/makefile
+++ b/makefile
@@ -1,5 +1,5 @@
.PHONY: clean
-BACKENDS = artnet.so midi.so osc.so loopback.so
+BACKENDS = artnet.so midi.so osc.so loopback.so evdev.so
OBJS = config.o backend.o plugin.o
PLUGINDIR = "\"./\""
@@ -11,6 +11,9 @@ CFLAGS ?= -g -Wall
midimonster: LDLIBS = -ldl
midimonster: CFLAGS += -rdynamic -DPLUGINS=$(PLUGINDIR)
midi.so: LDLIBS = -lasound
+evdev.so: CFLAGS += $(shell pkg-config --cflags libevdev)
+evdev.so: LDLIBS = $(shell pkg-config --libs libevdev)
+
%.so :: %.c %.h
$(CC) $(CFLAGS) $(LDLIBS) $< -o $@ $(LDFLAGS)
diff --git a/monster.cfg b/monster.cfg
index 558478e..33bedeb 100644
--- a/monster.cfg
+++ b/monster.cfg
@@ -1,25 +1,17 @@
[backend midi]
name = MIDIMonster
-[backend artnet]
-bind = *
-net = 0
+[evdev test]
+input = /dev/input/event14
+name = barfoo
+axis.ABS_X = 34300 0 65535 255 4095
+axis.ABS_RZ = 34300 0 65535 255 4095
+axis.ABS_Y = 34300 0 65535 255 4095
-[artnet net1]
-iface = 0
-uni = 0
-dest = 255.255.255.255
+[midi foo]
-[osc osc1]
-bind = * 8000
-dest = learn@8001
-/1/fader1 = f 0.0 1.0
-/1/fader2 = f 0.0 1.0
-/1/fader3 = f 0.0 1.0
-/1/fader4 = f 0.0 1.0
-/1/xy = ff 0.0 1.0 0.0 1.0
[map]
-osc1./1/xy:0 <> net1.1+2
-osc1./1/xy:1 <> net1.3+4
-osc1./1/fader1 <> net1.20+21
+test.EV_KEY.BTN_SOUTH > test.EV_KEY.KEY_A
+test.EV_ABS.ABS_RZ > test.EV_ABS.ABS_Y
+test.EV_ABS.ABS_X > test.EV_KEY.KEY_B
diff --git a/plugin.c b/plugin.c
index d2fe95c..1f552f0 100644
--- a/plugin.c
+++ b/plugin.c
@@ -42,12 +42,12 @@ static int plugin_attach(char* path, char* file){
else{
return 0;
}
+ free(lib);
plugin_handle = realloc(plugin_handle, (plugins + 1) * sizeof(void*));
if(!plugin_handle){
fprintf(stderr, "Failed to allocate memory\n");
dlclose(handle);
- free(lib);
return 1;
}