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();