plugin: wrap timers for use by plugins

Wrapping timers means plugins we can set up the event queue for a plugin
when the timer triggers, allowing plugins to queue events during the
timer func.

Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1217>
This commit is contained in:
Peter Hutterer 2025-06-02 17:52:26 +10:00 committed by Marge Bot
parent e69c0b9bfb
commit 5698e3c12f
2 changed files with 224 additions and 11 deletions

View file

@ -31,6 +31,8 @@
#include "libinput-plugin.h"
#include "libinput-plugin-private.h"
#include "libinput-plugin-system.h"
#include "timer.h"
#include "libinput-util.h"
#include "libinput-private.h"
@ -45,12 +47,23 @@ struct libinput_plugin {
const struct libinput_plugin_interface *interface;
struct list timers;
struct {
struct list *after;
struct list *before;
} event_queue;
};
struct libinput_plugin_timer {
int refcount;
struct list link;
struct libinput_plugin *plugin;
struct libinput_timer timer;
void (*func)(struct libinput_plugin *plugin, uint64_t now, void *user_data);
void *user_data;
};
LIBINPUT_ATTRIBUTE_PRINTF(3, 4)
void
plugin_log_msg(struct libinput_plugin *plugin,
@ -85,6 +98,7 @@ libinput_plugin_new(struct libinput *libinput,
plugin->interface = interface;
plugin->user_data = user_data;
plugin->name = strdup(name);
list_init(&plugin->timers);
libinput_plugin_system_register_plugin(&libinput->plugin_system, plugin);
@ -117,6 +131,12 @@ libinput_plugin_unref(struct libinput_plugin *plugin)
{
assert(plugin->refcount > 0);
if (--plugin->refcount == 0) {
struct libinput_plugin_timer *timer;
list_for_each_safe(timer, &plugin->timers, link) {
libinput_plugin_timer_cancel(timer);
libinput_plugin_timer_unref(timer);
}
list_remove(&plugin->link);
if (plugin->interface->destroy)
plugin->interface->destroy(plugin);
@ -170,8 +190,6 @@ 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);
@ -470,10 +488,11 @@ print_frame(struct libinput *libinput,
}
}
void
libinput_plugin_system_notify_evdev_frame(struct libinput_plugin_system *system,
struct libinput_device *device,
struct evdev_frame *frame)
static void
plugin_system_notify_evdev_frame(struct libinput_plugin_system *system,
struct libinput_device *device,
struct evdev_frame *frame,
struct libinput_plugin *sender_plugin)
{
/* This is messy because a single event frame may cause
* *each* plugin to generate multiple event frames for potentially
@ -490,8 +509,19 @@ libinput_plugin_system_notify_evdev_frame(struct libinput_plugin_system *system,
uint64_t frame_time = evdev_frame_get_time(frame);
bool delay = !!sender_plugin;
struct libinput_plugin *plugin;
list_for_each_safe(plugin, &system->plugins, link) {
/* We start processing *after* the sender plugin. sender_plugin
* is only set if we're queuing (not injecting) events from
* a plugin timer func
*/
if (delay) {
delay = plugin != sender_plugin;
continue;
}
/* The list of queued events for the *next* plugin */
struct list next_events = LIST_INIT(next_events);
@ -538,3 +568,118 @@ libinput_plugin_system_notify_evdev_frame(struct libinput_plugin_system *system,
}
libinput_plugin_system_drop_unregistered_plugins(system);
}
void
libinput_plugin_system_notify_evdev_frame(struct libinput_plugin_system *system,
struct libinput_device *device,
struct evdev_frame *frame)
{
plugin_system_notify_evdev_frame(system, device, frame, NULL);
}
static void
plugin_timer_func(uint64_t now, void *data)
{
struct libinput_plugin_timer *timer = data;
struct libinput_plugin *plugin = timer->plugin;
struct libinput *libinput = plugin->libinput;
if (!timer->func)
return;
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;
timer->func(plugin, now, timer->user_data);
plugin->event_queue.before = NULL;
plugin->event_queue.after = NULL;
list_chain(&before_events, &after_events);
struct plugin_queued_event *event;
list_for_each_safe(event, &before_events, link) {
plugin_system_notify_evdev_frame(&libinput->plugin_system,
event->device,
event->frame,
plugin);
plugin_queued_event_destroy(event);
}
}
struct libinput_plugin_timer *
libinput_plugin_timer_new(struct libinput_plugin *plugin,
const char *name,
void (*func)(struct libinput_plugin *plugin, uint64_t now, void *data),
void *data)
{
struct libinput_plugin_timer *timer = zalloc(sizeof(*timer));
_autofree_ char *timer_name = strdup_printf("%s-%s",
plugin->name,
name);
timer->plugin = plugin;
timer->refcount = 2; /* one for the caller, one for our list */
timer->func = func;
timer->user_data = data;
libinput_timer_init(&timer->timer,
plugin->libinput,
timer_name,
plugin_timer_func,
timer);
list_append(&plugin->timers, &timer->link);
return timer;
}
void
libinput_plugin_timer_set_user_data(struct libinput_plugin_timer *timer,
void *user_data)
{
timer->user_data = user_data;
}
void *
libinput_plugin_timer_get_user_data(struct libinput_plugin_timer *timer)
{
return timer->user_data;
}
struct libinput_plugin_timer *
libinput_plugin_timer_ref(struct libinput_plugin_timer *timer)
{
assert(timer->refcount > 0);
++timer->refcount;
return timer;
}
struct libinput_plugin_timer *
libinput_plugin_timer_unref(struct libinput_plugin_timer *timer)
{
assert(timer->refcount > 0);
if (--timer->refcount == 0) {
libinput_timer_cancel(&timer->timer);
libinput_timer_destroy(&timer->timer);
list_remove(&timer->link);
free(timer);
}
return NULL;
}
/* Set timer expire time, in absolute us CLOCK_MONOTONIC */
void
libinput_plugin_timer_set(struct libinput_plugin_timer *timer,
uint64_t expire)
{
libinput_timer_set(&timer->timer, expire);
}
void
libinput_plugin_timer_cancel(struct libinput_plugin_timer *timer)
{
libinput_timer_cancel(&timer->timer);
}

View file

@ -26,6 +26,7 @@
#include "config.h"
#include <stdbool.h>
#include <stdint.h>
#include <libudev.h>
#include <libevdev/libevdev.h>
@ -150,10 +151,10 @@ DEFINE_UNREF_CLEANUP_FUNC(libinput_plugin);
* 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, ...
* callback or within a plugin's timer func 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,
@ -174,7 +175,26 @@ DEFINE_UNREF_CLEANUP_FUNC(libinput_plugin);
* 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.
* evdev_frame() callback or a timer callback.
*
* If called within a plugin's timer callback, any frames generated by
* the plugin will only be seen by plugins after this plugin. These
* frames will be processed in the usual evdev_fame() callback and there
* is no indication that the events were queued from within a timer
* callback. Using the above example:
*
* - P1: <idle>
* - P2: timer callback invoked, appends Q1
* - P3: receives Q1, appends Q2, optionally modifies Q1
* - P4: receives Q2, optionally modifies Q2
* - P4: receives Q1, optionally modifies Q1
*
* Because there is no current frame during a timer callback
* libinput_plugin_append_evdev_frame() and
* libinput_plugin_prepend_evdev_frame() are functionally equivalent.
* If both functions are used, all events from
* libinput_plugin_prepend_evdev_frame() will be queued before
* events from libinput_plugin_append_evdev_frame().
*/
void
libinput_plugin_append_evdev_frame(struct libinput_plugin *libinput,
@ -192,3 +212,51 @@ void
libinput_plugin_prepend_evdev_frame(struct libinput_plugin *libinput,
struct libinput_device *device,
struct evdev_frame *frame);
/**
* Create a new timer for the given plugin.
*
* The timer needs to be set with libinput_plugin_timer_set()
* before it can be used.
*
* The refcount of the returned timer is at least 1, and the caller
* must call libinput_plugin_timer_unref() to release it.
*
* The func given is the callback invoked when the timer expires.
* It is passed the user_data given here or in a subsequent
* call to libinput_plugin_timer_set_user_data().
*
* Note that event generating inside a timer is subject to
* specific behaviors, see the documentation
* to libinput_plugin_append_evdev_frame(), libinput_plugin_prepend_evdev_frame()
* and libinput_plugin_inject_evdev_frame() for details.
*/
struct libinput_plugin_timer *
libinput_plugin_timer_new(struct libinput_plugin *plugin,
const char *name,
void (*func)(struct libinput_plugin *plugin, uint64_t now, void *user_data),
void *user_data);
struct libinput_plugin_timer *
libinput_plugin_timer_ref(struct libinput_plugin_timer *timer);
struct libinput_plugin_timer *
libinput_plugin_timer_unref(struct libinput_plugin_timer *timer);
#ifdef DEFINE_UNREF_CLEANUP_FUNC
DEFINE_UNREF_CLEANUP_FUNC(libinput_plugin_timer);
#endif
/* Set timer expire time, in absolute us CLOCK_MONOTONIC */
void
libinput_plugin_timer_set(struct libinput_plugin_timer *timer,
uint64_t expire);
void
libinput_plugin_timer_set_user_data(struct libinput_plugin_timer *timer,
void *user_data);
void *
libinput_plugin_timer_get_user_data(struct libinput_plugin_timer *timer);
void
libinput_plugin_timer_cancel(struct libinput_plugin_timer *timer);