Keep track of pressed keys and buttons in a bitmask array and iterate through it on device removal releasing every still pressed key.
This commit enables _GNU_SOURCE features in evdev.c, more specifically static_assert(). This is supported by gcc 4.6 and above, but is not part of the C standard until C11. configure will fail if static_assert() doesn't work whith _GNU_SOURCE defined. Signed-off-by: Jonas Ådahl <jad...@gmail.com> --- configure.ac | 11 +++++ src/evdev.c | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/evdev.h | 14 ++++++ test/keyboard.c | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++ test/pointer.c | 91 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 362 insertions(+), 4 deletions(-) diff --git a/configure.ac b/configure.ac index fd402e2..e35fff6 100644 --- a/configure.ac +++ b/configure.ac @@ -67,6 +67,17 @@ fi AC_SUBST(GCC_CFLAGS) AC_SUBST(GCC_CXXFLAGS) +AC_MSG_CHECKING([whether static_assert() is supported]) +AC_COMPILE_IFELSE( + [AC_LANG_SOURCE([[#define _GNU_SOURCE + #include <assert.h> + static_assert(1, "Test"); + ]])], + [have_static_assert=1 + AC_MSG_RESULT([yes])], + [have_static_assert=0 + AC_MSG_ERROR([no built-in static_assert])]) + AC_PATH_PROG(DOXYGEN, [doxygen]) if test "x$DOXYGEN" = "x"; then AC_MSG_WARN([doxygen not found - required for documentation]) diff --git a/src/evdev.c b/src/evdev.c index c031258..3fb5ab4 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -21,6 +21,8 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#define _GNU_SOURCE + #include "config.h" #include <errno.h> @@ -47,6 +49,87 @@ enum evdev_key_type { EVDEV_KEY_TYPE_BUTTON, }; +static void +get_key_index_and_bit(int code, int *index, int *bit) +{ + static_assert((KEY_CNT % 64) == 0, "KEY_CNT not aligned to 64"); + + *index = code / 64; + *bit = code % 64; +} + +static int +get_key_state(struct evdev_device *device, int code) +{ + int index = 0; + int bit = 0; + + get_key_index_and_bit(code, &index, &bit); + return !!(device->key_mask[index] & (1ULL << bit)); +} + +static void +set_key_state(struct evdev_device *device, int code, int pressed) +{ + int index = 0; + int bit = 0; + + get_key_index_and_bit(code, &index, &bit); + + if (pressed) + device->key_mask[index] |= (1ULL << bit); + else + device->key_mask[index] &= ~(1ULL << bit); +} + +void +evdev_keyboard_notify_key(struct evdev_device *device, + uint32_t time, + int key, + enum libinput_key_state state) +{ + struct libinput *libinput = device->base.seat->libinput; + + if ((state == LIBINPUT_KEY_STATE_PRESSED && + get_key_state(device, key) != 0) || + (state == LIBINPUT_KEY_STATE_RELEASED && + get_key_state(device, key) != 1)) { + log_bug_kernel( + libinput, + "%s: Driver sent multiple (0x%x) pressed/released", + device->devnode, key); + return; + } + + set_key_state(device, key, state == LIBINPUT_KEY_STATE_PRESSED); + + keyboard_notify_key(&device->base, time, key, state); +} + +void +evdev_pointer_notify_button(struct evdev_device *device, + uint32_t time, + int button, + enum libinput_button_state state) +{ + struct libinput *libinput = device->base.seat->libinput; + + if ((state == LIBINPUT_BUTTON_STATE_PRESSED && + get_key_state(device, button) != 0) || + (state == LIBINPUT_BUTTON_STATE_RELEASED && + get_key_state(device, button) != 1)) { + log_bug_kernel( + libinput, + "%s: Driver sent multiple (0%x) pressed/released", + device->devnode, button); + return; + } + + set_key_state(device, button, state == LIBINPUT_BUTTON_STATE_PRESSED); + + pointer_notify_button(&device->base, time, button, state); +} + void evdev_device_led_update(struct evdev_device *device, enum libinput_led leds) { @@ -278,6 +361,45 @@ get_key_type(uint16_t code) } 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_error(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_state(device, code) == 1) { + switch (get_key_type(code)) { + case EVDEV_KEY_TYPE_NONE: + break; + case EVDEV_KEY_TYPE_KEY: + evdev_keyboard_notify_key( + device, + time, + code, + LIBINPUT_KEY_STATE_RELEASED); + break; + case EVDEV_KEY_TYPE_BUTTON: + evdev_pointer_notify_button( + device, + time, + code, + LIBINPUT_BUTTON_STATE_RELEASED); + break; + } + } + } +} + +static void evdev_process_touch_button(struct evdev_device *device, uint64_t time, int value) { @@ -310,16 +432,16 @@ evdev_process_key(struct evdev_device *device, case EVDEV_KEY_TYPE_NONE: break; case EVDEV_KEY_TYPE_KEY: - keyboard_notify_key( - &device->base, + evdev_keyboard_notify_key( + device, time, e->code, e->value ? LIBINPUT_KEY_STATE_PRESSED : LIBINPUT_KEY_STATE_RELEASED); break; case EVDEV_KEY_TYPE_BUTTON: - pointer_notify_button( - &device->base, + evdev_pointer_notify_button( + device, time, e->code, e->value ? LIBINPUT_BUTTON_STATE_PRESSED : @@ -937,6 +1059,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); diff --git a/src/evdev.h b/src/evdev.h index fad1f84..9c3f3d8 100644 --- a/src/evdev.h +++ b/src/evdev.h @@ -94,6 +94,8 @@ struct evdev_device { struct { struct motion_filter *filter; } pointer; + + uint64_t key_mask[KEY_CNT / 64]; }; #define EVDEV_UNHANDLED_DEVICE ((struct evdev_device *) 1) @@ -173,6 +175,18 @@ evdev_device_transform_y(struct evdev_device *device, uint32_t height); void +evdev_keyboard_notify_key(struct evdev_device *device, + uint32_t time, + int key, + enum libinput_key_state state); + +void +evdev_pointer_notify_button(struct evdev_device *device, + uint32_t time, + int button, + enum libinput_button_state state); + +void evdev_device_remove(struct evdev_device *device); void diff --git a/test/keyboard.c b/test/keyboard.c index a55405c..1a21e49 100644 --- a/test/keyboard.c +++ b/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) @@ -112,10 +113,127 @@ START_TEST(keyboard_seat_key_count) } 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; + 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, }, + }; + static int events[2 * (ARRAY_LENGTH(keys) + 1)]; + unsigned i; + int key; + int valid_code; + + /* Enable all tested keys on the device */ + for (i = 0; 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); + + if (libinput_event_get_type(event) == + LIBINPUT_EVENT_DEVICE_REMOVED) { + libinput_event_destroy(event); + break; + } + + ck_assert_int_eq(libinput_event_get_type(event), + LIBINPUT_EVENT_KEYBOARD_KEY); + kevent = libinput_event_get_keyboard_event(event); + ck_assert_notnull(kevent); + 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_auto_release", keyboard_key_auto_release); return litest_run(argc, argv); } diff --git a/test/pointer.c b/test/pointer.c index aa75274..57aae8a 100644 --- a/test/pointer.c +++ b/test/pointer.c @@ -152,6 +152,96 @@ START_TEST(pointer_button) } END_TEST +START_TEST(pointer_button_auto_release) +{ + struct libinput *libinput; + struct litest_device *dev; + struct libinput_event *event; + 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, }, + }; + static 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); + + if (libinput_event_get_type(event) == + LIBINPUT_EVENT_DEVICE_REMOVED) { + libinput_event_destroy(event); + break; + } + + ck_assert_int_eq(libinput_event_get_type(event), + LIBINPUT_EVENT_POINTER_BUTTON); + pevent = libinput_event_get_pointer_event(event); + ck_assert_notnull(pevent); + 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 +390,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); -- 1.8.5.1 _______________________________________________ wayland-devel mailing list wayland-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/wayland-devel