diff options
| author | cbdev <cb@cbcdn.com> | 2018-03-02 03:20:11 +0100 | 
|---|---|---|
| committer | cbdev <cb@cbcdn.com> | 2018-03-02 03:20:11 +0100 | 
| commit | 2dfc564edc0c89c4a8de7e384806aae5d593426d (patch) | |
| tree | b1636ec14d3f35ed88b3f079e0c3d168f77b17b9 /backends/evdev.c | |
| parent | be5df1c4e639ca6a7cd70a3122039a1de4588e28 (diff) | |
| download | midimonster-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.c | 401 | 
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; +} | 
