aboutsummaryrefslogtreecommitdiffhomepage
path: root/backends/evdev.c
diff options
context:
space:
mode:
authorcbdev <cb@cbcdn.com>2018-03-02 03:20:11 +0100
committercbdev <cb@cbcdn.com>2018-03-02 03:20:11 +0100
commit2dfc564edc0c89c4a8de7e384806aae5d593426d (patch)
treeb1636ec14d3f35ed88b3f079e0c3d168f77b17b9 /backends/evdev.c
parentbe5df1c4e639ca6a7cd70a3122039a1de4588e28 (diff)
downloadmidimonster-2dfc564edc0c89c4a8de7e384806aae5d593426d.tar.gz
midimonster-2dfc564edc0c89c4a8de7e384806aae5d593426d.tar.bz2
midimonster-2dfc564edc0c89c4a8de7e384806aae5d593426d.zip
Move backend implementations to subdirectory
Diffstat (limited to 'backends/evdev.c')
-rw-r--r--backends/evdev.c401
1 files changed, 401 insertions, 0 deletions
diff --git a/backends/evdev.c b/backends/evdev.c
new file mode 100644
index 0000000..ac63850
--- /dev/null
+++ b/backends/evdev.c
@@ -0,0 +1,401 @@
+#include <fcntl.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <libevdev/libevdev.h>
+#ifndef EVDEV_NO_UINPUT
+#include <libevdev/libevdev-uinput.h>
+#endif
+
+#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;
+#ifndef EVDEV_NO_UINPUT
+ data->output_proto = libevdev_new();
+ if(!data->output_proto){
+ fprintf(stderr, "Failed to initialize libevdev output prototype device\n");
+ free(data);
+ return NULL;
+ }
+#endif
+
+ 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;
+#ifndef EVDEV_NO_UINPUT
+ char* next_token = NULL;
+ 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));
+ 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;
+ }
+#ifndef EVDEV_NO_UINPUT
+ 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;
+ }
+ }
+#endif
+ else{
+ fprintf(stderr, "Unknown configuration parameter %s for evdev backend\n", option);
+ return 1;
+ }
+ return 0;
+}
+
+static channel* evdev_channel(instance* inst, char* spec){
+#ifndef EVDEV_NO_UINPUT
+ evdev_instance_data* data = (evdev_instance_data*) inst->impl;
+#endif
+ 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);
+ }
+
+#ifndef EVDEV_NO_UINPUT
+ 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;
+ }
+ }
+ }
+#endif
+
+ 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;
+
+#ifndef EVDEV_NO_UINPUT
+ 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);
+ }
+#endif
+
+ 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) {
+#ifndef EVDEV_NO_UINPUT
+ 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;
+#else
+ fprintf(stderr, "The evdev backend does not support output on this platform\n");
+ return 1;
+#endif
+}
+
+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);
+ }
+
+#ifndef EVDEV_NO_UINPUT
+ if(data->output_enabled){
+ libevdev_uinput_destroy(data->output_ev);
+ }
+
+ libevdev_free(data->output_proto);
+#endif
+ free(data);
+ }
+
+ free(instances);
+ return 0;
+}