diff --git a/proto/protocol.xml b/proto/protocol.xml index c01c800..02411b8 100644 --- a/proto/protocol.xml +++ b/proto/protocol.xml @@ -420,7 +420,7 @@ - + An ei_seat represents a set of input devices that logically belong together. In most cases only one seat is present and all input devices on that seat share the same @@ -470,6 +470,39 @@ + + + + + Request a new device from the EIS implementation with the given capability + mask. If the EIS implementation creates a new device in response to this + request that device is announced via the ei_seat.device event as any other + device. + + The capabilities argument is a bitmask of one or more of the + masks representing an interface as provided in the ei_seat.capability event. + See the ei_seat.capability event documentation for examples. + + The newly created device(s), if any, may not provide all requested capabilities. + The EIS implementation may create multiple devices to accommodate all requested + capabilities. For example, a client requesting a device with the ei_keyboard and + ei_pointer capability may instead see one device with the ei_keyboard and one + device with the ei_pointer capability being created. + + If possible, the EIS implementation should create devices immediately in + response to this request so a client can utilize ei_connection.sync to + help identify those devices. This is a suggestion to help the client and not + a requirement. + + Requesting masks that are not supported in the ei_device's interface version + is a client bug and may result in disconnection. + + Requesting masks that are not currently bound via the most recent ei_seat.bind + is a client bug and may result in disconnection. + + + + diff --git a/src/libei-seat.c b/src/libei-seat.c index 9d2e49b..e35bf4e 100644 --- a/src/libei-seat.c +++ b/src/libei-seat.c @@ -349,3 +349,37 @@ ei_seat_unbind_capabilities(struct ei_seat *seat, ...) ei_seat_send_bind(seat, seat->capabilities.bound); } + +_public_ void +ei_seat_request_device_with_capabilities(struct ei_seat *seat, ...) +{ + switch (seat->state) { + case EI_SEAT_STATE_DONE: + break; + case EI_SEAT_STATE_NEW: + case EI_SEAT_STATE_REMOVED: + return; + } + + uint64_t mask = 0; + enum ei_device_capability cap; + + va_list args; + va_start(args, seat); + while ((cap = va_arg(args, enum ei_device_capability)) > 0) { + mask_add(mask, ei_seat_cap_mask(seat, cap)); + } + va_end(args); + + if (mask == 0) + return; + + /* Check if requested capabilities are a subset of bound capabilities */ + if (!mask_all(seat->capabilities.bound, mask)) { + struct ei *ei = ei_seat_get_context(seat); + log_bug_client(ei, "Requested capabilities are not a subset of the bound capabilities"); + return; + } + + ei_seat_request_request_device(seat, mask); +} diff --git a/src/libei.c b/src/libei.c index 7fe9a62..f4fdf0f 100644 --- a/src/libei.c +++ b/src/libei.c @@ -126,7 +126,7 @@ ei_create_context(bool is_sender, void *user_data) .ei_handshake = VERSION_V(1), .ei_callback = VERSION_V(1), .ei_pingpong = VERSION_V(1), - .ei_seat = VERSION_V(1), + .ei_seat = VERSION_V(2), .ei_device = VERSION_V(3), .ei_pointer = VERSION_V(1), .ei_pointer_absolute = VERSION_V(1), diff --git a/src/libei.h b/src/libei.h index 151b24e..56b370c 100644 --- a/src/libei.h +++ b/src/libei.h @@ -1089,6 +1089,31 @@ void ei_seat_unbind_capabilities(struct ei_seat *seat, ...) __attribute__((sentinel)); +/** + * @ingroup libei-seat + * + * Request a new device with (a subset of) the given capabilities from the EIS + * implementation. If the EIS implementation creates a device in response + * to this request the device will arrive via an event of type @ref + * EI_EVENT_DEVICE_ADDED. + * + * The device's capabilities may not match the requested capabilities. For + * example requesting a pointer + keyboard device may result in the creation + * of a pointer-only device or it may result in the creation of a pointer + + * keyboard + touch device. It is up to the caller to handle capability + * mismatches. + * + * This function should be used by a caller when the current set of devices + * is insufficient for the functionality the client requires. For example + * a client that has previously called ei_device_close() on a device may + * need a device again with similar capabilities. + * + * The capabilities must be a subset of the capabilities requested in + * ei_seat_bind_capabilities(). + */ +void +ei_seat_request_device_with_capabilities(struct ei_seat *seat, ...) +__attribute__((sentinel)); /** * @ingroup libei-seat diff --git a/src/libeis-client.c b/src/libeis-client.c index 22c2170..a60c5c8 100644 --- a/src/libeis-client.c +++ b/src/libeis-client.c @@ -521,7 +521,7 @@ eis_client_new(struct eis *eis, int fd) .ei_handshake = VERSION_V(1), .ei_callback = VERSION_V(1), .ei_pingpong = VERSION_V(1), - .ei_seat = VERSION_V(1), + .ei_seat = VERSION_V(2), .ei_device = flag_is_set(eis->flags, EIS_FLAG_DEVICE_READY) ? VERSION_V(3) : VERSION_V(2), .ei_pointer = VERSION_V(1), .ei_pointer_absolute = VERSION_V(1), diff --git a/src/libeis-event.c b/src/libeis-event.c index 125dc51..ffc9993 100644 --- a/src/libeis-event.c +++ b/src/libeis-event.c @@ -41,6 +41,7 @@ eis_event_destroy(struct eis_event *event) case EIS_EVENT_CLIENT_CONNECT: case EIS_EVENT_CLIENT_DISCONNECT: case EIS_EVENT_SEAT_BIND: + case EIS_EVENT_SEAT_DEVICE_REQUESTED: case EIS_EVENT_DEVICE_CLOSED: case EIS_EVENT_DEVICE_START_EMULATING: case EIS_EVENT_DEVICE_STOP_EMULATING: @@ -208,7 +209,10 @@ eis_event_pong_get_ping(struct eis_event *event) _public_ bool eis_event_seat_has_capability(struct eis_event *event, enum eis_device_capability cap) { - require_event_type(event, false, EIS_EVENT_SEAT_BIND); + require_event_type(event, + false, + EIS_EVENT_SEAT_BIND, + EIS_EVENT_SEAT_DEVICE_REQUESTED); switch (cap) { case EIS_DEVICE_CAP_POINTER: diff --git a/src/libeis-private.h b/src/libeis-private.h index b3634ef..bb5055f 100644 --- a/src/libeis-private.h +++ b/src/libeis-private.h @@ -103,6 +103,9 @@ eis_queue_sync_event(struct eis_client *client, object_id_t newid, uint32_t vers void eis_queue_seat_bind_event(struct eis_seat *seat, uint32_t capabilities); +void +eis_queue_seat_device_requested_event(struct eis_seat *seat, uint32_t capabilities); + void eis_queue_device_closed_event(struct eis_device *device); diff --git a/src/libeis-seat.c b/src/libeis-seat.c index bca387e..91e6df9 100644 --- a/src/libeis-seat.c +++ b/src/libeis-seat.c @@ -86,15 +86,11 @@ client_msg_release(struct eis_seat *seat) return NULL; } -static struct brei_result * -client_msg_bind(struct eis_seat *seat, uint64_t caps) +static inline uint32_t +convert_capabilities(uint64_t caps) { uint32_t capabilities = 0; - if (caps & ~seat->capabilities.proto_mask) - return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_VALUE, - "Invalid capabilities %#" PRIx64, caps); - /* Convert from protocol capabilities to our C API capabilities */ if (caps & bit(EIS_POINTER_INTERFACE_INDEX)) capabilities |= EIS_DEVICE_CAP_POINTER; @@ -109,14 +105,41 @@ client_msg_bind(struct eis_seat *seat, uint64_t caps) if (caps & bit(EIS_SCROLL_INTERFACE_INDEX)) capabilities |= EIS_DEVICE_CAP_SCROLL; + return capabilities; +} + +static struct brei_result * +client_msg_bind(struct eis_seat *seat, uint64_t caps) +{ + if (caps & ~seat->capabilities.proto_mask) + return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_VALUE, + "Invalid capabilities %#" PRIx64, caps); + + uint32_t capabilities = convert_capabilities(caps); + eis_seat_bind(seat, capabilities); return NULL; } +static struct brei_result * +client_msg_request_device(struct eis_seat *seat, uint64_t caps) +{ + if (caps == 0 || caps & ~seat->capabilities.proto_mask) + return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_VALUE, + "Invalid capabilities %#" PRIx64, caps); + + uint32_t capabilities = convert_capabilities(caps); + + eis_queue_seat_device_requested_event(seat, capabilities); + + return NULL; +} + static const struct eis_seat_interface interface = { .release = client_msg_release, .bind = client_msg_bind, + .request_device = client_msg_request_device, }; const struct eis_seat_interface * diff --git a/src/libeis.c b/src/libeis.c index 002ee7b..6996114 100644 --- a/src/libeis.c +++ b/src/libeis.c @@ -137,6 +137,7 @@ eis_event_type_to_string(enum eis_event_type type) CASE_RETURN_STRING(EIS_EVENT_CLIENT_CONNECT); CASE_RETURN_STRING(EIS_EVENT_CLIENT_DISCONNECT); CASE_RETURN_STRING(EIS_EVENT_SEAT_BIND); + CASE_RETURN_STRING(EIS_EVENT_SEAT_DEVICE_REQUESTED); CASE_RETURN_STRING(EIS_EVENT_DEVICE_CLOSED); CASE_RETURN_STRING(EIS_EVENT_DEVICE_READY); CASE_RETURN_STRING(EIS_EVENT_PONG); @@ -268,6 +269,16 @@ eis_queue_seat_bind_event(struct eis_seat *seat, uint32_t capabilities) eis_queue_event(e); } +void +eis_queue_seat_device_requested_event(struct eis_seat *seat, + uint32_t capabilities) +{ + struct eis_event *e = eis_event_new_for_seat(seat); + e->type = EIS_EVENT_SEAT_DEVICE_REQUESTED; + e->bind.capabilities = capabilities; + eis_queue_event(e); +} + void eis_queue_device_closed_event(struct eis_device *device) { diff --git a/src/libeis.h b/src/libeis.h index 17b2619..267b6b2 100644 --- a/src/libeis.h +++ b/src/libeis.h @@ -286,6 +286,14 @@ enum eis_event_type { */ EIS_EVENT_DEVICE_READY, + /** + * The client requested a device with the given capabilities + * on this seat. + * + * @since 1.6 + */ + EIS_EVENT_SEAT_DEVICE_REQUESTED, + /** * Returned in response to eis_ping(). */ @@ -779,11 +787,12 @@ struct eis_ping * eis_event_pong_get_ping(struct eis_event *event); /** - * For an event of type @ref EIS_EVENT_SEAT_BIND, return the capabilities - * requested by the client. + * For an event of type @ref EIS_EVENT_SEAT_BIND or @ref + * EIS_EVENT_SEAT_DEVICE_REQUESTED, return the capabilities requested by the + * client. * - * This is the set of *all* capabilities bound by the client as of this event, - * not just the changed ones. + * For events of type @ref EIS_EVENT_SEAT_BIND this is the set of *all* + * capabilities bound by the client as of this event, not just the changed ones. */ bool eis_event_seat_has_capability(struct eis_event *event, enum eis_device_capability cap); diff --git a/test/eierpecken.c b/test/eierpecken.c index 094b986..b7f8df2 100644 --- a/test/eierpecken.c +++ b/test/eierpecken.c @@ -1117,6 +1117,8 @@ _peck_dispatch_eis(struct peck *peck, int lineno) else process_event = tristate_no; break; + case EIS_EVENT_SEAT_DEVICE_REQUESTED: + break; case EIS_EVENT_DEVICE_CLOSED: if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_HANDLE_CLOSE_DEVICE)) process_event = tristate_yes; @@ -1686,6 +1688,7 @@ peck_eis_event_type_name(enum eis_event_type type) CASE_STRING(CLIENT_CONNECT); CASE_STRING(CLIENT_DISCONNECT); CASE_STRING(SEAT_BIND); + CASE_STRING(SEAT_DEVICE_REQUESTED); CASE_STRING(DEVICE_CLOSED); CASE_STRING(PONG); CASE_STRING(SYNC); diff --git a/test/test-ei-seat.c b/test/test-ei-seat.c index 7eeba56..c5065ca 100644 --- a/test/test-ei-seat.c +++ b/test/test-ei-seat.c @@ -173,3 +173,43 @@ MUNIT_TEST(test_ei_seat_bind_unbind_immediately) return MUNIT_OK; } + +MUNIT_TEST(test_ei_seat_request_device) +{ + _unref_(peck) *peck = peck_new(); + + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); + + peck_dispatch_until_stable(peck); + + with_client(peck) { + struct ei_seat *seat = peck_ei_get_default_seat(peck); + ei_seat_request_device_with_capabilities(seat, + EI_DEVICE_CAP_POINTER, + EI_DEVICE_CAP_KEYBOARD, + NULL); + ei_seat_request_device_with_capabilities(seat, + EI_DEVICE_CAP_TOUCH, + EI_DEVICE_CAP_POINTER_ABSOLUTE, + NULL); + } + + peck_dispatch_until_stable(peck); + + with_server(peck) { + _unref_(eis_event) *e1 = peck_eis_next_event(eis, EIS_EVENT_SEAT_DEVICE_REQUESTED); + _unref_(eis_event) *e2 = peck_eis_next_event(eis, EIS_EVENT_SEAT_DEVICE_REQUESTED); + + munit_assert(eis_event_seat_has_capability(e1, EIS_DEVICE_CAP_POINTER)); + munit_assert(eis_event_seat_has_capability(e1, EIS_DEVICE_CAP_KEYBOARD)); + munit_assert(!eis_event_seat_has_capability(e1, EIS_DEVICE_CAP_TOUCH)); + munit_assert(!eis_event_seat_has_capability(e1, EIS_DEVICE_CAP_POINTER_ABSOLUTE)); + + munit_assert(!eis_event_seat_has_capability(e2, EIS_DEVICE_CAP_POINTER)); + munit_assert(!eis_event_seat_has_capability(e2, EIS_DEVICE_CAP_KEYBOARD)); + munit_assert(eis_event_seat_has_capability(e2, EIS_DEVICE_CAP_TOUCH)); + munit_assert(eis_event_seat_has_capability(e2, EIS_DEVICE_CAP_POINTER_ABSOLUTE)); + } + + return MUNIT_OK; +} diff --git a/test/test_protocol.py b/test/test_protocol.py index 98d2f14..85ddb16 100644 --- a/test/test_protocol.py +++ b/test/test_protocol.py @@ -590,7 +590,9 @@ class TestEiProtocol: assert ei.seats for seat in ei.seats: - assert seat.version == 1 # we have 100, but the server only has 1 + assert seat.version == VERSION_V( + 2 + ) # we have 100, but the server only has 1 for call in seat.calllog: if call.name == "Capability": assert call.args["mask"].bit_count() == 1