mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2025-12-26 19:10:06 +01:00
evdev: Release still pressed keys/buttons when removing device
When removing a device, its not guaranteed that all button or key presses have been released, resulting in an invalid seat wide button count. Note that kernel devices normally will send release events when being unplugged, but this won't happen when removing a device from the path backend. Signed-off-by: Jonas Ådahl <jadahl@gmail.com> Reviewed-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
parent
3a3d70a3a8
commit
8f846a41fa
3 changed files with 254 additions and 0 deletions
47
src/evdev.c
47
src/evdev.c
|
|
@ -59,6 +59,12 @@ is_key_down(struct evdev_device *device, int code)
|
|||
return long_bit_is_set(device->key_mask, code);
|
||||
}
|
||||
|
||||
static int
|
||||
get_key_down_count(struct evdev_device *device, int code)
|
||||
{
|
||||
return device->key_count[code];
|
||||
}
|
||||
|
||||
static int
|
||||
update_key_down_count(struct evdev_device *device, int code, int pressed)
|
||||
{
|
||||
|
|
@ -1013,6 +1019,45 @@ evdev_device_get_size(struct evdev_device *device,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
release_pressed_keys(struct evdev_device *device)
|
||||
{
|
||||
struct libinput *libinput = device->base.seat->libinput;
|
||||
struct timespec ts;
|
||||
uint64_t time;
|
||||
int code;
|
||||
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
|
||||
log_bug_libinput(libinput, "clock_gettime: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
time = ts.tv_sec * 1000ULL + ts.tv_nsec / 1000000;
|
||||
|
||||
for (code = 0; code < KEY_CNT; code++) {
|
||||
if (get_key_down_count(device, code) > 0) {
|
||||
switch (get_key_type(code)) {
|
||||
case EVDEV_KEY_TYPE_NONE:
|
||||
break;
|
||||
case EVDEV_KEY_TYPE_KEY:
|
||||
keyboard_notify_key(
|
||||
&device->base,
|
||||
time,
|
||||
code,
|
||||
LIBINPUT_KEY_STATE_RELEASED);
|
||||
break;
|
||||
case EVDEV_KEY_TYPE_BUTTON:
|
||||
pointer_notify_button(
|
||||
&device->base,
|
||||
time,
|
||||
code,
|
||||
LIBINPUT_BUTTON_STATE_RELEASED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
evdev_device_remove(struct evdev_device *device)
|
||||
{
|
||||
|
|
@ -1020,6 +1065,8 @@ evdev_device_remove(struct evdev_device *device)
|
|||
libinput_remove_source(device->base.seat->libinput,
|
||||
device->source);
|
||||
|
||||
release_pressed_keys(device);
|
||||
|
||||
if (device->mtdev)
|
||||
mtdev_close_delete(device->mtdev);
|
||||
close_restricted(device->base.seat->libinput, device->fd);
|
||||
|
|
|
|||
117
test/keyboard.c
117
test/keyboard.c
|
|
@ -25,6 +25,7 @@
|
|||
#include <check.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "libinput-util.h"
|
||||
#include "litest.h"
|
||||
|
||||
START_TEST(keyboard_seat_key_count)
|
||||
|
|
@ -174,11 +175,127 @@ START_TEST(keyboard_ignore_no_pressed_release)
|
|||
}
|
||||
END_TEST
|
||||
|
||||
static void
|
||||
test_key_event(struct litest_device *dev, unsigned int key, int state)
|
||||
{
|
||||
struct libinput *li = dev->libinput;
|
||||
struct libinput_event *event;
|
||||
struct libinput_event_keyboard *kevent;
|
||||
|
||||
litest_event(dev, EV_KEY, key, state);
|
||||
litest_event(dev, EV_SYN, SYN_REPORT, 0);
|
||||
|
||||
libinput_dispatch(li);
|
||||
|
||||
event = libinput_get_event(li);
|
||||
ck_assert(event != NULL);
|
||||
ck_assert_int_eq(libinput_event_get_type(event), LIBINPUT_EVENT_KEYBOARD_KEY);
|
||||
|
||||
kevent = libinput_event_get_keyboard_event(event);
|
||||
ck_assert(kevent != NULL);
|
||||
ck_assert_int_eq(libinput_event_keyboard_get_key(kevent), key);
|
||||
ck_assert_int_eq(libinput_event_keyboard_get_key_state(kevent),
|
||||
state ? LIBINPUT_KEY_STATE_PRESSED :
|
||||
LIBINPUT_KEY_STATE_RELEASED);
|
||||
libinput_event_destroy(event);
|
||||
}
|
||||
|
||||
START_TEST(keyboard_key_auto_release)
|
||||
{
|
||||
struct libinput *libinput;
|
||||
struct litest_device *dev;
|
||||
struct libinput_event *event;
|
||||
enum libinput_event_type type;
|
||||
struct libinput_event_keyboard *kevent;
|
||||
struct {
|
||||
int code;
|
||||
int released;
|
||||
} keys[] = {
|
||||
{ .code = KEY_A, },
|
||||
{ .code = KEY_S, },
|
||||
{ .code = KEY_D, },
|
||||
{ .code = KEY_G, },
|
||||
{ .code = KEY_Z, },
|
||||
{ .code = KEY_DELETE, },
|
||||
{ .code = KEY_F24, },
|
||||
};
|
||||
int events[2 * (ARRAY_LENGTH(keys) + 1)];
|
||||
unsigned i;
|
||||
int key;
|
||||
int valid_code;
|
||||
|
||||
/* Enable all tested keys on the device */
|
||||
i = 0;
|
||||
while (i < 2 * ARRAY_LENGTH(keys)) {
|
||||
key = keys[i / 2].code;
|
||||
events[i++] = EV_KEY;
|
||||
events[i++] = key;
|
||||
}
|
||||
events[i++] = -1;
|
||||
events[i++] = -1;
|
||||
|
||||
libinput = litest_create_context();
|
||||
dev = litest_add_device_with_overrides(libinput,
|
||||
LITEST_KEYBOARD,
|
||||
"Generic keyboard",
|
||||
NULL, NULL, events);
|
||||
|
||||
litest_drain_events(libinput);
|
||||
|
||||
/* Send pressed events, without releasing */
|
||||
for (i = 0; i < ARRAY_LENGTH(keys); ++i) {
|
||||
test_key_event(dev, keys[i].code, 1);
|
||||
}
|
||||
|
||||
litest_drain_events(libinput);
|
||||
|
||||
/* "Disconnect" device */
|
||||
litest_delete_device(dev);
|
||||
|
||||
/* Mark all released keys until device is removed */
|
||||
while (1) {
|
||||
event = libinput_get_event(libinput);
|
||||
ck_assert_notnull(event);
|
||||
type = libinput_event_get_type(event);
|
||||
|
||||
if (type == LIBINPUT_EVENT_DEVICE_REMOVED) {
|
||||
libinput_event_destroy(event);
|
||||
break;
|
||||
}
|
||||
|
||||
ck_assert_int_eq(type, LIBINPUT_EVENT_KEYBOARD_KEY);
|
||||
kevent = libinput_event_get_keyboard_event(event);
|
||||
ck_assert_int_eq(libinput_event_keyboard_get_key_state(kevent),
|
||||
LIBINPUT_KEY_STATE_RELEASED);
|
||||
key = libinput_event_keyboard_get_key(kevent);
|
||||
|
||||
valid_code = 0;
|
||||
for (i = 0; i < ARRAY_LENGTH(keys); ++i) {
|
||||
if (keys[i].code == key) {
|
||||
ck_assert_int_eq(keys[i].released, 0);
|
||||
keys[i].released = 1;
|
||||
valid_code = 1;
|
||||
}
|
||||
}
|
||||
ck_assert_int_eq(valid_code, 1);
|
||||
libinput_event_destroy(event);
|
||||
}
|
||||
|
||||
/* Check that all pressed keys has been released. */
|
||||
for (i = 0; i < ARRAY_LENGTH(keys); ++i) {
|
||||
ck_assert_int_eq(keys[i].released, 1);
|
||||
}
|
||||
|
||||
libinput_unref(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);
|
||||
litest_add_no_device("keyboard:key counting", keyboard_key_auto_release);
|
||||
|
||||
return litest_run(argc, argv);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -152,6 +152,95 @@ START_TEST(pointer_button)
|
|||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(pointer_button_auto_release)
|
||||
{
|
||||
struct libinput *libinput;
|
||||
struct litest_device *dev;
|
||||
struct libinput_event *event;
|
||||
enum libinput_event_type type;
|
||||
struct libinput_event_pointer *pevent;
|
||||
struct {
|
||||
int code;
|
||||
int released;
|
||||
} buttons[] = {
|
||||
{ .code = BTN_LEFT, },
|
||||
{ .code = BTN_MIDDLE, },
|
||||
{ .code = BTN_EXTRA, },
|
||||
{ .code = BTN_SIDE, },
|
||||
{ .code = BTN_BACK, },
|
||||
{ .code = BTN_FORWARD, },
|
||||
{ .code = BTN_4, },
|
||||
};
|
||||
int events[2 * (ARRAY_LENGTH(buttons) + 1)];
|
||||
unsigned i;
|
||||
int button;
|
||||
int valid_code;
|
||||
|
||||
/* Enable all tested buttons on the device */
|
||||
for (i = 0; i < 2 * ARRAY_LENGTH(buttons);) {
|
||||
button = buttons[i / 2].code;
|
||||
events[i++] = EV_KEY;
|
||||
events[i++] = button;
|
||||
}
|
||||
events[i++] = -1;
|
||||
events[i++] = -1;
|
||||
|
||||
libinput = litest_create_context();
|
||||
dev = litest_add_device_with_overrides(libinput,
|
||||
LITEST_MOUSE,
|
||||
"Generic mouse",
|
||||
NULL, NULL, events);
|
||||
|
||||
litest_drain_events(libinput);
|
||||
|
||||
/* Send pressed events, without releasing */
|
||||
for (i = 0; i < ARRAY_LENGTH(buttons); ++i) {
|
||||
test_button_event(dev, buttons[i].code, 1);
|
||||
}
|
||||
|
||||
litest_drain_events(libinput);
|
||||
|
||||
/* "Disconnect" device */
|
||||
litest_delete_device(dev);
|
||||
|
||||
/* Mark all released buttons until device is removed */
|
||||
while (1) {
|
||||
event = libinput_get_event(libinput);
|
||||
ck_assert_notnull(event);
|
||||
type = libinput_event_get_type(event);
|
||||
|
||||
if (type == LIBINPUT_EVENT_DEVICE_REMOVED) {
|
||||
libinput_event_destroy(event);
|
||||
break;
|
||||
}
|
||||
|
||||
ck_assert_int_eq(type, LIBINPUT_EVENT_POINTER_BUTTON);
|
||||
pevent = libinput_event_get_pointer_event(event);
|
||||
ck_assert_int_eq(libinput_event_pointer_get_button_state(pevent),
|
||||
LIBINPUT_BUTTON_STATE_RELEASED);
|
||||
button = libinput_event_pointer_get_button(pevent);
|
||||
|
||||
valid_code = 0;
|
||||
for (i = 0; i < ARRAY_LENGTH(buttons); ++i) {
|
||||
if (buttons[i].code == button) {
|
||||
ck_assert_int_eq(buttons[i].released, 0);
|
||||
buttons[i].released = 1;
|
||||
valid_code = 1;
|
||||
}
|
||||
}
|
||||
ck_assert_int_eq(valid_code, 1);
|
||||
libinput_event_destroy(event);
|
||||
}
|
||||
|
||||
/* Check that all pressed buttons has been released. */
|
||||
for (i = 0; i < ARRAY_LENGTH(buttons); ++i) {
|
||||
ck_assert_int_eq(buttons[i].released, 1);
|
||||
}
|
||||
|
||||
libinput_unref(libinput);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
static void
|
||||
test_wheel_event(struct litest_device *dev, int which, int amount)
|
||||
{
|
||||
|
|
@ -300,6 +389,7 @@ int main (int argc, char **argv) {
|
|||
|
||||
litest_add("pointer:motion", pointer_motion_relative, LITEST_POINTER, LITEST_ANY);
|
||||
litest_add("pointer:button", pointer_button, LITEST_BUTTON, LITEST_CLICKPAD);
|
||||
litest_add_no_device("pointer:button_auto_release", pointer_button_auto_release);
|
||||
litest_add("pointer:scroll", pointer_scroll_wheel, LITEST_WHEEL, LITEST_ANY);
|
||||
litest_add_no_device("pointer:seat button count", pointer_seat_button_count);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue