mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2026-01-06 14:10:12 +01:00
evdev: Ignore key/button release events if key was never pressed
The kernel may send a 'release' event without ever having sent a key 'pressed' event in case the key was pressed before libinput was initiated. Ignore these events so that we always guarantee a release event always comes after a pressed event for any given key or button. Signed-off-by: Jonas Ådahl <jadahl@gmail.com>
This commit is contained in:
parent
100ba70e87
commit
c8017595fc
5 changed files with 131 additions and 7 deletions
39
src/evdev.c
39
src/evdev.c
|
|
@ -47,6 +47,18 @@ enum evdev_key_type {
|
|||
EVDEV_KEY_TYPE_BUTTON,
|
||||
};
|
||||
|
||||
static void
|
||||
set_key_down(struct evdev_device *device, int code, int pressed)
|
||||
{
|
||||
long_set_bit_state(device->key_mask, code, pressed);
|
||||
}
|
||||
|
||||
static int
|
||||
is_key_down(struct evdev_device *device, int code)
|
||||
{
|
||||
return long_bit_is_set(device->key_mask, code);
|
||||
}
|
||||
|
||||
void
|
||||
evdev_device_led_update(struct evdev_device *device, enum libinput_led leds)
|
||||
{
|
||||
|
|
@ -294,6 +306,8 @@ static inline void
|
|||
evdev_process_key(struct evdev_device *device,
|
||||
struct input_event *e, uint64_t time)
|
||||
{
|
||||
enum evdev_key_type type;
|
||||
|
||||
/* ignore kernel key repeat */
|
||||
if (e->value == 2)
|
||||
return;
|
||||
|
|
@ -306,7 +320,24 @@ evdev_process_key(struct evdev_device *device,
|
|||
|
||||
evdev_flush_pending_event(device, time);
|
||||
|
||||
switch (get_key_type(e->code)) {
|
||||
type = get_key_type(e->code);
|
||||
|
||||
/* Ignore key release events from the kernel for keys that libinput
|
||||
* never got a pressed event for. */
|
||||
if (e->value == 0) {
|
||||
switch (type) {
|
||||
case EVDEV_KEY_TYPE_NONE:
|
||||
break;
|
||||
case EVDEV_KEY_TYPE_KEY:
|
||||
case EVDEV_KEY_TYPE_BUTTON:
|
||||
if (!is_key_down(device, e->code))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
set_key_down(device, e->code, e->value);
|
||||
|
||||
switch (type) {
|
||||
case EVDEV_KEY_TYPE_NONE:
|
||||
break;
|
||||
case EVDEV_KEY_TYPE_KEY:
|
||||
|
|
@ -853,12 +884,8 @@ err:
|
|||
int
|
||||
evdev_device_get_keys(struct evdev_device *device, char *keys, size_t size)
|
||||
{
|
||||
int len;
|
||||
|
||||
memset(keys, 0, size);
|
||||
len = ioctl(device->fd, EVIOCGKEY(size), keys);
|
||||
|
||||
return (len == -1) ? -errno : len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *
|
||||
|
|
|
|||
|
|
@ -95,6 +95,10 @@ struct evdev_device {
|
|||
struct {
|
||||
struct motion_filter *filter;
|
||||
} pointer;
|
||||
|
||||
/* Bitmask of pressed keys used to ignore initial release events from
|
||||
* the kernel. */
|
||||
unsigned long key_mask[NLONGS(KEY_CNT)];
|
||||
};
|
||||
|
||||
#define EVDEV_UNHANDLED_DEVICE ((struct evdev_device *) 1)
|
||||
|
|
|
|||
|
|
@ -72,6 +72,8 @@ int list_empty(const struct list *list);
|
|||
pos = tmp, \
|
||||
tmp = container_of(pos->member.next, tmp, member))
|
||||
|
||||
#define LONG_BITS (sizeof(long) * 8)
|
||||
#define NLONGS(x) (((x) + LONG_BITS - 1) / LONG_BITS)
|
||||
#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0])
|
||||
#define ARRAY_FOR_EACH(_arr, _elem) \
|
||||
for (size_t _i = 0; (_elem = &_arr[_i]) && _i < ARRAY_LENGTH(_arr); _i++)
|
||||
|
|
@ -150,4 +152,31 @@ vector_get_direction(int dx, int dy)
|
|||
return dir;
|
||||
}
|
||||
|
||||
static inline int
|
||||
long_bit_is_set(const unsigned long *array, int bit)
|
||||
{
|
||||
return !!(array[bit / LONG_BITS] & (1LL << (bit % LONG_BITS)));
|
||||
}
|
||||
|
||||
static inline void
|
||||
long_set_bit(unsigned long *array, int bit)
|
||||
{
|
||||
array[bit / LONG_BITS] |= (1LL << (bit % LONG_BITS));
|
||||
}
|
||||
|
||||
static inline void
|
||||
long_clear_bit(unsigned long *array, int bit)
|
||||
{
|
||||
array[bit / LONG_BITS] &= ~(1LL << (bit % LONG_BITS));
|
||||
}
|
||||
|
||||
static inline void
|
||||
long_set_bit_state(unsigned long *array, int bit, int state)
|
||||
{
|
||||
if (state)
|
||||
long_set_bit(array, bit);
|
||||
else
|
||||
long_clear_bit(array, bit);
|
||||
}
|
||||
|
||||
#endif /* LIBINPUT_UTIL_H */
|
||||
|
|
|
|||
|
|
@ -1359,7 +1359,8 @@ libinput_device_led_update(struct libinput_device *device,
|
|||
*/
|
||||
int
|
||||
libinput_device_get_keys(struct libinput_device *device,
|
||||
char *keys, size_t size);
|
||||
char *keys, size_t size)
|
||||
LIBINPUT_ATTRIBUTE_DEPRECATED;
|
||||
|
||||
/**
|
||||
* @ingroup device
|
||||
|
|
|
|||
|
|
@ -112,10 +112,73 @@ START_TEST(keyboard_seat_key_count)
|
|||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(keyboard_ignore_no_pressed_release)
|
||||
{
|
||||
struct litest_device *dev;
|
||||
struct libinput *unused_libinput;
|
||||
struct libinput *libinput;
|
||||
struct libinput_event *event;
|
||||
struct libinput_event_keyboard *kevent;
|
||||
int events[] = {
|
||||
EV_KEY, KEY_A,
|
||||
-1, -1,
|
||||
};
|
||||
enum libinput_key_state *state;
|
||||
enum libinput_key_state expected_states[] = {
|
||||
LIBINPUT_KEY_STATE_PRESSED,
|
||||
LIBINPUT_KEY_STATE_RELEASED,
|
||||
};
|
||||
|
||||
/* We can't send pressed -> released -> pressed events using uinput
|
||||
* as such non-symmetric events are dropped. Work-around this by first
|
||||
* adding the test device to the tested context after having sent an
|
||||
* initial pressed event. */
|
||||
unused_libinput = litest_create_context();
|
||||
dev = litest_add_device_with_overrides(unused_libinput,
|
||||
LITEST_KEYBOARD,
|
||||
"Generic keyboard",
|
||||
NULL, NULL, events);
|
||||
|
||||
litest_keyboard_key(dev, KEY_A, true);
|
||||
litest_drain_events(unused_libinput);
|
||||
|
||||
libinput = litest_create_context();
|
||||
libinput_path_add_device(libinput,
|
||||
libevdev_uinput_get_devnode(dev->uinput));
|
||||
litest_drain_events(libinput);
|
||||
|
||||
litest_keyboard_key(dev, KEY_A, false);
|
||||
litest_keyboard_key(dev, KEY_A, true);
|
||||
litest_keyboard_key(dev, KEY_A, false);
|
||||
|
||||
libinput_dispatch(libinput);
|
||||
|
||||
ARRAY_FOR_EACH(expected_states, state) {
|
||||
event = libinput_get_event(libinput);
|
||||
ck_assert_notnull(event);
|
||||
ck_assert_int_eq(libinput_event_get_type(event),
|
||||
LIBINPUT_EVENT_KEYBOARD_KEY);
|
||||
kevent = libinput_event_get_keyboard_event(event);
|
||||
ck_assert_int_eq(libinput_event_keyboard_get_key(kevent),
|
||||
KEY_A);
|
||||
ck_assert_int_eq(libinput_event_keyboard_get_key_state(kevent),
|
||||
*state);
|
||||
libinput_event_destroy(event);
|
||||
libinput_dispatch(libinput);
|
||||
}
|
||||
|
||||
litest_assert_empty_queue(libinput);
|
||||
litest_delete_device(dev);
|
||||
libinput_unref(libinput);
|
||||
libinput_unref(unused_libinput);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
litest_add_no_device("keyboard:seat key count", keyboard_seat_key_count);
|
||||
litest_add_no_device("keyboard:key counting", keyboard_ignore_no_pressed_release);
|
||||
|
||||
return litest_run(argc, argv);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue