163 lines
4.5 KiB
C
163 lines
4.5 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;
|
|
bool should_grab = false;
|
|
if (config->options) {
|
|
node->namespace = env_resolve_constant(env, config_setting_get_member(config->options, "namespace"));
|
|
should_grab = env_resolve_constant(env, config_setting_get_member(config->options, "grab")) != 0;
|
|
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;
|
|
}
|
|
if (should_grab) {
|
|
if ((err = libevdev_grab(node->dev, LIBEVDEV_GRAB)) < 0) {
|
|
errno = -err;
|
|
libevdev_free(node->dev);
|
|
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 = ®ister_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"
|
|
"\nOption 'grab' (optional): whether to prevent others from receiving events from this device"
|
|
,
|
|
};
|
|
|
|
MODULE_CONSTRUCTOR(init)
|
|
{
|
|
register_graph_node_specification(&nodespec_evdev);
|
|
}
|