mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2026-02-03 13:30:27 +01:00
Add an evdev_frame wrapper struct
The kernel only ever gives us a frame of events in one go (it flushes on SYN_REPORT). We then need to look at that frame as a single state change (keyboards excepted for historical reasons) so let's push this into a proper struct we can pass around. Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1215>
This commit is contained in:
parent
548279abee
commit
9f00f77bc4
3 changed files with 401 additions and 0 deletions
251
src/evdev-frame.h
Normal file
251
src/evdev-frame.h
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
|
||||
/*
|
||||
* Copyright © 2025 Red Hat, Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "util-mem.h"
|
||||
#include "util-input-event.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <linux/input.h>
|
||||
|
||||
/**
|
||||
* A wrapper around a SYN_REPORT-terminated set of input events.
|
||||
*
|
||||
* This struct always has a count of >= 1 (the SYN_REPORT)
|
||||
* and the timestamp of the SYN_REPORT is always that of the
|
||||
* most recently appended event (if nonzero)
|
||||
*
|
||||
* The event frame is of a fixed size given in
|
||||
* evdev_frame_new() and cannot be resized via helpers.
|
||||
*
|
||||
* The struct should be considered opaque, use the helpers
|
||||
* to access the various fields.
|
||||
*/
|
||||
struct evdev_frame {
|
||||
int refcount;
|
||||
size_t max_size;
|
||||
size_t count;
|
||||
struct input_event events[];
|
||||
};
|
||||
|
||||
static inline struct evdev_frame *
|
||||
evdev_frame_ref(struct evdev_frame *frame)
|
||||
{
|
||||
assert(frame->refcount > 0);
|
||||
++frame->refcount;
|
||||
return frame;
|
||||
}
|
||||
|
||||
static inline struct evdev_frame *
|
||||
evdev_frame_unref(struct evdev_frame *frame)
|
||||
{
|
||||
if (frame) {
|
||||
assert(frame->refcount > 0);
|
||||
if (--frame->refcount == 0) {
|
||||
frame->max_size = 0;
|
||||
frame->count = 0;
|
||||
free(frame);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DEFINE_UNREF_CLEANUP_FUNC(evdev_frame);
|
||||
|
||||
static inline bool
|
||||
evdev_frame_is_empty(const struct evdev_frame *frame)
|
||||
{
|
||||
return frame->count == 1;
|
||||
}
|
||||
|
||||
static inline size_t
|
||||
evdev_frame_get_count(const struct evdev_frame *frame)
|
||||
{
|
||||
return frame->count;
|
||||
}
|
||||
|
||||
static inline struct input_event *
|
||||
evdev_frame_get_events(struct evdev_frame *frame, size_t *nevents)
|
||||
{
|
||||
if (nevents)
|
||||
*nevents = frame->count;
|
||||
|
||||
return frame->events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the timestamp for all events in this event frame.
|
||||
*/
|
||||
static inline void
|
||||
evdev_frame_set_time(struct evdev_frame *frame, uint64_t time)
|
||||
{
|
||||
assert(frame->count > 0);
|
||||
|
||||
for (size_t i = 0; i < frame->count; i++)
|
||||
input_event_set_time(&frame->events[i], time);
|
||||
}
|
||||
|
||||
static inline uint64_t
|
||||
evdev_frame_get_time(const struct evdev_frame *frame)
|
||||
{
|
||||
assert(frame->count > 0);
|
||||
|
||||
return input_event_time(&frame->events[frame->count - 1]);
|
||||
}
|
||||
|
||||
static inline int
|
||||
evdev_frame_reset(struct evdev_frame *frame)
|
||||
{
|
||||
memset(frame->events, 0, frame->max_size * sizeof(struct input_event));
|
||||
frame->count = 1; /* SYN_REPORT is always there */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline struct evdev_frame *
|
||||
evdev_frame_new(size_t max_size)
|
||||
{
|
||||
struct evdev_frame *frame = zalloc(max_size * sizeof(struct input_event) + sizeof(*frame));
|
||||
|
||||
frame->refcount = 1;
|
||||
frame->max_size = max_size;
|
||||
frame->count = 1; /* SYN_REPORT is always there */
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
static inline struct evdev_frame *
|
||||
evdev_frame_new_on_stack(size_t max_size)
|
||||
{
|
||||
assert(max_size <= 64);
|
||||
struct evdev_frame *frame = alloca(max_size * sizeof(struct input_event) + sizeof(*frame));
|
||||
|
||||
frame->refcount = 1;
|
||||
frame->max_size = max_size;
|
||||
frame->count = 1; /* SYN_REPORT is always there */
|
||||
memset(frame->events, 0, max_size * sizeof(struct input_event));
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append events to the event frame. nevents must be larger than 0
|
||||
* and specifies the number of elements in events. If any events in
|
||||
* the given events is a EV_SYN/SYN_REPORT event, that event is the last
|
||||
* one appended even if nevents states a higher number of events (roughly
|
||||
* equivalent to having a \0 inside a string).
|
||||
*
|
||||
* This function guarantees the frame is terminated with a SYN_REPORT event.
|
||||
* Appending SYN_REPORTS to a frame does not increase the count of events in the
|
||||
* frame - the new SYN_REPORT will simply replace the existing SYN_REPORT.
|
||||
*
|
||||
* The timestamp of the SYN_REPORT (if any) is used for this event
|
||||
* frame. If the appended sequence does not contain a SYN_REPORT, the highest
|
||||
* timestamp of any event appended is used. This timestamp will overwrite the
|
||||
* frame's timestamp even if the timestamp of the frame is higher.
|
||||
*
|
||||
* If all to-be-appended events (including the SYN_REPORT) have a timestamp of
|
||||
* 0, the existing frame's timestamp is left as-is.
|
||||
*
|
||||
* The caller SHOULD terminate the events with a SYN_REPORT event with a
|
||||
* valid timestamp to ensure correct behavior.
|
||||
*
|
||||
* Returns 0 on success, or a negative errno on failure
|
||||
*/
|
||||
static inline int
|
||||
evdev_frame_append(struct evdev_frame *frame,
|
||||
const struct input_event *events,
|
||||
size_t nevents)
|
||||
{
|
||||
assert(nevents > 0);
|
||||
|
||||
uint64_t time = 0;
|
||||
|
||||
for (size_t i = 0; i < nevents; i++) {
|
||||
if (events[i].type == EV_SYN && events[i].code == SYN_REPORT) {
|
||||
nevents = i;
|
||||
time = input_event_time(&events[i]);
|
||||
break;
|
||||
}
|
||||
time = max(time, input_event_time(&events[i]));
|
||||
}
|
||||
|
||||
if (nevents > 0) {
|
||||
if (frame->count + nevents > frame->max_size)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(frame->events + frame->count - 1, events, nevents * sizeof(struct input_event));
|
||||
frame->count += nevents;
|
||||
}
|
||||
if (time)
|
||||
evdev_frame_set_time(frame, time);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Behaves like evdev_frame_append() but resets the frame before appending.
|
||||
*
|
||||
* On error the frame is left as-is.
|
||||
*
|
||||
* Returns 0 on success, or a negative errno on failure
|
||||
*/
|
||||
static inline int
|
||||
evdev_frame_set(struct evdev_frame *frame,
|
||||
const struct input_event *events,
|
||||
size_t nevents)
|
||||
{
|
||||
assert(nevents > 0);
|
||||
|
||||
size_t count = nevents;
|
||||
|
||||
for (size_t i = 0; i < nevents; i++) {
|
||||
if (events[i].type == EV_SYN && events[i].code == SYN_REPORT) {
|
||||
count = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > frame->max_size - 1)
|
||||
return -ENOMEM;
|
||||
|
||||
evdev_frame_reset(frame);
|
||||
return evdev_frame_append(frame, events, nevents);
|
||||
}
|
||||
|
||||
static inline struct evdev_frame *
|
||||
evdev_frame_clone(struct evdev_frame *frame)
|
||||
{
|
||||
size_t nevents;
|
||||
struct input_event *events = evdev_frame_get_events(frame, &nevents);
|
||||
struct evdev_frame *clone = evdev_frame_new(nevents);
|
||||
|
||||
evdev_frame_append(clone, events, nevents);
|
||||
evdev_frame_set_time(clone, evdev_frame_get_time(frame));
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
|
@ -26,6 +26,9 @@
|
|||
#include "config.h"
|
||||
|
||||
#include "util-time.h"
|
||||
#include "util-mem.h"
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <linux/input.h>
|
||||
|
||||
static inline struct input_event
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
#include "litest.h"
|
||||
#include "litest-runner.h"
|
||||
#include "evdev-frame.h"
|
||||
#include "util-files.h"
|
||||
#include "util-list.h"
|
||||
#include "util-strings.h"
|
||||
|
|
@ -2559,6 +2560,150 @@ START_TEST(macros_expand)
|
|||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(evdev_frames)
|
||||
{
|
||||
{
|
||||
evdev_frame_unref(NULL); /* unref on NULL is permitted */
|
||||
}
|
||||
{
|
||||
_unref_(evdev_frame) *frame = evdev_frame_new(3);
|
||||
litest_assert_int_eq(evdev_frame_get_count(frame), 1U); /* SYN_REPORT */
|
||||
|
||||
litest_assert_ptr_eq(evdev_frame_ref(frame), frame);
|
||||
litest_assert_ptr_eq(evdev_frame_unref(frame), NULL);
|
||||
}
|
||||
{
|
||||
_unref_(evdev_frame) *frame = evdev_frame_new(3);
|
||||
struct input_event toobig[] = {
|
||||
{ .type = EV_ABS, .code = ABS_X, .value = 1, },
|
||||
{ .type = EV_ABS, .code = ABS_Y, .value = 2, },
|
||||
{ .type = EV_ABS, .code = ABS_Z, .value = 3, },
|
||||
{ .type = EV_SYN, .code = SYN_REPORT, .value = 0, },
|
||||
};
|
||||
|
||||
int rc = evdev_frame_set(frame, toobig, ARRAY_LENGTH(toobig));
|
||||
litest_assert_int_eq(rc, -ENOMEM);
|
||||
}
|
||||
{
|
||||
struct input_event events[] = {
|
||||
{ .type = EV_ABS, .code = ABS_X, .value = 1, },
|
||||
{ .type = EV_ABS, .code = ABS_Y, .value = 2, },
|
||||
{ .type = EV_SYN, .code = SYN_REPORT, .value = 0, },
|
||||
};
|
||||
|
||||
_unref_(evdev_frame) *frame = evdev_frame_new(3);
|
||||
int rc = evdev_frame_set(frame, events, ARRAY_LENGTH(events));
|
||||
litest_assert_neg_errno_success(rc);
|
||||
litest_assert_int_eq(evdev_frame_get_count(frame), ARRAY_LENGTH(events));
|
||||
litest_assert_int_eq(frame->max_size, ARRAY_LENGTH(events));
|
||||
|
||||
size_t nevents;
|
||||
rc = memcmp(evdev_frame_get_events(frame, &nevents), events, sizeof(events));
|
||||
litest_assert_int_eq(rc, 0);
|
||||
litest_assert_int_eq(nevents, ARRAY_LENGTH(events));
|
||||
|
||||
/* Already full, can't append */
|
||||
rc = evdev_frame_append(frame, events, 1);
|
||||
litest_assert_int_eq(rc, -ENOMEM);
|
||||
}
|
||||
{
|
||||
struct input_event events[] = {
|
||||
{ .type = EV_ABS, .code = ABS_X, .value = 1, },
|
||||
{ .type = EV_ABS, .code = ABS_Y, .value = 2, },
|
||||
{ .type = EV_SYN, .code = SYN_REPORT, .value = 0, },
|
||||
};
|
||||
|
||||
_unref_(evdev_frame) *frame = evdev_frame_new(3);
|
||||
int rc = evdev_frame_set(frame, events, 1);
|
||||
litest_assert_neg_errno_success(rc);
|
||||
litest_assert_int_eq(evdev_frame_get_count(frame), 2U); /* we appended SYN_REPORT */
|
||||
rc = evdev_frame_append(frame, events + 1, 1);
|
||||
litest_assert_neg_errno_success(rc);
|
||||
litest_assert_int_eq(evdev_frame_get_count(frame), 3U); /* we appended SYN_REPORT */
|
||||
rc = evdev_frame_append(frame, events + 2, 1);
|
||||
litest_assert_neg_errno_success(rc);
|
||||
litest_assert_int_eq(evdev_frame_get_count(frame), 3U); /* SYN_REPORT already there */
|
||||
}
|
||||
{
|
||||
struct input_event interrupted[] = {
|
||||
{ .type = EV_ABS, .code = ABS_X, .value = 1, },
|
||||
{ .type = EV_ABS, .code = ABS_Y, .value = 2, },
|
||||
{ .type = EV_SYN, .code = SYN_REPORT, .value = 0, },
|
||||
{ .type = EV_ABS, .code = ABS_RX, .value = 1, },
|
||||
{ .type = EV_ABS, .code = ABS_RY, .value = 2, },
|
||||
{ .type = EV_SYN, .code = SYN_REPORT, .value = 0, },
|
||||
};
|
||||
|
||||
_unref_(evdev_frame) *frame = evdev_frame_new(5);
|
||||
int rc = evdev_frame_set(frame, interrupted, ARRAY_LENGTH(interrupted));
|
||||
litest_assert_neg_errno_success(rc);
|
||||
litest_assert_int_eq(evdev_frame_get_count(frame), 3U);
|
||||
|
||||
rc = evdev_frame_set(frame, &interrupted[2], 1);
|
||||
litest_assert_neg_errno_success(rc);
|
||||
litest_assert_int_eq(evdev_frame_get_count(frame), 1U);
|
||||
|
||||
rc = evdev_frame_set(frame, &interrupted[1], ARRAY_LENGTH(interrupted) - 1);
|
||||
litest_assert_neg_errno_success(rc);
|
||||
litest_assert_int_eq(evdev_frame_get_count(frame), 2U);
|
||||
|
||||
/* We never appended a timestamp */
|
||||
litest_assert_int_eq(evdev_frame_get_time(frame), 0U);
|
||||
}
|
||||
{
|
||||
struct input_event e = {
|
||||
.type = EV_ABS,
|
||||
.code = ABS_X,
|
||||
.value = 1,
|
||||
.input_event_sec = 1234,
|
||||
.input_event_usec = 567,
|
||||
|
||||
};
|
||||
|
||||
_unref_(evdev_frame) *frame = evdev_frame_new(3);
|
||||
litest_assert_int_eq(evdev_frame_get_time(frame), 0U);
|
||||
|
||||
evdev_frame_append(frame, &e, 1);
|
||||
litest_assert_int_eq(evdev_frame_get_time(frame), 1234000567U);
|
||||
evdev_frame_append(frame, &e, 1);
|
||||
litest_assert_int_eq(evdev_frame_get_time(frame), 1234000567U);
|
||||
|
||||
struct input_event syn = {
|
||||
.type = EV_SYN,
|
||||
.code = SYN_REPORT,
|
||||
.value = 0,
|
||||
.input_event_sec = 111,
|
||||
.input_event_usec = 333,
|
||||
|
||||
};
|
||||
|
||||
litest_assert_neg_errno_success(evdev_frame_append(frame, &syn, 1));
|
||||
litest_assert_int_eq(evdev_frame_get_time(frame), 111000333U);
|
||||
|
||||
/* SYN_REPORT overwrites lower timestamp */
|
||||
syn.input_event_usec = 111;
|
||||
litest_assert_neg_errno_success(evdev_frame_append(frame, &syn, 1));
|
||||
litest_assert_int_eq(evdev_frame_get_time(frame), 111000111U);
|
||||
}
|
||||
{
|
||||
/* Expect highest timestamp */
|
||||
_unref_(evdev_frame) *frame = evdev_frame_new(4);
|
||||
struct input_event mixed_times[] = {
|
||||
{ .type = EV_ABS, .code = ABS_X, .value = 1, .input_event_sec = 12, .input_event_usec = 700, },
|
||||
{ .type = EV_ABS, .code = ABS_Y, .value = 2, .input_event_sec = 56, .input_event_usec = 800, },
|
||||
{ .type = EV_ABS, .code = ABS_Z, .value = 3, .input_event_sec = 34, .input_event_usec = 900, },
|
||||
{ .type = EV_SYN, .code = SYN_REPORT, .value = 0, .input_event_sec = 0, .input_event_usec = 1, },
|
||||
};
|
||||
evdev_frame_set(frame, mixed_times, 3);
|
||||
litest_assert_int_eq(evdev_frame_get_time(frame), 56000800U);
|
||||
|
||||
/* but SYN_REPORT overwrites any other timestamp */
|
||||
evdev_frame_set(frame, mixed_times, 4);
|
||||
litest_assert_int_eq(evdev_frame_get_time(frame), 1U);
|
||||
}
|
||||
}
|
||||
END_TEST
|
||||
|
||||
int main(void)
|
||||
{
|
||||
struct litest_runner *runner = litest_runner_new();
|
||||
|
|
@ -2640,6 +2785,8 @@ int main(void)
|
|||
ADD_TEST(attribute_cleanup);
|
||||
ADD_TEST(macros_expand);
|
||||
|
||||
ADD_TEST(evdev_frames);
|
||||
|
||||
enum litest_runner_result result = litest_runner_run_tests(runner);
|
||||
litest_runner_destroy(runner);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue