event-sequence-transformation/nodes/evdev.c

152 lines
4.0 KiB
C

#include <libevdev/libevdev.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include "evdev.h"
#include "../processing.h"
#include "../module_registry.h"
typedef struct {
GraphNode as_GraphNode;
IOHandling subscription;
struct libevdev *dev;
int fd;
int namespace;
} EvdevGraphNode;
static void
handle_io(EventPositionBase * self, int fd, bool is_output)
{
(void) is_output;
(void) fd;
EvdevGraphNode * node = DOWNCAST(EvdevGraphNode, GraphNode, DOWNCAST(GraphNode, EventPositionBase, self));
int err = 0;
AbsoluteTime realtime;
clock_gettime(CLOCK_REALTIME, &realtime.absolute);
AbsoluteTime monotime = get_current_time();
RelativeTime realtime_adj = absolute_time_sub_absolute(realtime, monotime);
while (!err) {
struct input_event buf;
err = libevdev_next_event(node->dev, LIBEVDEV_READ_FLAG_NORMAL, &buf);
if (err) {
if (err == -EAGAIN || err == 1) {
break;
}
node->subscription.enabled = false;
if (err < 0) {
errno = -err;
perror("Failed to read evdev event");
}
break;
}
realtime.absolute.tv_sec = buf.time.tv_sec;
realtime.absolute.tv_nsec = buf.time.tv_usec * (long) 1000;
monotime = absolute_time_sub_relative(realtime, realtime_adj);
EventData data = {
.code = {
.ns = node->namespace,
.major = buf.type,
.minor = buf.code,
},
.ttl = 100,
.priority = 10,
.payload = buf.value,
.modifiers = EMPTY_MODIFIER_SET,
.time = monotime,
};
for (size_t i = 0; i < node->as_GraphNode.outputs.length; ++i) {
EventNode *ev = event_create(&data);
if (!ev) {
perror("Failed to create event");
break;
}
ev->position = &node->as_GraphNode.outputs.elements[i]->as_EventPositionBase;
}
}
}
static GraphNode *
create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEnvironment * env)
{
EvdevGraphNode * node = T_ALLOC(1, EvdevGraphNode);
if (!node) {
return NULL;
}
const char *filename = NULL;
if (config) {
node->namespace = env_resolve_constant(env, config_setting_get_member(config->options, "namespace"));
config_setting_lookup_string(config->options, "file", &filename);
}
if (filename == NULL) {
free(node);
return NULL;
}
int fd = open(filename, O_RDONLY | O_NONBLOCK);
int err;
if ((err = libevdev_new_from_fd(fd, &node->dev)) < 0) {
errno = -err;
free(node);
return NULL;
}
*node = (EvdevGraphNode) {
.as_GraphNode = {
.as_EventPositionBase = {
.handle_event = NULL,
.waiting_new_event = false,
},
.specification = spec,
.inputs = EMPTY_GRAPH_CHANNEL_LIST,
.outputs = EMPTY_GRAPH_CHANNEL_LIST,
},
.subscription = {
.self = &node->as_GraphNode.as_EventPositionBase,
.handle_io = handle_io,
.enabled = true,
},
.dev = node->dev,
.fd = fd,
.namespace = node->namespace,
};
return &node->as_GraphNode;
}
static void destroy
(GraphNodeSpecification * self, GraphNode * target)
{
(void) self;
EvdevGraphNode * node = DOWNCAST(EvdevGraphNode, GraphNode, target);
if (node->dev) {
libevdev_free(node->dev);
node->dev = NULL;
close(node->fd);
}
free(target);
}
static void
register_io(GraphNodeSpecification * self, GraphNode * target, ProcessingState * state)
{
(void) self;
EvdevGraphNode * node = DOWNCAST(EvdevGraphNode, GraphNode, target);
io_subscription_list_add(&state->wait_input, node->fd, &node->subscription);
}
GraphNodeSpecification nodespec_evdev = (GraphNodeSpecification) {
.create = &create,
.destroy = &destroy,
.register_io = &register_io,
.name = "evdev",
.documentation = "Reads evdev events of the specified device\nDoes not accept events\nSends events on all connectors with major code, minor code, payload respectively set to evdev event type, code, value"
"\nOption 'namespace' (optional): set namespace for the generated events"
"\nOption 'file' (required): device file to read events from (like '/dev/input/eventN'), the process must have sufficient privileges to read the file"
,
};
MODULE_CONSTRUCTOR(init)
{
register_graph_node_specification(&nodespec_evdev);
}