util: add a helper struct for an evdev mask

Allows to keep track of a set of evdev masks, or at least the subset we
care about.

The EV_KEY range is split into two masks to save some memory. The future
use of this is for the plugins to use those masks and some of those will
set BTN_TOOL_PEN and friends. This would immediately create an 81 byte
mask of zeroes just to keep that one bit.

Splitting it into a key/btn mask with the latter starting at BTN_MISC
means we duplicate the infmask struct (2x16 bytes) but instead only use
8 bytes for the mask itself to keep the BTN_TOOL_PEN bits.

Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1271>
This commit is contained in:
Peter Hutterer 2025-07-14 14:33:31 +10:00
parent d45f4493f1
commit f04b276ac1
2 changed files with 166 additions and 0 deletions

View file

@ -29,6 +29,7 @@
#include <linux/input.h>
#include <stdbool.h>
#include "util-bits.h"
#include "util-input-event.h"
#include "util-mem.h"
#include "util-newtype.h"
@ -652,3 +653,121 @@ evdev_frame_clone(struct evdev_frame *frame)
return clone;
}
struct evdev_mask {
bitmask_t ev;
bitmask_t rel;
bitmask_t sw;
infmask_t key; /* < BTN_MISC */
infmask_t btn; /* >= BTN_MISC */
infmask_t abs;
};
static_assert(sizeof(bitmask_t) * 8 >= EV_MAX, "bitmask size too small");
static_assert(sizeof(bitmask_t) * 8 >= EV_REL, "bitmask size too small");
static_assert(sizeof(bitmask_t) * 8 >= EV_SW, "bitmask size too small");
static inline void
evdev_mask_reset(struct evdev_mask *mask)
{
mask->ev = bitmask_new();
mask->rel = bitmask_new();
mask->sw = bitmask_new();
infmask_reset(&mask->key);
infmask_reset(&mask->btn);
infmask_reset(&mask->abs);
}
static inline struct evdev_mask *
evdev_mask_new(void)
{
struct evdev_mask *mask = zalloc(sizeof(*mask));
evdev_mask_reset(mask);
return mask;
}
static inline void
evdev_mask_destroy(struct evdev_mask *mask)
{
if (mask) {
evdev_mask_reset(mask);
free(mask);
}
}
DEFINE_DESTROY_CLEANUP_FUNC(evdev_mask);
static inline void
evdev_mask_set_usage(struct evdev_mask *mask, evdev_usage_t usage)
{
unsigned int type = evdev_usage_type(usage);
unsigned int code = evdev_usage_code(usage);
if (type >= EV_MAX)
return;
bitmask_set_bit(&mask->ev, type);
switch (type) {
case EV_ABS:
if (code <= ABS_MAX)
infmask_set_bit(&mask->abs, code);
break;
case EV_KEY:
if (code < BTN_MISC)
infmask_set_bit(&mask->key, code);
else if (code <= KEY_MAX)
infmask_set_bit(&mask->btn, code - BTN_MISC);
break;
case EV_REL:
if (code <= REL_MAX)
bitmask_set_bit(&mask->rel, code);
break;
case EV_SW:
if (code <= SW_MAX)
bitmask_set_bit(&mask->sw, code);
break;
}
}
static inline void
evdev_mask_set_enum(struct evdev_mask *mask, enum evdev_usage usage)
{
evdev_mask_set_usage(mask, evdev_usage_from(usage));
}
static inline bool
evdev_mask_is_set(const struct evdev_mask *mask, evdev_usage_t usage)
{
unsigned int type = evdev_usage_type(usage);
unsigned int code = evdev_usage_code(usage);
if (type >= EV_MAX)
return false;
if (!bitmask_bit_is_set(mask->ev, type))
return false;
bool isset = false;
switch (type) {
case EV_ABS:
isset = infmask_bit_is_set(&mask->abs, code);
break;
case EV_KEY:
if (code < BTN_MISC)
isset = infmask_bit_is_set(&mask->key, code);
else
isset = infmask_bit_is_set(&mask->btn, code - BTN_MISC);
break;
case EV_REL:
isset = bitmask_bit_is_set(mask->rel, code);
break;
case EV_SW:
isset = bitmask_bit_is_set(mask->sw, code);
break;
default:
break;
}
return isset;
}

View file

@ -3150,6 +3150,51 @@ START_TEST(infmask_test)
}
END_TEST
START_TEST(evdev_mask_test)
{
_destroy_(evdev_mask) *mask = evdev_mask_new();
evdev_mask_reset(mask);
litest_assert(bitmask_is_empty(mask->ev));
litest_assert(bitmask_is_empty(mask->rel));
litest_assert(bitmask_is_empty(mask->sw));
litest_assert(infmask_is_empty(&mask->key));
litest_assert(infmask_is_empty(&mask->btn));
litest_assert(infmask_is_empty(&mask->abs));
evdev_mask_set_enum(mask, EVDEV_BTN_TOOL_PEN);
evdev_mask_set_enum(mask, EVDEV_BTN_TOOL_AIRBRUSH);
litest_assert(bitmask_bit_is_set(mask->ev, EV_KEY));
/* Verify these are in btn, not key */
litest_assert(!infmask_is_empty(&mask->btn));
litest_assert(infmask_is_empty(&mask->key));
litest_assert(evdev_mask_is_set(mask, evdev_usage_from(EVDEV_BTN_TOOL_PEN)));
litest_assert(
!evdev_mask_is_set(mask, evdev_usage_from(EVDEV_BTN_TOOL_RUBBER)));
litest_assert(
evdev_mask_is_set(mask, evdev_usage_from(EVDEV_BTN_TOOL_AIRBRUSH)));
/* Test regular key (should go into key field) */
evdev_mask_set_enum(mask, EVDEV_KEY_ESC);
litest_assert(!infmask_is_empty(&mask->key));
litest_assert(evdev_mask_is_set(mask, evdev_usage_from(EVDEV_KEY_ESC)));
evdev_mask_set_enum(mask, EVDEV_REL_X);
litest_assert(bitmask_bit_is_set(mask->ev, EV_REL));
litest_assert(bitmask_bit_is_set(mask->rel, REL_X));
litest_assert(evdev_mask_is_set(mask, evdev_usage_from(EVDEV_REL_X)));
evdev_mask_set_enum(mask, EVDEV_ABS_X);
litest_assert(bitmask_bit_is_set(mask->ev, EV_ABS));
litest_assert(!infmask_is_empty(&mask->abs));
litest_assert(evdev_mask_is_set(mask, evdev_usage_from(EVDEV_ABS_X)));
}
END_TEST
int
main(void)
{
@ -3238,6 +3283,8 @@ main(void)
ADD_TEST(infmask_test);
ADD_TEST(evdev_mask_test);
enum litest_runner_result result = litest_runner_run_tests(runner);
litest_runner_destroy(runner);