From c317de742befa3f4079b50cc9ce81f00a2c95a77 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Thu, 29 Oct 2020 14:02:46 +1000 Subject: [PATCH] libei: immediately remove devices added for a removed seat Once the SEAT_REMOVED event has been processed, adding new devices is pointless. But we do promise a DEVICE_REMOVED event for any device added with ei_device_add(), so let's immediately queue an event and mark the device as dead. Since the SEAT_REMOVED event may still be pending in the queue (i.e. not yet read by the client), we need to prepend the event to the queue. Note that client that immediately add a device when a device is removed will cause an infinite loop. Signed-off-by: Peter Hutterer --- src/libei-device.c | 13 +++++ src/libei-private.h | 3 ++ src/libei.c | 28 +++++++++++ src/libei.h | 4 ++ test/test-ei.c | 120 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 168 insertions(+) diff --git a/src/libei-device.c b/src/libei-device.c index c85cef6..5b9a9ed 100644 --- a/src/libei-device.c +++ b/src/libei-device.c @@ -310,8 +310,21 @@ ei_device_set_keymap(struct ei_device *device, _public_ void ei_device_add(struct ei_device *device) { + struct ei_seat *seat = ei_device_get_seat(device); + switch (device->state) { case EI_DEVICE_STATE_NEW: + /* If the caller tries to add a device to a seat that has + * been removed, immediately drop that device. But since our + * SEAT_REMOVED event may still be in the queue, prepend the + * device to the rest of the queue. + */ + if (seat->state == EI_SEAT_STATE_REMOVED) { + ei_insert_device_removed_event(device); + ei_device_set_state(device, EI_DEVICE_STATE_DEAD); + ei_device_unref(device); + return; + } break; case EI_DEVICE_STATE_REMOVED_FROM_CLIENT: case EI_DEVICE_STATE_REMOVED_FROM_SERVER: diff --git a/src/libei-private.h b/src/libei-private.h index 5b6778f..5b4a8d5 100644 --- a/src/libei-private.h +++ b/src/libei-private.h @@ -183,6 +183,9 @@ ei_send_remove_device(struct ei_device *device); void ei_queue_device_removed_event(struct ei_device *device); +void +ei_insert_device_removed_event(struct ei_device *device); + void ei_queue_seat_removed_event(struct ei_seat *seat); diff --git a/src/libei.c b/src/libei.c index 1211e40..558c674 100644 --- a/src/libei.c +++ b/src/libei.c @@ -189,6 +189,16 @@ queue_event(struct ei *ei, struct ei_event *event) list_append(&ei->event_queue, &event->link); } +static void +insert_event(struct ei *ei, struct ei_event *event) +{ + log_debug(ei, "inserting event type %s (%d)\n", + ei_event_type_to_string(event->type), event->type); + + list_insert(&ei->event_queue, &event->link); +} + + static void queue_connect_event(struct ei *ei) { @@ -261,6 +271,18 @@ queue_device_removed_event(struct ei_device *device) queue_event(ei, e); } +static void +insert_device_removed_event(struct ei_device *device) +{ + struct ei *ei= ei_device_get_context(device); + + struct ei_event *e = ei_event_create(&ei->object); + e->type = EI_EVENT_DEVICE_REMOVED; + e->device = ei_device_ref(device); + + insert_event(ei, e); +} + static void queue_suspended_event(struct ei_device *device) { @@ -529,6 +551,12 @@ ei_queue_device_removed_event(struct ei_device *device) queue_device_removed_event(device); } +void +ei_insert_device_removed_event(struct ei_device *device) +{ + insert_device_removed_event(device); +} + static int handle_msg_device_removed(struct ei *ei, uint32_t deviceid) { diff --git a/src/libei.h b/src/libei.h index dd54f95..8187523 100644 --- a/src/libei.h +++ b/src/libei.h @@ -788,6 +788,10 @@ ei_device_keyboard_configure_keymap(struct ei_device *device, * * A client may not send events through this device until it has been added * by the server. + * + * Devices should only be added once all events from ei_get_event() have + * been processed. It is considered a client bug to add a device to a seat + * after the SEAT_REMOVED has been received by libei. */ void ei_device_add(struct ei_device *device); diff --git a/test/test-ei.c b/test/test-ei.c index d1ae178..e0212d7 100644 --- a/test/test-ei.c +++ b/test/test-ei.c @@ -840,6 +840,126 @@ MUNIT_TEST(test_ei_device_add_zero_caps) return MUNIT_OK; } +MUNIT_TEST(test_ei_device_add_after_seat_remove) +{ + _unref_(peck) *peck = peck_new(); + + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); + peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_ADDED); + peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_RESUMED); + peck_dispatch_until_stable(peck); + + with_client(peck) { + struct ei_seat *seat = peck_ei_get_default_seat(peck); + _unref_(ei_device) *device = ei_device_new(seat); + + ei_device_configure_name(device, __func__); + ei_device_configure_capability(device, EI_DEVICE_CAP_POINTER); + ei_device_add(device); + } + + peck_dispatch_until_stable(peck); + + with_server(peck) { + struct eis_seat *seat = peck_eis_get_default_seat(peck); + eis_seat_remove(seat); + } + + /* Seat was removed by server but we don't know this yet, let's add + * a device */ + with_client(peck) { + struct ei_seat *seat = peck_ei_get_default_seat(peck); + _unref_(ei_device) *device = ei_device_new(seat); + ei_device_configure_name(device, __func__); + ei_device_configure_capability(device, EI_DEVICE_CAP_POINTER); + ei_device_add(device); + } + + peck_dispatch_until_stable(peck); + + /* Now add a device while the SEAT_REMOVED event is pending */ + with_client(peck) { + /* The original device */ + _unref_(ei_event) *removed1 = + peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED); + + /* The device added before we knew about seat removal */ + _unref_(ei_event) *removed2 = + peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED); + + struct ei_seat *seat = peck_ei_get_default_seat(peck); + _unref_(ei_device) *device = ei_device_new(seat); + ei_device_configure_name(device, __func__); + ei_device_configure_capability(device, EI_DEVICE_CAP_POINTER); + ei_device_add(device); + + /* The device added just above */ + _unref_(ei_event) *removed3 = + peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED); + + _unref_(ei_event) *seat_removed = + peck_ei_next_event(ei, EI_EVENT_SEAT_REMOVED); + } + + /* Now add a device when the seat had already been removed */ + with_client(peck) { + struct ei_seat *seat = peck_ei_get_default_seat(peck); + _unref_(ei_device) *device = ei_device_new(seat); + ei_device_configure_name(device, __func__); + ei_device_configure_capability(device, EI_DEVICE_CAP_POINTER); + ei_device_add(device); + + _unref_(ei_event) *removed = + peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED); + } + + return MUNIT_OK; +} + +MUNIT_TEST(test_ei_device_add_after_disconnect) +{ + _unref_(peck) *peck = peck_new(); + + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); + peck_dispatch_until_stable(peck); + + with_server(peck) { + struct eis_client *client = peck_eis_get_default_client(peck); + eis_client_disconnect(client); + } + + peck_dispatch_until_stable(peck); + + with_client(peck) { + struct ei_seat *seat = peck_ei_get_default_seat(peck); + _unref_(ei_device) *d1 = ei_device_new(seat); + + ei_device_configure_name(d1, __func__); + ei_device_configure_capability(d1, EI_DEVICE_CAP_POINTER); + ei_device_add(d1); + + _unref_(ei_event) *removed1 = + peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED); + + _unref_(ei_event) *seat_removed = + peck_ei_next_event(ei, EI_EVENT_SEAT_REMOVED); + + _unref_(ei_device) *d2 = ei_device_new(seat); + + ei_device_configure_name(d2, __func__); + ei_device_configure_capability(d2, EI_DEVICE_CAP_POINTER); + ei_device_add(d2); + + _unref_(ei_event) *removed2 = + peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED); + + _unref_(ei_event) *disconnect = + peck_ei_next_event(ei, EI_EVENT_DISCONNECT); + } + + return MUNIT_OK; +} + MUNIT_TEST(test_ei_device_pointer_rel) { _unref_(peck) *peck = peck_new();