Compare commits

..

16 Commits

25 changed files with 1072 additions and 35 deletions

View File

@ -12,7 +12,7 @@ CPPFLAGS += $(shell pkg-config --cflags $(DEPS))
LDLIBS += $(shell pkg-config --libs $(DEPS)) LDLIBS += $(shell pkg-config --libs $(DEPS))
INTERP ?= INTERP ?=
MAIN = main MAIN = main
OBJS = main.o events.o processing.o graph.o config.o event_code_names.o hash_table.o module_registry.o event_predicate.o nodes/getchar.o nodes/print.o nodes/evdev.o nodes/tee.o nodes/router.o nodes/modifiers.o nodes/modify_predicate.o nodes/uinput.o nodes/assign.o OBJS = main.o events.o processing.o graph.o config.o event_code_names.o hash_table.o queue.o module_registry.o event_predicate.o nodes/getchar.o nodes/print.o nodes/evdev.o nodes/tee.o nodes/router.o nodes/modifiers.o nodes/modify_predicate.o nodes/uinput.o nodes/assign.o nodes/differentiate.o nodes/scale.o nodes/window.o
all: $(MAIN) all: $(MAIN)

5
defs.h
View File

@ -12,8 +12,9 @@
// So for structs it is usually actual downcast, but for unions it is an upcast // So for structs it is usually actual downcast, but for unions it is an upcast
#define DOWNCAST(contype, basename, ptr) containerof(ptr, contype, as_##basename) #define DOWNCAST(contype, basename, ptr) containerof(ptr, contype, as_##basename)
// Expects ptr to be of type srctype* or void*, returns (dsttype*)ptr // Expects ptr to be of type srctype* or void*, returns (dsttype*)ptr
#define IMPLICIT_CAST(dsttype, srctype, ptr) (((union { typeof(srctype) *src; typeof(dsttype) *dst; }){.src = ptr}).dst) #define IMPLICIT_CAST(dsttype, srctype, ptr) (((union { typeof(srctype) *src; typeof(dsttype) *dst; }){.src = (ptr)}).dst)
#define T_ALLOC(count, T) ((T*)calloc(count, sizeof(T))) #define T_ALLOC(count, T) ((T*)calloc((count), sizeof(T)))
#define T_REALLOC(ptr, count, T) ((T*)reallocarray(IMPLICIT_CAST(void, T, ptr), (count), sizeof(T)))
#define MODULE_CONSTRUCTOR(name) __attribute__((constructor)) static void name(void) #define MODULE_CONSTRUCTOR(name) __attribute__((constructor)) static void name(void)

View File

@ -21,7 +21,7 @@ event_predicate_list_extend(EventPredicateList * lst)
EventPredicate *new_values; EventPredicate *new_values;
if (lst->values) { if (lst->values) {
new_values = reallocarray(lst->values, capacity, sizeof(EventPredicate)); new_values = T_REALLOC(lst->values, capacity, EventPredicate);
} else { } else {
new_values = T_ALLOC(capacity, EventPredicate); new_values = T_ALLOC(capacity, EventPredicate);
} }

View File

@ -35,6 +35,9 @@ EventNode *
event_create(const EventData * content) event_create(const EventData * content)
{ {
EventNode * event = T_ALLOC(1, EventNode); EventNode * event = T_ALLOC(1, EventNode);
if (!event) {
return NULL;
}
if (content) { if (content) {
event->data = *content; event->data = *content;
event->data.modifiers = modifier_set_copy(content->modifiers); event->data.modifiers = modifier_set_copy(content->modifiers);

View File

@ -5,7 +5,7 @@ static void
graph_channel_list_resize(GraphChannelList * lst, size_t target) graph_channel_list_resize(GraphChannelList * lst, size_t target)
{ {
if (target > lst->length) { if (target > lst->length) {
GraphChannel **new_elements = reallocarray(lst->elements, target, sizeof(GraphChannel*)); GraphChannel **new_elements = T_REALLOC(lst->elements, target, GraphChannel*);
if (!new_elements) { if (!new_elements) {
return; return;
} }
@ -49,7 +49,7 @@ channel_handle_event(EventPositionBase * self, EventNode * event)
event->position = &target->as_EventPositionBase; event->position = &target->as_EventPositionBase;
event->input_index = ch->idx_end; event->input_index = ch->idx_end;
target->as_EventPositionBase.waiting_new_event = false; target->as_EventPositionBase.waiting_new_event = false;
return false; // Continue processing events return true; // Changes were made
} }
void void

View File

@ -325,3 +325,36 @@ hash_table_find_impl(const HashTableDynamicData * ht, const HashTableKey key)
return -1; return -1;
} }
bool
hash_table_delete_at_index_impl(HashTableDynamicData * ht, const HashTableIndex index)
{
if (!ht) {
return false;
}
if (index < 0 || (size_t) index > ht->capacity) {
return false;
}
HashTableKeyEntry *key_entry_ptr = ht->key_array + index;
void *value_ptr = ht->value_array + (ht->value_size * index);
if (!key_entry_ptr->key.bytes) {
// No entry at index
return false;
}
// TODO find base_index and reduce its max_collision_offset when provably justified
hash_table_key_deinit_copied(&key_entry_ptr->key);
ht->length -= 1;
void (*value_deinit)(void*) = ht->value_deinit;
if (value_deinit) {
value_deinit(value_ptr);
}
memset(value_ptr, 0, ht->value_size);
return true;
}

View File

@ -53,10 +53,19 @@ void hash_table_init_impl(HashTableDynamicData * dyndata, size_t value_size, voi
void hash_table_deinit_impl(HashTableDynamicData * dyndata); void hash_table_deinit_impl(HashTableDynamicData * dyndata);
HashTableIndex hash_table_insert_impl(HashTableDynamicData * dyndata, const HashTableKey key, const void * value_ptr); HashTableIndex hash_table_insert_impl(HashTableDynamicData * dyndata, const HashTableKey key, const void * value_ptr);
HashTableIndex hash_table_find_impl(const HashTableDynamicData * dyndata, const HashTableKey key); HashTableIndex hash_table_find_impl(const HashTableDynamicData * dyndata, const HashTableKey key);
bool hash_table_delete_at_index_impl(HashTableDynamicData * dyndata, const HashTableIndex index);
__attribute__((unused)) inline static bool
hash_table_delete_by_key_impl(HashTableDynamicData * dyndata, const HashTableKey key)
{
return hash_table_delete_at_index_impl(dyndata, hash_table_find_impl(dyndata, key));
}
#define hash_table_init(ht, value_deinit) hash_table_init_impl(&(ht)->as_HashTableDynamicData, sizeof(*(ht)->value_array), IMPLICIT_CAST(void(void*), void(typeof((ht)->value_array)), value_deinit)) #define hash_table_init(ht, value_deinit) hash_table_init_impl(&(ht)->as_HashTableDynamicData, sizeof(*(ht)->value_array), IMPLICIT_CAST(void(void*), void(typeof((ht)->value_array)), value_deinit))
#define hash_table_deinit(ht) hash_table_deinit_impl(&(ht)->as_HashTableDynamicData) #define hash_table_deinit(ht) hash_table_deinit_impl(&(ht)->as_HashTableDynamicData)
#define hash_table_insert(ht, key, value_ptr) hash_table_insert_impl(&(ht)->as_HashTableDynamicData, key, IMPLICIT_CAST(const void, const typeof(*(ht)->value_array), value_ptr)) #define hash_table_insert(ht, key, value_ptr) hash_table_insert_impl(&(ht)->as_HashTableDynamicData, key, IMPLICIT_CAST(const void, const typeof(*(ht)->value_array), value_ptr))
#define hash_table_find(ht, key) hash_table_find_impl(&(ht)->as_HashTableDynamicData, key) #define hash_table_find(ht, key) hash_table_find_impl(&(ht)->as_HashTableDynamicData, key)
#define hash_table_delete_at_index(ht, index) hash_table_delete_at_index_impl(&(ht)->as_HashTableDynamicData, index)
#define hash_table_delete_by_key(ht, key) hash_table_delete_by_key_impl(&(ht)->as_HashTableDynamicData, key)
#endif /* end of include guard: HASH_TABLE_H_ */ #endif /* end of include guard: HASH_TABLE_H_ */

5
main.c
View File

@ -99,7 +99,10 @@ main(int argc, char ** argv)
config_t config_tree; config_t config_tree;
FullConfig loaded_config; FullConfig loaded_config;
config_init(&config_tree); config_init(&config_tree);
config_read_file(&config_tree, config_filename); if (config_read_file(&config_tree, config_filename) != CONFIG_TRUE) {
fprintf(stderr, "Config syntax error: %s:%d: %s\n", config_error_file(&config_tree), config_error_line(&config_tree), config_error_text(&config_tree));
exit(1);
}
config_set_auto_convert(&config_tree, CONFIG_TRUE); config_set_auto_convert(&config_tree, CONFIG_TRUE);
if (!load_config(config_root_setting(&config_tree), &loaded_config)) { if (!load_config(config_root_setting(&config_tree), &loaded_config)) {
perror("Failed to load config"); perror("Failed to load config");

View File

@ -23,6 +23,9 @@ typedef enum {
__attribute__((unused)) inline static ModifierSet __attribute__((unused)) inline static ModifierSet
modifier_set_copy(const ModifierSet old) modifier_set_copy(const ModifierSet old)
{ {
if (!old.byte_length) {
return EMPTY_MODIFIER_SET;
};
ModifierSet result = old; ModifierSet result = old;
result.bits = malloc(result.byte_length); result.bits = malloc(result.byte_length);
if (!result.bits) { if (!result.bits) {

View File

@ -35,6 +35,11 @@ handle_event(EventPositionBase * self, EventNode * event)
} }
for (size_t i = 0; i < count; ++i) { for (size_t i = 0; i < count; ++i) {
event->position = &node->as_GraphNode.outputs.elements[i]->as_EventPositionBase; event->position = &node->as_GraphNode.outputs.elements[i]->as_EventPositionBase;
if (!event->position) {
EventNode *orphaned = event;
event = orphaned->prev;
event_destroy(orphaned);
}
event = event->next; event = event->next;
} }
return true; return true;
@ -58,6 +63,7 @@ create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEn
has_payload = false; has_payload = false;
const config_setting_t *setting; const config_setting_t *setting;
if (config->options) {
if ((setting = config_setting_get_member(config->options, "namespace"))) { if ((setting = config_setting_get_member(config->options, "namespace"))) {
has_ns = true; has_ns = true;
source.code.ns = env_resolve_constant(env, setting); source.code.ns = env_resolve_constant(env, setting);
@ -74,6 +80,17 @@ create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEn
has_payload = true; has_payload = true;
source.payload = env_resolve_constant(env, setting); source.payload = env_resolve_constant(env, setting);
} }
} else {
// Prevent warning for uninitialized variable
source = (EventData) {
.code = {
.ns = 0,
.major = 0,
.minor = 0,
},
.payload = 0,
};
}
*node = (AssignGraphNode) { *node = (AssignGraphNode) {
.as_GraphNode = { .as_GraphNode = {

88
nodes/differentiate.c Normal file
View File

@ -0,0 +1,88 @@
#include "../graph.h"
#include "../module_registry.h"
typedef struct {
GraphNode as_GraphNode;
int64_t previous;
} DifferentiateGraphNode;
static bool
handle_event(EventPositionBase * self, EventNode * event)
{
DifferentiateGraphNode *node = DOWNCAST(DifferentiateGraphNode, GraphNode, DOWNCAST(GraphNode, EventPositionBase, self));
size_t count = node->as_GraphNode.outputs.length;
if (!count) {
event_destroy(event);
return true;
}
int64_t current = event->data.payload;
event->data.payload = current - node->previous;
node->previous = current;
if (count > 1) {
count = event_replicate(event, count - 1) + 1;
}
for (size_t i = 0; i < count; ++i) {
event->position = &node->as_GraphNode.outputs.elements[i]->as_EventPositionBase;
if (!event->position) {
EventNode *orphaned = event;
event = orphaned->prev;
event_destroy(orphaned);
}
event = event->next;
}
return true;
}
static GraphNode *
create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEnvironment * env)
{
(void) config;
(void) env;
DifferentiateGraphNode * node = T_ALLOC(1, DifferentiateGraphNode);
if (!node) {
return NULL;
}
int64_t initial = 0;
if (config->options) {
initial = env_resolve_constant(env, config_setting_get_member(config->options, "initial"));
}
*node = (DifferentiateGraphNode) {
.as_GraphNode = {
.as_EventPositionBase = {
.handle_event = &handle_event,
.waiting_new_event = false,
},
.specification = spec,
.inputs = EMPTY_GRAPH_CHANNEL_LIST,
.outputs = EMPTY_GRAPH_CHANNEL_LIST,
},
.previous = initial,
};
return &node->as_GraphNode;
}
static void destroy
(GraphNodeSpecification * self, GraphNode * target)
{
(void) self;
free(target);
}
GraphNodeSpecification nodespec_differentiate = (GraphNodeSpecification) {
.create = &create,
.destroy = &destroy,
.register_io = NULL,
.name = "differentiate",
.documentation = "Subtracts the previous event payload from the current one\nAccepts events on any connector\nSends events on all connectors"
"\nOption 'initial' (optional): the value to subtract from the first event payload"
,
};
MODULE_CONSTRUCTOR(init)
{
register_graph_node_specification(&nodespec_differentiate);
}

View File

@ -77,7 +77,7 @@ create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEn
} }
const char *filename = NULL; const char *filename = NULL;
bool should_grab = false; bool should_grab = false;
if (config) { if (config->options) {
node->namespace = env_resolve_constant(env, config_setting_get_member(config->options, "namespace")); 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; should_grab = env_resolve_constant(env, config_setting_get_member(config->options, "grab")) != 0;
config_setting_lookup_string(config->options, "file", &filename); config_setting_lookup_string(config->options, "file", &filename);

View File

@ -70,7 +70,7 @@ create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEn
.handle_io = handle_io, .handle_io = handle_io,
.enabled = true, .enabled = true,
}, },
.namespace = env_resolve_constant(env, config_setting_get_member(config->options, "namespace")), .namespace = config->options ? env_resolve_constant(env, config_setting_get_member(config->options, "namespace")) : 0,
}; };
return &node->as_GraphNode; return &node->as_GraphNode;
} }

View File

@ -22,6 +22,11 @@ handle_event(EventPositionBase * self, EventNode * event)
} }
for (size_t i = 0; i < count; ++i) { for (size_t i = 0; i < count; ++i) {
event->position = &node->as_GraphNode.outputs.elements[i]->as_EventPositionBase; event->position = &node->as_GraphNode.outputs.elements[i]->as_EventPositionBase;
if (!event->position) {
EventNode *orphaned = event;
event = orphaned->prev;
event_destroy(orphaned);
}
event = event->next; event = event->next;
} }
return true; return true;
@ -30,8 +35,10 @@ handle_event(EventPositionBase * self, EventNode * event)
static GraphNode * static GraphNode *
create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEnvironment * env) create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEnvironment * env)
{ {
(void) config; if (!config->options) {
(void) env; return NULL;
}
ModifiersGraphNode * node = T_ALLOC(1, ModifiersGraphNode); ModifiersGraphNode * node = T_ALLOC(1, ModifiersGraphNode);
if (!node) { if (!node) {
return NULL; return NULL;

View File

@ -50,8 +50,10 @@ handle_event(EventPositionBase * self, EventNode * event)
static GraphNode * static GraphNode *
create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEnvironment * env) create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEnvironment * env)
{ {
(void) config; if (!config->options) {
(void) env; return NULL;
}
ModifyPredicateGraphNode * node = T_ALLOC(1, ModifyPredicateGraphNode); ModifyPredicateGraphNode * node = T_ALLOC(1, ModifyPredicateGraphNode);
if (!node) { if (!node) {
return NULL; return NULL;

View File

@ -17,7 +17,11 @@ handle_event(EventPositionBase * self, EventNode * event)
} }
if (event_predicate_apply(node->predicates[i], event) == EVPREDRES_ACCEPTED) { if (event_predicate_apply(node->predicates[i], event) == EVPREDRES_ACCEPTED) {
if (event_replicate(event, 1)) { if (event_replicate(event, 1)) {
event->next->position = &node->as_GraphNode.outputs.elements[i]->as_EventPositionBase; EventNode * replica = event->next;
replica->position = &node->as_GraphNode.outputs.elements[i]->as_EventPositionBase;
if (!replica->position) {
event_destroy(replica);
}
} }
} }
} }
@ -28,8 +32,10 @@ handle_event(EventPositionBase * self, EventNode * event)
static GraphNode * static GraphNode *
create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEnvironment * env) create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEnvironment * env)
{ {
(void) config; if (!config->options) {
(void) env; return NULL;
}
RouterGraphNode * node = T_ALLOC(1, RouterGraphNode); RouterGraphNode * node = T_ALLOC(1, RouterGraphNode);
if (!node) { if (!node) {
return NULL; return NULL;

116
nodes/scale.c Normal file
View File

@ -0,0 +1,116 @@
#include "../graph.h"
#include "../module_registry.h"
typedef struct {
GraphNode as_GraphNode;
int64_t numerator;
int64_t denominator;
int64_t center;
int64_t defect;
bool amortize_rounding_error;
} ScaleGraphNode;
static bool
handle_event(EventPositionBase * self, EventNode * event)
{
ScaleGraphNode *node = DOWNCAST(ScaleGraphNode, GraphNode, DOWNCAST(GraphNode, EventPositionBase, self));
size_t count = node->as_GraphNode.outputs.length;
if (!count) {
event_destroy(event);
return true;
}
int64_t value = event->data.payload;
value -= node->center;
value *= node->numerator;
if (node->amortize_rounding_error) {
value += node->defect;
}
if (node->denominator) {
uint64_t undivided = value;
value /= node->denominator;
node->defect = undivided - value * node->denominator;
}
value += node->center;
event->data.payload = value;
if (count > 1) {
count = event_replicate(event, count - 1) + 1;
}
for (size_t i = 0; i < count; ++i) {
event->position = &node->as_GraphNode.outputs.elements[i]->as_EventPositionBase;
if (!event->position) {
EventNode *orphaned = event;
event = orphaned->prev;
event_destroy(orphaned);
}
event = event->next;
}
return true;
}
static GraphNode *
create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEnvironment * env)
{
(void) config;
(void) env;
ScaleGraphNode * node = T_ALLOC(1, ScaleGraphNode);
if (!node) {
return NULL;
}
int64_t
numerator = 1,
denominator = 1,
center = 0;
bool amortize_rounding_error = false;
if (config->options) {
numerator = env_resolve_constant_or(env, config_setting_get_member(config->options, "numerator"), numerator);
denominator = env_resolve_constant_or(env, config_setting_get_member(config->options, "denominator"), denominator);
center = env_resolve_constant_or(env, config_setting_get_member(config->options, "center"), center);
amortize_rounding_error = env_resolve_constant(env, config_setting_get_member(config->options, "amortize_rounding_error")) != 0;
}
*node = (ScaleGraphNode) {
.as_GraphNode = {
.as_EventPositionBase = {
.handle_event = &handle_event,
.waiting_new_event = false,
},
.specification = spec,
.inputs = EMPTY_GRAPH_CHANNEL_LIST,
.outputs = EMPTY_GRAPH_CHANNEL_LIST,
},
.numerator = numerator,
.denominator = denominator,
.center = center,
.amortize_rounding_error = amortize_rounding_error,
.defect = 0,
};
return &node->as_GraphNode;
}
static void destroy
(GraphNodeSpecification * self, GraphNode * target)
{
(void) self;
free(target);
}
GraphNodeSpecification nodespec_scale = (GraphNodeSpecification) {
.create = &create,
.destroy = &destroy,
.register_io = NULL,
.name = "scale",
.documentation = "Multiplies event payload by a constant fraction\nAccepts events on any connector\nSends events on all connectors"
"\nOption 'numerator' (optional): an integer to multiply by"
"\nOption 'denominator' (optional): an integer to divide by"
"\nOption 'center' (optional): an integer to scale around"
"\nOption 'amortize_rounding_error' (optional): whether to adjust the new event value by the rounding error of the previous event value"
,
};
MODULE_CONSTRUCTOR(init)
{
register_graph_node_specification(&nodespec_scale);
}

View File

@ -15,6 +15,11 @@ handle_event(EventPositionBase * self, EventNode * event)
} }
for (size_t i = 0; i < count; ++i) { for (size_t i = 0; i < count; ++i) {
event->position = &node->outputs.elements[i]->as_EventPositionBase; event->position = &node->outputs.elements[i]->as_EventPositionBase;
if (!event->position) {
EventNode *orphaned = event;
event = orphaned->prev;
event_destroy(orphaned);
}
event = event->next; event = event->next;
} }
return true; return true;

View File

@ -78,6 +78,10 @@ handle_event(EventPositionBase * self, EventNode * event)
static GraphNode * static GraphNode *
create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEnvironment * env) create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEnvironment * env)
{ {
if (!config->options) {
return NULL;
}
UinputGraphNode * node = T_ALLOC(1, UinputGraphNode); UinputGraphNode * node = T_ALLOC(1, UinputGraphNode);
if (!node) { if (!node) {
return NULL; return NULL;
@ -96,6 +100,12 @@ create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEn
return NULL; return NULL;
} }
size_t properties_length = 0;
const config_setting_t *properties_setting = config_setting_get_member(config->options, "properties");
if (properties_setting) {
properties_length = config_setting_length(enabled_codes_setting);
}
struct libevdev *dev = libevdev_new(); struct libevdev *dev = libevdev_new();
if (!dev) { if (!dev) {
free(node); free(node);
@ -107,6 +117,9 @@ create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEn
for (size_t i = 0; i < codes_length; ++i) { for (size_t i = 0; i < codes_length; ++i) {
load_enable_code(dev, config_setting_get_elem(enabled_codes_setting, i), env); load_enable_code(dev, config_setting_get_elem(enabled_codes_setting, i), env);
} }
for (size_t i = 0; i < properties_length; ++i) {
libevdev_enable_property(dev, (unsigned int) env_resolve_constant(env, config_setting_get_elem(properties_setting, i)));
}
struct libevdev_uinput *uidev; struct libevdev_uinput *uidev;
int err; int err;
@ -158,6 +171,7 @@ GraphNodeSpecification nodespec_uinput = (GraphNodeSpecification) {
.name = "uinput", .name = "uinput",
.documentation = "Writes received events to a new uinput device\nAccepts events on any connector\nDoes not send events" .documentation = "Writes received events to a new uinput device\nAccepts events on any connector\nDoes not send events"
"\nOption 'name' (required): device name provided to uinput" "\nOption 'name' (required): device name provided to uinput"
"\nOption 'properties' (optional): collection of integers --- input properties"
"\nOption 'enabled_codes' (required): collection of event code specifications:" "\nOption 'enabled_codes' (required): collection of event code specifications:"
"\n\tField 'major' (required): evdev event type" "\n\tField 'major' (required): evdev event type"
"\n\tField 'minor' (required): evdev event code" "\n\tField 'minor' (required): evdev event code"

329
nodes/window.c Normal file
View File

@ -0,0 +1,329 @@
#include <assert.h>
#include <limits.h>
#include "../graph.h"
#include "../module_registry.h"
#include "../queue.h"
#include "../hash_table.h"
typedef TYPED_HASH_TABLE(bool) EventSet;
typedef struct {
GraphNode as_GraphNode;
EventData terminator_prototype;
bool has_terminator;
bool is_jumping;
bool has_max_time, has_max_length;
RelativeTime max_time;
size_t max_length;
size_t additional_step;
size_t skip_next;
Queue buffer;
EventSet buffered_set;
} WindowGraphNode;
inline static HashTableKey
hash_event_ptr(const EventNode * ptr) {
return hash_table_key_from_bytes((void*)&ptr, sizeof(ptr));
}
inline static bool
event_set_has(const EventSet * set, const EventNode * element) {
return hash_table_find(set, hash_event_ptr(element)) >= 0;
}
inline static bool
event_set_add(EventSet * set, const EventNode * element) {
const bool value = true;
return hash_table_insert(set, hash_event_ptr(element), &value) >= 0;
}
inline static bool
event_set_del(EventSet * set, const EventNode * element) {
return hash_table_delete_by_key(set, hash_event_ptr(element));
}
static void
broadcast_event(GraphNode * source, EventNode * event)
{
if (!event) {
return;
}
size_t count = source->outputs.length;
if (!count) {
event_destroy(event);
return;
}
if (count > 1) {
count = event_replicate(event, count - 1) + 1;
}
for (size_t i = 0; i < count; ++i) {
event->position = &source->outputs.elements[i]->as_EventPositionBase;
if (!event->position) {
EventNode *orphaned = event;
event = orphaned->prev;
event_destroy(orphaned);
}
event = event->next;
}
}
static EventNode *
replicate_and_advance(EventNode ** ptr)
{
EventNode * old = *ptr;
if (!old) {
return NULL;
}
if (event_replicate(old, 1)) {
*ptr = old->next;
return old;
}
return NULL;
}
static void
trigger_new_window(WindowGraphNode * node, EventNode * base)
{
if (!base) {
return;
}
if (node->has_terminator) {
EventNode * terminator = replicate_and_advance(&base);
if (terminator) {
terminator->data.code = node->terminator_prototype.code;
terminator->data.modifiers = modifier_set_copy(node->terminator_prototype.modifiers);
terminator->data.payload = node->terminator_prototype.payload;
broadcast_event(&node->as_GraphNode, terminator);
}
}
size_t step = 1;
if (node->is_jumping) {
step = queue_length(&node->buffer);
}
step += node->additional_step;
if (step < 1) {
step = 1;
}
while (step > 0) {
QueueValue popped;
if (!queue_try_pop(&node->buffer, &popped)) {
break;
}
--step;
EventNode * event = popped.as_ptr;
event_set_del(&node->buffered_set, event);
}
node->skip_next += step;
QUEUE_FOREACH_INDEX(i, &node->buffer) {
EventNode *orig = node->buffer.values[i].as_ptr;
if (!orig) {
continue;
}
EventNode *recipient = replicate_and_advance(&base);
if (!recipient) {
continue;
}
recipient->data = orig->data;
recipient->data.modifiers = modifier_set_copy(orig->data.modifiers);
broadcast_event(&node->as_GraphNode, recipient);
}
event_destroy(base);
}
static bool
handle_event(EventPositionBase * self, EventNode * event)
{
WindowGraphNode *node = DOWNCAST(WindowGraphNode, GraphNode, DOWNCAST(GraphNode, EventPositionBase, self));
if (event_set_has(&node->buffered_set, event)) {
return false;
}
EventNode *replacement;
const AbsoluteTime new_time = event->data.time;
if (node->has_max_time) {
const RelativeTime threshold = node->max_time;
while (queue_length(&node->buffer) > 0) {
EventNode *first_event = queue_peek(&node->buffer).as_ptr;
if (!first_event) {
break;
}
RelativeTime delta = absolute_time_sub_absolute(new_time, first_event->data.time);
if (relative_time_cmp(delta, threshold) <= 0) {
break;
}
// Here replacement should occur simultaneously-before the event
replacement = replicate_and_advance(&event);
if (!replacement) {
return false;
}
event->position = self;
replacement->position = NULL;
trigger_new_window(node, replacement);
}
}
if (node->skip_next > 0) {
--(node->skip_next);
return true;
}
// Replacement should occur after the forwarded replica
replacement = NULL;
if (event_replicate(event, 1)) {
replacement = event->next;
}
if (event_replicate(event, 1)) {
broadcast_event(&node->as_GraphNode, event->next);
}
queue_put(&node->buffer, (QueueValue){.as_ptr = event});
event_set_add(&node->buffered_set, event);
if (node->has_max_length) {
const size_t threshold = node->max_length;
while (replacement && queue_length(&node->buffer) >= threshold) {
trigger_new_window(node, replicate_and_advance(&replacement));
}
}
if (replacement) {
event_destroy(replacement);
}
self->waiting_new_event = true;
return true;
}
static void
load_event_prototype(InitializationEnvironment * env, const config_setting_t * setting, EventData * proto)
{
if (!setting) {
return;
}
proto->code.ns = env_resolve_constant(env, config_setting_get_member(setting, "namespace"));
proto->code.major = env_resolve_constant(env, config_setting_get_member(setting, "major"));
proto->code.minor = env_resolve_constant(env, config_setting_get_member(setting, "minor"));
proto->payload = env_resolve_constant(env, config_setting_get_member(setting, "payload"));
// TODO modifiers
}
static GraphNode *
create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEnvironment * env)
{
WindowGraphNode * node = T_ALLOC(1, WindowGraphNode);
if (!node) {
return NULL;
}
bool is_jumping = false;
ssize_t additional_step = 0;
long long max_length = 0;
long long max_milliseconds = 0;
EventData terminator = {.code = {0, 0, 0}, .payload = 0, .priority = 10, .modifiers = EMPTY_MODIFIER_SET, .time = {}};
bool
has_max_length = false,
has_max_time = false,
has_terminator = false;
if (config->options) {
is_jumping = env_resolve_constant(env, config_setting_get_member(config->options, "is_jumping")) != 0;
additional_step = (ssize_t) env_resolve_constant(env, config_setting_get_member(config->options, "additional_step"));
if (additional_step < 0) {
additional_step = 0;
}
const config_setting_t *setting;
if ((setting = config_setting_get_member(config->options, "max_length"))) {
has_max_length = true;
max_length = env_resolve_constant(env, setting);
if (max_length < 0) {
max_length = 0;
}
if (max_length > INT32_MAX) {
max_length = 0;
has_max_length = false;
}
}
if ((setting = config_setting_get_member(config->options, "max_milliseconds"))) {
has_max_time = true;
max_milliseconds = env_resolve_constant(env, setting);
if (max_milliseconds < 0) {
max_milliseconds = 0;
}
}
if ((setting = config_setting_get_member(config->options, "terminator"))) {
has_terminator = true;
load_event_prototype(env, setting, &terminator);
}
}
*node = (WindowGraphNode) {
.as_GraphNode = {
.as_EventPositionBase = {
.handle_event = &handle_event,
.waiting_new_event = false,
},
.specification = spec,
.inputs = EMPTY_GRAPH_CHANNEL_LIST,
.outputs = EMPTY_GRAPH_CHANNEL_LIST,
},
.terminator_prototype = terminator,
.has_terminator = has_terminator,
.is_jumping = is_jumping,
.has_max_time = has_max_time,
.has_max_length = has_max_length,
.max_time = relative_time_from_millisecond(max_milliseconds),
.max_length = max_length,
.additional_step = additional_step,
.skip_next = 0,
.buffer = EMPTY_QUEUE,
.buffered_set = {},
};
hash_table_init(&node->buffered_set, NULL);
return &node->as_GraphNode;
}
static void destroy
(GraphNodeSpecification * self, GraphNode * target)
{
(void) self;
WindowGraphNode * node = DOWNCAST(WindowGraphNode, GraphNode, target);
modifier_set_destruct(&node->terminator_prototype.modifiers);
queue_deinit(&node->buffer);
hash_table_deinit(&node->buffered_set);
free(target);
}
GraphNodeSpecification nodespec_window = (GraphNodeSpecification) {
.create = &create,
.destroy = &destroy,
.register_io = NULL,
.name = "window",
.documentation = "Passes events through while copying them into an internal buffer, when buffer buffer.length or (buffer.last.time - buffer.first.time) thresholds are met optionally sends terminator event, rewinds events to buffer start, skips ((is_jumping ? buffer.length : 1) + additional_step) events, retransmits remaining buffered events and starts over\nAccepts events on any connector\nSends events on all connectors"
"\nOption 'is_jumping' (optional): whether to send events at most once"
"\nOption 'additional_step' (optional): natural number --- additional step relative to a regular sliding/jumping window"
"\nOption 'max_length' (optional): natural number --- maximum number of events in a window"
"\nOption 'max_milliseconds' (optional): natural number --- maximum number milliseconds between the first and the last event in a window"
"\nOption 'terminator' (optional): event to send after the window fullness condition is met:"
"\n\tField 'namespace' (optional): set generated event code namespace"
"\n\tField 'major' (optional): set generated event code major"
"\n\tField 'minor' (optional): set generated event code minor"
"\n\tField 'payload' (optional): set generated event payload"
,
};
MODULE_CONSTRUCTOR(init)
{
register_graph_node_specification(&nodespec_window);
}

View File

@ -10,13 +10,13 @@ io_subscription_list_extend(IOSubscriptionList * lst)
size_t capacity = lst->capacity; size_t capacity = lst->capacity;
capacity = capacity + (capacity >> 1) + 1; capacity = capacity + (capacity >> 1) + 1;
int * new_fds = reallocarray(lst->fds, capacity, sizeof(int)); int * new_fds = T_REALLOC(lst->fds, capacity, int);
if (!new_fds) { if (!new_fds) {
return false; return false;
} }
lst->fds = new_fds; lst->fds = new_fds;
IOHandling ** new_subscribers = reallocarray(lst->subscribers, capacity, sizeof(IOHandling*)); IOHandling ** new_subscribers = T_REALLOC(lst->subscribers, capacity, IOHandling*);
if (!new_subscribers) { if (!new_subscribers) {
return false; return false;
} }

View File

@ -1,5 +1,10 @@
constants = {
sensitivity_num = 2;
sensitivity_denom = 5;
};
enums = { enums = {
namespaces: ["clickpad"]; namespaces: ["clickpad", "window_terminator"];
}; };
predicates = { predicates = {
@ -30,6 +35,28 @@ predicates = {
} }
); );
}; };
abs_event = {
type = "and";
args = (
"is_evdev",
{
type = "code_major";
min = "event_type.ABS";
max = "event_type.ABS";
}
);
};
misc_event = {
type = "and";
args = (
"is_evdev",
{
type = "code_major";
min = "event_type.MSC";
max = "event_type.MSC";
}
);
};
btn_quadtap_event = { btn_quadtap_event = {
type = "and"; type = "and";
args = ( args = (
@ -41,6 +68,66 @@ predicates = {
} }
); );
}; };
misc_timestamp = {
type = "and";
args = (
"misc_event",
{
type = "code_minor";
min = "misc_event.TIMESTAMP";
max = "misc_event.TIMESTAMP";
}
);
};
abs_x = {
type = "and";
args = (
"abs_event",
{
type = "code_minor";
min = "absolute_axis.X";
max = "absolute_axis.X";
}
);
};
abs_y = {
type = "and";
args = (
"abs_event",
{
type = "code_minor";
min = "absolute_axis.Y";
max = "absolute_axis.Y";
}
);
};
bypass = {
type = "or";
args = ["syn_event", "misc_timestamp"];
};
payload_zero = {
type = "payload";
min = 0;
max = 0;
};
payload_one = {
type = "payload";
min = 1;
max = 1;
};
is_window_terminator = {
type = "code_ns";
min = "namespaces.window_terminator";
max = "namespaces.window_terminator";
};
inside_drag_window = {
type = "accept";
inverted = 1;
};
drag_held = {
type = "accept";
inverted = 1;
};
}; };
nodes = { nodes = {
@ -54,7 +141,7 @@ nodes = {
select_interesting_events = { select_interesting_events = {
type = "router"; type = "router";
options = { options = {
predicates = ["syn_event", "btn_quadtap_event"]; predicates = ["bypass", "btn_quadtap_event", "abs_x", "abs_y"];
}; };
}; };
quadtap_subst_seq = { quadtap_subst_seq = {
@ -85,7 +172,75 @@ nodes = {
{major: "event_type.KEY"; minor: "button_event.RIGHT"} {major: "event_type.KEY"; minor: "button_event.RIGHT"}
, ,
{major: "event_type.KEY"; minor: "button_event.MIDDLE"} {major: "event_type.KEY"; minor: "button_event.MIDDLE"}
,
{major: "event_type.REL"; minor: "relative_axis.X"}
,
{major: "event_type.REL"; minor: "relative_axis.Y"}
,
{major: "event_type.MSC"; minor: "misc_event.TIMESTAMP"}
); );
properties = ["input_property.POINTER", "input_property.BUTTONPAD"];
};
};
differentiate_x = {
type = "differentiate";
};
differentiate_y = {
type = "differentiate";
};
morph_rel = {
type = "assign";
options = {
major = "event_type.REL";
// Preserve minor, because relative_axis.X = absolute_axis.X, relative_axis.Y = absolute_axis.Y
};
};
while_drag_held = {
type = "router";
options = {
predicates = ["drag_held"];
};
};
sensitivity_x = {
type = "scale";
options = {
numerator = "sensitivity_num";
denominator = "sensitivity_denom";
amortize_rounding_error = "true";
};
};
sensitivity_y = {
type = "scale";
options = {
numerator = "sensitivity_num";
denominator = "sensitivity_denom";
amortize_rounding_error = "true";
};
};
drag_start_window = {
type = "window";
options = {
max_length = 2;
max_milliseconds = 500;
terminator = {
namespace = "namespaces.window_terminator";
};
};
};
update_inside_drag_window = {
type = "modify_predicate";
options = {
target = "inside_drag_window";
uninvert_on = {type: "and"; args: ["is_evdev", "payload_zero"]};
invert_on = "is_window_terminator";
};
};
update_drag_held = {
type = "modify_predicate";
options = {
target = "drag_held";
uninvert_on = {type: "and"; args: ["inside_drag_window", "is_evdev", "payload_one"]};
invert_on = {type: "and"; args: ["is_evdev", "payload_zero"]};
}; };
}; };
}; };
@ -111,9 +266,42 @@ channels = ({
}, { }, {
from: ("morph_right_click", 0); from: ("morph_right_click", 0);
to: ("merge", 2); to: ("merge", 2);
}, {
from: ("select_interesting_events", 2); // ABS_X
to: ("differentiate_x", 0);
}, {
from: ("select_interesting_events", 3); // ABS_Y
to: ("differentiate_y", 0);
}, {
from: ("differentiate_x", 0);
to: ("sensitivity_x", 0);
}, {
from: ("differentiate_y", 0);
to: ("sensitivity_y", 0);
}, {
from: ("sensitivity_x", 0);
to: ("while_drag_held", 0);
}, {
from: ("sensitivity_y", 0);
to: ("while_drag_held", 1);
}, {
from: ("while_drag_held", 0);
to: ("morph_rel", 0);
}, {
from: ("morph_rel", 0);
to: ("merge", 3);
}, { }, {
from: ("merge", 0); from: ("merge", 0);
to: ("virtual_out", 0); to: ("virtual_out", 0);
}, {
from: ("quadtap_subst_seq", 4);
to: ("drag_start_window", 0);
}, {
from: ("drag_start_window", 0);
to: ("update_inside_drag_window", 0);
}, {
from: ("drag_start_window", 1);
to: ("update_drag_held", 0);
}); });
// vim: ft=libconfig // vim: ft=libconfig

90
queue.c Normal file
View File

@ -0,0 +1,90 @@
#include <assert.h>
#include <string.h>
#include "queue.h"
void
queue_deinit_with_destructor(Queue * q, void (*value_destructor)(QueueValue value, void * destructor_closure), void * destructor_closure)
{
if (q->values) {
if (value_destructor) {
QUEUE_FOREACH_INDEX(i, q) {
value_destructor(q->values[i], destructor_closure);
}
}
free(q->values);
q->values = NULL;
}
q->capacity = 0;
q->after_tail_idx = 0;
q->head_idx = 0;
}
static bool
queue_grow(Queue * queue)
{
size_t old_capacity = queue->capacity;
size_t new_capacity = old_capacity + (old_capacity >> 1) + 2;
QueueValue *values = queue->values;
if (values) {
values = T_REALLOC(values, new_capacity, QueueValue);
if (!values) {
return false;
}
if (queue->head_idx > queue->after_tail_idx) {
size_t old_head_idx = queue->head_idx;
size_t head_shift = new_capacity - old_capacity;
size_t shifted_count = old_capacity - old_head_idx;
QueueValue *old_head_ptr = &values[old_head_idx];
memmove(old_head_ptr + head_shift, old_head_ptr, shifted_count * sizeof(QueueValue));
queue->head_idx = old_head_idx + head_shift;
}
} else {
values = T_ALLOC(new_capacity, QueueValue);
if (!values) {
return false;
}
}
queue->values = values;
queue->capacity = new_capacity;
return true;
}
ssize_t
queue_put(Queue * queue, QueueValue value)
{
if (queue_is_full(queue)) {
if (!queue_grow(queue)) {
return -1;
}
assert(!queue_is_full(queue));
}
size_t capacity = queue->capacity;
assert(capacity > 0);
size_t index = queue->after_tail_idx;
queue->values[index] = value;
queue->after_tail_idx = (index + 1) % capacity;
return index;
}
bool
queue_try_pop(Queue * queue, QueueValue * ret)
{
if (!queue_try_peek(queue, ret)) {
return false;
}
queue->head_idx = (queue->head_idx + 1) % queue->capacity;
return true;
}
bool
queue_try_peek(const Queue * queue, QueueValue * ret)
{
if (queue_is_empty(queue)) {
return false;
}
assert(queue->capacity > 0);
if (ret) {
*ret = queue->values[queue->head_idx];
}
return true;
}

83
queue.h Normal file
View File

@ -0,0 +1,83 @@
#ifndef QUEUE_H_
#define QUEUE_H_
#include "defs.h"
typedef union {
void *as_ptr;
intptr_t as_intptr_t;
intptr_t as_signed;
uintptr_t as_uintptr_t;
uintptr_t as_unsigned;
uint8_t as_bytes[sizeof(void*)];
} QueueValue;
#define ZERO_QUEUE_VALUE ((QueueValue) {.as_ptr = NULL})
typedef struct {
// Always keep one unused slot to distinguish empty and full queue
size_t capacity;
size_t after_tail_idx;
size_t head_idx;
QueueValue *values;
} Queue;
#define EMPTY_QUEUE ((Queue) {.capacity = 0, .after_tail_idx = 0, .head_idx = 0, .values = NULL})
void queue_deinit_with_destructor(Queue * queue, void (*value_destructor)(QueueValue value, void * destructor_closure), void * destructor_closure);
ssize_t queue_put(Queue * queue, QueueValue value);
bool queue_try_pop(Queue * queue, QueueValue * ret);
bool queue_try_peek(const Queue * queue, QueueValue * ret);
__attribute__((unused)) inline static size_t
queue_length(const Queue * queue)
{
ssize_t n = queue->after_tail_idx - queue->head_idx;
if (n < 0) {
n += queue->capacity;
}
return n;
}
__attribute__((unused)) inline static bool
queue_is_empty(const Queue * queue)
{
return queue->head_idx == queue->after_tail_idx;
}
__attribute__((unused)) inline static bool
queue_is_full(const Queue * queue)
{
size_t capacity = queue->capacity;
if (!capacity) {
return true;
}
return (queue->after_tail_idx + 1) % capacity == queue->head_idx;
}
__attribute__((unused)) inline static void
queue_deinit(Queue * queue)
{
queue_deinit_with_destructor(queue, NULL, NULL);
}
__attribute__((unused)) inline static QueueValue
queue_pop(Queue * queue)
{
QueueValue value = ZERO_QUEUE_VALUE;
queue_try_pop(queue, &value);
return value;
}
__attribute__((unused)) inline static QueueValue
queue_peek(const Queue * queue)
{
QueueValue value = ZERO_QUEUE_VALUE;
queue_try_peek(queue, &value);
return value;
}
// As long as head and tail indices are correct, division by zero should not happen
#define QUEUE_FOREACH_INDEX(i, q) for (size_t i = (q)->head_idx; i != (q)->after_tail_idx; i = (i + 1) % (q)->capacity)
#endif /* end of include guard: QUEUE_H_ */

40
time.h
View File

@ -3,6 +3,7 @@
#include <time.h> #include <time.h>
#define NANOSECONDS_IN_SECOND 1000000000 #define NANOSECONDS_IN_SECOND 1000000000
#define MILLISECONDS_IN_SECOND 1000
typedef struct { typedef struct {
struct timespec absolute; struct timespec absolute;
@ -12,6 +13,29 @@ typedef struct {
struct timespec relative; struct timespec relative;
} RelativeTime; } RelativeTime;
__attribute__((unused)) inline static void
timespec_assign_nanosecond(struct timespec *target, long long ns)
{
time_t sec = ns / NANOSECONDS_IN_SECOND;
if (ns < 0) {
sec -= 1;
}
target->tv_sec = sec;
target->tv_nsec = ns - sec * NANOSECONDS_IN_SECOND;
}
__attribute__((unused)) inline static void
timespec_assign_millisecond(struct timespec *target, long long ms)
{
time_t sec = ms / MILLISECONDS_IN_SECOND;
if (ms < 0) {
sec -= 1;
}
ms -= sec * MILLISECONDS_IN_SECOND;
target->tv_sec = sec;
target->tv_nsec = ms * (NANOSECONDS_IN_SECOND / MILLISECONDS_IN_SECOND);
}
__attribute__((unused)) inline static void __attribute__((unused)) inline static void
timespec_add(struct timespec *accum, const struct timespec *item) timespec_add(struct timespec *accum, const struct timespec *item)
{ {
@ -87,6 +111,22 @@ absolute_time_sub_absolute(AbsoluteTime lhs, AbsoluteTime rhs)
return result; return result;
} }
__attribute__((unused)) inline static RelativeTime
relative_time_from_nanosecond(long long ns)
{
RelativeTime result;
timespec_assign_nanosecond(&result.relative, ns);
return result;
}
__attribute__((unused)) inline static RelativeTime
relative_time_from_millisecond(long long ms)
{
RelativeTime result;
timespec_assign_millisecond(&result.relative, ms);
return result;
}
__attribute__((unused)) inline static RelativeTime __attribute__((unused)) inline static RelativeTime
relative_time_add(RelativeTime lhs, RelativeTime rhs) relative_time_add(RelativeTime lhs, RelativeTime rhs)
{ {