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 <peter.hutterer@who-t.net>
This commit is contained in:
Peter Hutterer 2020-10-29 14:02:46 +10:00
parent f068ddf2a8
commit c317de742b
5 changed files with 168 additions and 0 deletions

View file

@ -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:

View file

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

View file

@ -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)
{

View file

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

View file

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