plugin: add ability to queue more events in the evdev_frame callback

This adds a event queue pointer to each plugin that is set when the
plugin's evdev_frame() callback is invoked. If the plugin calls
libinput_plugin_queue_event_frame() during the callback the given
event frame is appended to an event frame list (starting with the
current frame). Once the callback completes, that frame list is
passed to the next plugin and each frame is replayed on the next plugin.

In the case of multiple plugins queueing events this effectively builds
a tree of frames which each level of the tree representing one plugin.

Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1217>
This commit is contained in:
Peter Hutterer 2025-05-29 11:19:07 +10:00 committed by Marge Bot
parent 38d92a96b9
commit 7137eb9702
2 changed files with 267 additions and 1 deletions

View file

@ -44,6 +44,11 @@ struct libinput_plugin {
bool registered; bool registered;
const struct libinput_plugin_interface *interface; const struct libinput_plugin_interface *interface;
struct {
struct list *after;
struct list *before;
} event_queue;
}; };
LIBINPUT_ATTRIBUTE_PRINTF(3, 4) LIBINPUT_ATTRIBUTE_PRINTF(3, 4)
@ -146,6 +151,79 @@ libinput_plugin_get_context(struct libinput_plugin *plugin)
return plugin->libinput; return plugin->libinput;
} }
struct plugin_queued_event {
struct list link;
struct evdev_frame *frame; /* owns a ref */
struct libinput_device *device; /* owns a ref */
};
static void
plugin_queued_event_destroy(struct plugin_queued_event *event)
{
evdev_frame_unref(event->frame);
libinput_device_unref(event->device);
list_remove(&event->link);
free(event);
}
static inline struct plugin_queued_event *
plugin_queued_event_new(struct evdev_frame *frame, struct libinput_device *device)
{
struct plugin_queued_event *event = zalloc(sizeof(*event));
if (!event)
return NULL;
event->frame = evdev_frame_ref(frame);
event->device = libinput_device_ref(device);
return event;
}
static void
libinput_plugin_queue_evdev_frame(struct list *queue,
const char *func,
struct libinput_plugin *plugin,
struct libinput_device *device,
struct evdev_frame *frame)
{
if (queue == NULL) {
plugin_log_bug(plugin,
"%s() called outside evdev_frame processing\n",
func);
libinput_plugin_unregister(plugin);
return;
}
_unref_(evdev_frame) *clone = evdev_frame_clone(frame);
struct plugin_queued_event *event =
plugin_queued_event_new(clone, device);
list_take_append(queue, event, link);
}
void
libinput_plugin_append_evdev_frame(struct libinput_plugin *plugin,
struct libinput_device *device,
struct evdev_frame *frame)
{
libinput_plugin_queue_evdev_frame(plugin->event_queue.after,
__func__,
plugin,
device,
frame);
}
void
libinput_plugin_prepend_evdev_frame(struct libinput_plugin *plugin,
struct libinput_device *device,
struct evdev_frame *frame)
{
libinput_plugin_queue_evdev_frame(plugin->event_queue.before,
__func__,
plugin,
device,
frame);
}
void void
libinput_plugin_run(struct libinput_plugin *plugin) libinput_plugin_run(struct libinput_plugin *plugin)
{ {
@ -306,14 +384,154 @@ libinput_plugin_system_notify_device_ignored(struct libinput_plugin_system *syst
libinput_plugin_system_drop_unregistered_plugins(system); libinput_plugin_system_drop_unregistered_plugins(system);
} }
static void
libinput_plugin_process_frame(struct libinput_plugin *plugin,
struct libinput_device *device,
struct evdev_frame *frame,
struct list *queued_events)
{
struct list before_events = LIST_INIT(before_events);
struct list after_events = LIST_INIT(after_events);
plugin->event_queue.before = &before_events;
plugin->event_queue.after = &after_events;
if (plugin->interface->evdev_frame)
plugin->interface->evdev_frame(plugin, device, frame);
plugin->event_queue.before = NULL;
plugin->event_queue.after = NULL;
list_chain(queued_events, &before_events);
if (!evdev_frame_is_empty(frame)) {
struct plugin_queued_event *event = plugin_queued_event_new(frame, device);
list_take_append(queued_events, event, link);
}
list_chain(queued_events, &after_events);
}
_unused_
static inline void
print_frame(struct libinput *libinput,
struct evdev_frame *frame)
{
static uint32_t offset = 0;
static uint32_t last_time = 0;
uint32_t time = evdev_frame_get_time(frame) / 1000;
if (offset == 0) {
offset = time;
last_time = time - offset;
}
time -= offset;
size_t nevents;
struct evdev_event *events = evdev_frame_get_events(frame, &nevents);
for (size_t i = 0; i < nevents; i++) {
struct evdev_event *e = &events[i];
switch (evdev_usage_enum(e->usage)) {
case EVDEV_SYN_REPORT:
log_debug(libinput,
"%u.%03u ----------------- EV_SYN ----------------- +%ums\n",
time / 1000,
time % 1000,
time - last_time);
last_time = time;
break;
case EVDEV_MSC_SERIAL:
log_debug(libinput,
"%u.%03u %-16s %-16s %#010x\n",
time / 1000,
time % 1000,
evdev_event_get_type_name(e),
evdev_event_get_code_name(e),
e->value);
break;
default:
log_debug(libinput,
"%u.%03u %-16s %-20s %4d\n",
time / 1000,
time % 1000,
evdev_event_get_type_name(e),
evdev_event_get_code_name(e),
e->value);
break;
}
}
}
void void
libinput_plugin_system_notify_evdev_frame(struct libinput_plugin_system *system, libinput_plugin_system_notify_evdev_frame(struct libinput_plugin_system *system,
struct libinput_device *device, struct libinput_device *device,
struct evdev_frame *frame) struct evdev_frame *frame)
{ {
/* This is messy because a single event frame may cause
* *each* plugin to generate multiple event frames for potentially
* different devices and replaying is basically breadth-first traversal.
*
* So we have our event (passed in as 'frame') and we create a queue.
* Each plugin then creates a new event list from each frame in the
* queue.
*/
struct plugin_queued_event *our_event = plugin_queued_event_new(frame, device);
struct list queued_events = LIST_INIT(queued_events);
list_take_insert(&queued_events, our_event, link);
uint64_t frame_time = evdev_frame_get_time(frame);
struct libinput_plugin *plugin; struct libinput_plugin *plugin;
list_for_each_safe(plugin, &system->plugins, link) { list_for_each_safe(plugin, &system->plugins, link) {
libinput_plugin_notify_evdev_frame(plugin, device, frame); /* The list of queued events for the *next* plugin */
struct list next_events = LIST_INIT(next_events);
/* Iterate through the current list of queued events, pass
* each through to the plugin and remove it from the current
* list. The plugin may generate a new event list (possibly
* containing our frame but not our queued_event directly)
* and that list becomes the event list for the next plugin.
*/
struct plugin_queued_event *event;
list_for_each_safe(event, &queued_events, link) {
struct list next = LIST_INIT(next);
if (evdev_frame_get_time(event->frame) == 0)
evdev_frame_set_time(event->frame, frame_time);
#ifdef EVENT_DEBUGGING
log_debug(libinput_device_get_context(device),
"Plugin %s processing frame for device %s\n",
plugin->name,
libinput_device_get_name(event->device));
print_frame(libinput_device_get_context(device), event->frame);
#endif
libinput_plugin_process_frame(plugin,
event->device,
event->frame,
&next);
list_chain(&next_events, &next);
plugin_queued_event_destroy(event);
}
assert(list_empty(&queued_events));
list_chain(&queued_events, &next_events);
if (list_empty(&queued_events)) {
/* No more events to process, stop here */
break;
}
}
/* Our own evdev plugin is last and discards the event for us */
if (!list_empty(&queued_events)) {
log_bug_libinput(libinput_device_get_context(device),
"Events left over to replay after last plugin\n");
} }
libinput_plugin_system_drop_unregistered_plugins(system); libinput_plugin_system_drop_unregistered_plugins(system);
} }

View file

@ -144,3 +144,51 @@ libinput_plugin_unref(struct libinput_plugin *plugin);
#ifdef DEFINE_UNREF_CLEANUP_FUNC #ifdef DEFINE_UNREF_CLEANUP_FUNC
DEFINE_UNREF_CLEANUP_FUNC(libinput_plugin); DEFINE_UNREF_CLEANUP_FUNC(libinput_plugin);
#endif #endif
/**
* Queue an event frame for the next plugin in sequence, after
* the current event frame being processed.
*
* This function can only be called from within the evdev_frame()
* callback and may be used to queue new frames. If called multiple
* times with frames F1, F2, ... while processing the current frame C,
* frame sequence to be passed to the next plugin is
* C, F1, F2, ...
*
* Assuming several plugins P1, P2, P3, and P4 and an event frame E,
* and P2 and P3 calling this function with an queued event frame Q1 and Q2,
* respectively.
*
* - P1: receives E, optionally modifies E
* - P2: receives E, appends Q1, optionally modifies E
* - P3: receives E, appends Q2, optionally modifies E
* - P3: receives Q1, optionally modifies Q1
* - P4: receives E, optionally modifies E
* - P4: receives Q2, optionally modifies Q2
* - P4: receives Q1, optionally modifies Q1
*
* Once plugin processing is complete, the event sequence passed
* back to libinput is thus [E, Q2, Q1].
*
* To discard the original event frame, the plugin needs to
* call evdev_frame_reset() on the frame passed to it.
*
* It is a plugin bug to call this function from outside the
* evdev_frame() callback.
*/
void
libinput_plugin_append_evdev_frame(struct libinput_plugin *libinput,
struct libinput_device *device,
struct evdev_frame *frame);
/**
* Identical to libinput_plugin_append_evdev_frame(), but prepends
* the event frame to the current event frame being processed.
* If called multiple times with frames F1, F2, ... while processing
* the current frame C, frame sequence to be passed to the next
* plugin is F1, F2, ..., C
*/
void
libinput_plugin_prepend_evdev_frame(struct libinput_plugin *libinput,
struct libinput_device *device,
struct evdev_frame *frame);