From a902d5dbd859d25f9994234bed2c1befdc0e2d77 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 14 Apr 2023 07:46:44 +1000 Subject: [PATCH] protocol: replace the capabilities enum with an interface list Previously we had ei_seat.capabilities and ei_device.capabilities, both referring to the same enum. The seat caps were used to bind, the device caps were used to announce capabilities. The device caps were already mostly superfluous as the information they carried was implicitly available by the set of interfaces the device announced - if the device has a keyboard interface it must also have the keyboard capability. So let's drop the separate enum and make the capabilities the set of supported interfaces. In the device we can drop the event directly and just send the interface list. In the seat we have a capability event that sends each *possible* interface with a custom-assigned mask. The client can then use that mask to bind to the capability as before. For example: <- ei_seat.capability(0x1, "ei_pointer") <- ei_seat.capability(0x4, "ei_keyboard") <- ei_seat.capability(0x8, "ei_touchscreen") <- ei_seat.done() -> ei_seat.bind(0x4 | 0x8) # bind to keyboard and touchscreen <- ei_seat.device() -> ei_device.interface("ei_keyboard") -> ei_device.interface("ei_touchscreen") <- ei_device.done() In the generated bindings we simply use the interface index to generate the masks, but the protocol at least states that the mask may not be constant. Because the button/scroll interfaces are not exposed by the C API, some of the handling is a bit awkward since we need to use both depending whether we have pointer/pointer_absolute selected. Fixes #28 Signed-off-by: Peter Hutterer --- proto/protocol.xml | 83 ++++++++------------------ src/ei-proto.h.tmpl | 13 ++++ src/libei-device.c | 36 ++++++----- src/libei-device.h | 1 - src/libei-seat.c | 126 +++++++++++++++++++++++++-------------- src/libei-seat.h | 11 +++- src/libeis-device.c | 8 --- src/libeis-seat.c | 85 ++++++++++++++++++++------ src/libeis-seat.h | 5 +- test/eiproto.py.tmpl | 2 +- test/test_protocol.py | 135 ++++++++++++++++++++++++++++++------------ 11 files changed, 316 insertions(+), 189 deletions(-) diff --git a/proto/protocol.xml b/proto/protocol.xml index c4a02fa..64a481d 100644 --- a/proto/protocol.xml +++ b/proto/protocol.xml @@ -458,22 +458,17 @@ Bind to the bitmask of capabilities given. The bitmask is zero or more of the - capabilities provided in the ei_seat.capabilities event. That event's capabilities - are used as a mask of capabilities that can be bound. + masks representing an interface as provided in the ei_seat.capability event. + See the ei_seat.capability event documentation for examples. - Note that available seat capabilities are defined in the ei_device - interface and not dependent on the seat interface version. In other - words, ei_seat.bind supports any capability available in the - client-announced ei_device interface version. - - Binding capabilities that are not supported in the ei_device's interface version + Binding masks that are not supported in the ei_device's interface version is a client bug and may result in disconnection. A client may send this request multiple times to adjust the capabilities it - is interested in. If capabilities are dropped, the EIS implementation may - ei_device.remove devices that have these capabilities. + is interested in. If previously-bound capabilities are dropped by the client, + the EIS implementation may ei_device.remove devices that have these capabilities. - + @@ -500,31 +495,38 @@ - + - A bitmask of the capabilities of this seat. A client may ei_seat.bind to these - capabilies and an EIS implementation may then create device based on the bound - capabilities. + A notification that this seat supports devices with the given interface. + The interface is mapped to a bitmask by the EIS implementation. + A client may then binary OR these bitmasks in ei_seat.bind. + In response, the EIS implementation may then create device based on those + bound capabilities. - Note that available seat capabilities are defined in the ei_device - interface and not dependent on the seat interface version. In other - words, ei_seat.bind supports any capability available in the - client-announced ei_device interface version. + For example, an EIS implementation may map "ei_pointer" to 0x1, + "ei_keyboard" to 0x4 and "ei_touchscreen" to 0x8. A client may then + ei_seat.bind(0xc) to bind to keyboard and touchscreen but not pointer. + Note that as shown in this example the set of masks may be sparse. + The value of the mask is contant for the lifetime of the seat but may differ + between seats. Note that seat capabilities only represent a mask of possible capabilities on devices in this seat. A capability that is not available on the seat cannot ever be available on any device in this seat. For example, a seat that only has the pointer and keyboard capabilities can never have a device with the touchscreen - capability. + capability. It is up to the EIS implementation to decide how many (if any) devices + with any given capability exist in this seat. - It is up to the EIS implementation to decide how many (if any) devices with any given - capability exist in this seat. + Only interfaces that the client announced during ei_handshake.interface_version + can be a seat capability. - Capabilities are only advertised once and are constant for the lifetime of the seat. + This event is sent multiple times - once per supported interface. + The set of capabilities is constant for the lifetime of the seat. It is a protocol violation to send this event after the ei_seat.done event. - + + @@ -669,39 +671,6 @@ - - - A set of capabilities available on this device. - - - - - - - - - - - - A bitmask of the capabilities of this device. This mask is a subset of the - ei_seat.capabilities announced for the seat for this device. - - Capabilities are only advertised once and are constant for the lifetime of the device. - - For clients of ei_handshake.context_type.sender, the capabilities limit - the type of emulated input events a client can request. For example, a device - without the keyboard capability cannot emulate keyboard events. - - For clients of ei_handshake.context_type.receiver, the capabilities limit - the type of emulated input events a client may receive from the EIS implementation. - For example, a device without the keyboard capability will ever generate keyboard events. - - This event must be is sent once immediately after object creation. - It is a protocol violation to send this event after the ei_device.done event. - - - - If the device type is ei_device.device_type.virtual, the device is a diff --git a/src/ei-proto.h.tmpl b/src/ei-proto.h.tmpl index d00378b..be58796 100644 --- a/src/ei-proto.h.tmpl +++ b/src/ei-proto.h.tmpl @@ -55,6 +55,19 @@ extern const struct brei_interface {{interface.name}}_proto_interface; #define {{interface.name.upper()}}_INTERFACE_NAME "{{interface.protocol_name}}" {% endfor %} +__attribute__((unused)) +static const char *{{component.upper()}}_INTERFACE_NAMES[] = { + {% for interface in interfaces %} + {{interface.name.upper()}}_INTERFACE_NAME, + {% endfor %} +}; + +/* Interface indices as used in the protocol.xml file */ +{% for interface in interfaces %} +#define {{interface.name.upper()}}_INTERFACE_INDEX {{loop.index0}} +{% endfor %} +#define {{component.upper()}}_INTERFACE_COUNT {{interfaces|length}} + {% for interface in interfaces %} /** {{interface.name}} **/ diff --git a/src/libei-device.c b/src/libei-device.c index 817db3e..ab5b933 100644 --- a/src/libei-device.c +++ b/src/libei-device.c @@ -163,17 +163,6 @@ handle_msg_name(struct ei_device *device, const char *name) return NULL; } -static struct brei_result * -handle_msg_capabilities(struct ei_device *device, uint32_t caps) -{ - if (device->capabilities != 0) - return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, - "EIS sent the device capabilities twice"); - - ei_device_set_capabilities(device, caps); - return NULL; -} - static struct brei_result * handle_msg_device_type(struct ei_device *device, enum ei_device_type type) { @@ -232,6 +221,23 @@ handle_msg_done(struct ei_device *device) } + if (device->pointer) + mask_add(device->capabilities, EI_DEVICE_CAP_POINTER); + if (device->pointer_absolute) + mask_add(device->capabilities, EI_DEVICE_CAP_POINTER_ABSOLUTE); + + /* button/scroll-only defaults to normal pointer cap */ + if ((device->scroll || device->button) && + (!device->pointer_absolute)) { + mask_add(device->capabilities, EI_DEVICE_CAP_POINTER); + } + + if (device->keyboard) + mask_add(device->capabilities, EI_DEVICE_CAP_KEYBOARD); + + if (device->touchscreen) + mask_add(device->capabilities, EI_DEVICE_CAP_TOUCH); + if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER) && !ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE) && !ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD) && @@ -438,7 +444,6 @@ handle_msg_interface(struct ei_device *device, object_id_t id, const char *name, static const struct ei_device_interface interface = { .destroyed = handle_msg_destroy, .name = handle_msg_name, - .capabilities = handle_msg_capabilities, .device_type = handle_msg_device_type, .dimensions = handle_msg_dimensions, .region = handle_msg_region, @@ -1112,13 +1117,6 @@ ei_device_set_name(struct ei_device *device, const char *name) device->name = xstrdup(name); } -void -ei_device_set_capabilities(struct ei_device *device, - uint32_t capabilities) -{ - device->capabilities = capabilities; -} - _public_ bool ei_device_has_capability(struct ei_device *device, enum ei_device_capability cap) diff --git a/src/libei-device.h b/src/libei-device.h index 3604d75..1d5587f 100644 --- a/src/libei-device.h +++ b/src/libei-device.h @@ -124,7 +124,6 @@ OBJECT_DECLARE_GETTER(ei_device, touchscreen_interface, const struct ei_touchscr OBJECT_DECLARE_SETTER(ei_device, type, enum ei_device_type); OBJECT_DECLARE_SETTER(ei_device, name, const char*); OBJECT_DECLARE_SETTER(ei_device, seat, const char*); -OBJECT_DECLARE_SETTER(ei_device, capabilities, uint32_t); struct ei_device * ei_device_new(struct ei_seat *seat, object_id_t deviceid, uint32_t version); diff --git a/src/libei-seat.c b/src/libei-seat.c index 432651c..562c7e6 100644 --- a/src/libei-seat.c +++ b/src/libei-seat.c @@ -95,14 +95,26 @@ handle_msg_name(struct ei_seat *seat, const char * name) } static struct brei_result * -handle_msg_capabilities(struct ei_seat *seat, uint32_t capabilities) +handle_msg_capability(struct ei_seat *seat, uint64_t mask, const char *interface) { - if (seat->capabilities != 0) - return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, - "EIS sent the seat capabilities twice"); + struct ei *ei = ei_seat_get_context(seat); - seat->capabilities = capabilities; - return 0; + assert(ARRAY_LENGTH(EI_INTERFACE_NAMES) == ARRAY_LENGTH(seat->capabilities.map)); + + for (size_t i = 0; i < ARRAY_LENGTH(EI_INTERFACE_NAMES); i++) { + if (streq(EI_INTERFACE_NAMES[i], interface)) { + if (seat->capabilities.map[i] != 0) + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "EIS sent the seat capabilities for %s twice", interface); + log_debug(ei, "seat %#" PRIx64" has cap %s as %#" PRIx64, ei_seat_get_id(seat), interface, mask); + seat->capabilities.map[i] = mask; + return 0; + } + } + + /* EIS must not send anything we didn't announce as supported */ + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "EIS sent an unsupported interface %s", interface); } static struct brei_result * @@ -111,7 +123,7 @@ handle_msg_done(struct ei_seat *seat) struct ei *ei = ei_seat_get_context(seat); seat->state = EI_SEAT_STATE_DONE; - log_debug(ei, "Added seat '%s' with caps %#x", seat->name, seat->capabilities); + log_debug(ei, "Added seat '%s'", seat->name); ei_queue_seat_added_event(seat); @@ -145,7 +157,7 @@ handle_msg_device(struct ei_seat *seat, object_id_t id, uint32_t version) static const struct ei_seat_interface interface = { .destroyed = handle_msg_destroyed, .name = handle_msg_name, - .capabilities = handle_msg_capabilities, + .capability = handle_msg_capability, .done = handle_msg_done, .device = handle_msg_device, }; @@ -167,7 +179,7 @@ ei_seat_new(struct ei *ei, object_id_t id, uint32_t version) ei_register_object(ei, &seat->proto_object); seat->state = EI_SEAT_STATE_NEW; - seat->capabilities_bound = 0; + seat->capabilities.bound = 0; list_init(&seat->devices); list_init(&seat->devices_removed); @@ -219,10 +231,16 @@ ei_seat_has_capability(struct ei_seat *seat, { switch (cap) { case EI_DEVICE_CAP_POINTER: + /* FIXME: a seat without pointer or pointer_absolute but button + * and/or scroll should count as pointer here but that's niche + * enough that we can figure that out when needed */ + return seat->capabilities.map[EI_POINTER_INTERFACE_INDEX] != 0; case EI_DEVICE_CAP_POINTER_ABSOLUTE: + return seat->capabilities.map[EI_POINTER_ABSOLUTE_INTERFACE_INDEX] != 0; case EI_DEVICE_CAP_KEYBOARD: + return seat->capabilities.map[EI_KEYBOARD_INTERFACE_INDEX] != 0; case EI_DEVICE_CAP_TOUCH: - return mask_all(seat->capabilities, cap); + return seat->capabilities.map[EI_TOUCHSCREEN_INTERFACE_INDEX] != 0; } return false; } @@ -233,22 +251,8 @@ ei_seat_bind_capability(struct ei_seat *seat, enum ei_device_capability cap) ei_seat_bind_capabilities(seat, cap, NULL); } -static inline bool -is_known_cap(enum ei_device_capability cap) -{ - switch (cap) { - case EI_DEVICE_CAP_POINTER_ABSOLUTE: - case EI_DEVICE_CAP_POINTER: - case EI_DEVICE_CAP_KEYBOARD: - case EI_DEVICE_CAP_TOUCH: - return true; - } - - return false; -} - static int -ei_seat_send_bind(struct ei_seat *seat, uint32_t capabilities) +ei_seat_send_bind(struct ei_seat *seat, uint64_t capabilities) { struct ei *ei = ei_seat_get_context(seat); @@ -261,6 +265,39 @@ ei_seat_send_bind(struct ei_seat *seat, uint32_t capabilities) return rc; } +static uint64_t +ei_seat_cap_mask(struct ei_seat *seat, enum ei_device_capability cap) +{ + switch (cap) { + case EI_DEVICE_CAP_POINTER_ABSOLUTE: + return seat->capabilities.map[EI_POINTER_ABSOLUTE_INTERFACE_INDEX] | + seat->capabilities.map[EI_SCROLL_INTERFACE_INDEX] | + seat->capabilities.map[EI_BUTTON_INTERFACE_INDEX]; + case EI_DEVICE_CAP_POINTER: + return seat->capabilities.map[EI_POINTER_INTERFACE_INDEX] | + seat->capabilities.map[EI_SCROLL_INTERFACE_INDEX] | + seat->capabilities.map[EI_BUTTON_INTERFACE_INDEX]; + case EI_DEVICE_CAP_KEYBOARD: + return seat->capabilities.map[EI_KEYBOARD_INTERFACE_INDEX]; + case EI_DEVICE_CAP_TOUCH: + return seat->capabilities.map[EI_TOUCHSCREEN_INTERFACE_INDEX]; + } + + return 0; +} + +static uint64_t +ei_seat_pointer_cap_mask(struct ei_seat *seat) +{ + return seat->capabilities.map[EI_POINTER_INTERFACE_INDEX]; +} + +static uint64_t +ei_seat_pointer_absolute_cap_mask(struct ei_seat *seat) +{ + return seat->capabilities.map[EI_POINTER_ABSOLUTE_INTERFACE_INDEX]; +} + _public_ void ei_seat_bind_capabilities(struct ei_seat *seat, ...) { @@ -272,25 +309,20 @@ ei_seat_bind_capabilities(struct ei_seat *seat, ...) return; } - uint32_t mask = seat->capabilities_bound; + uint64_t mask = seat->capabilities.bound; enum ei_device_capability cap; va_list args; va_start(args, seat); while ((cap = va_arg(args, enum ei_device_capability)) > 0) { - if (!is_known_cap(cap)) - continue; - - mask_add(mask, cap); + mask_add(mask,ei_seat_cap_mask(seat, cap)); } - mask &= seat->capabilities; - - if (seat->capabilities_bound == mask) + if (seat->capabilities.bound == mask) return; - seat->capabilities_bound = mask; - ei_seat_send_bind(seat, seat->capabilities_bound); + seat->capabilities.bound = mask; + ei_seat_send_bind(seat, seat->capabilities.bound); } _public_ void @@ -311,22 +343,30 @@ ei_seat_unbind_capabilities(struct ei_seat *seat, ...) return; } - uint32_t old_mask = seat->capabilities_bound; + uint64_t mask = seat->capabilities.bound; enum ei_device_capability cap; va_list args; va_start(args, seat); while ((cap = va_arg(args, enum ei_device_capability)) > 0) { - if (!is_known_cap(cap)) - continue; - - mask_remove(seat->capabilities_bound, cap); + mask_remove(mask, ei_seat_cap_mask(seat, cap)); } - if (seat->capabilities_bound == old_mask) + /* This is a bit frustrating because scroll/button aren't visible + * to the libei C API: if we remove POINTER but *not* POINTER_ABSOLUTE + * or vice versa we don't want to remove button/scroll either but the + * above code does just that. So here we re-add it if either is present. + */ + if (mask & ei_seat_pointer_cap_mask(seat)) + mask_add(mask, ei_seat_cap_mask(seat, EI_DEVICE_CAP_POINTER)); + if (mask & ei_seat_pointer_absolute_cap_mask(seat)) + mask_add(mask, ei_seat_cap_mask(seat, EI_DEVICE_CAP_POINTER_ABSOLUTE)); + + if (seat->capabilities.bound == mask) return; - if (seat->capabilities_bound == 0) { + seat->capabilities.bound = mask; + if (seat->capabilities.bound == 0) { struct ei_device *device; list_for_each(device, &seat->devices, link) { if (ei_device_has_capability(device, cap)) @@ -334,5 +374,5 @@ ei_seat_unbind_capabilities(struct ei_seat *seat, ...) } } - ei_seat_send_bind(seat, seat->capabilities_bound); + ei_seat_send_bind(seat, seat->capabilities.bound); } diff --git a/src/libei-seat.h b/src/libei-seat.h index 1f7a21a..43835d4 100644 --- a/src/libei-seat.h +++ b/src/libei-seat.h @@ -28,6 +28,7 @@ #include "util-list.h" #include "brei-shared.h" +#include "ei-proto.h" struct ei; @@ -47,8 +48,14 @@ struct ei_seat { enum ei_seat_state state; struct list devices; struct list devices_removed; /* removed from seat but client still has a ref */ - uint32_t capabilities; - uint32_t capabilities_bound; + + struct { + /* Maps the interface as index to the capability bitmask on the protocol */ + uint64_t map[EI_INTERFACE_COUNT]; + /* A bitmask of the bitmask the client bound last */ + uint64_t bound; + } capabilities; + char *name; }; diff --git a/src/libeis-device.c b/src/libeis-device.c index c4bd43c..a5d6b48 100644 --- a/src/libeis-device.c +++ b/src/libeis-device.c @@ -34,10 +34,6 @@ #include "libeis-private.h" #include "eis-proto.h" -static_assert((int)EIS_DEVICE_CAP_POINTER == EIS_DEVICE_CAPABILITIES_POINTER, "ABI mismatch"); -static_assert((int)EIS_DEVICE_CAP_POINTER_ABSOLUTE == EIS_DEVICE_CAPABILITIES_POINTER_ABSOLUTE, "ABI mismatch"); -static_assert((int)EIS_DEVICE_CAP_KEYBOARD == EIS_DEVICE_CAPABILITIES_KEYBOARD, "ABI mismatch"); -static_assert((int)EIS_DEVICE_CAP_TOUCH == EIS_DEVICE_CAPABILITIES_TOUCHSCREEN, "ABI mismatch"); static_assert((int)EIS_DEVICE_TYPE_VIRTUAL == EIS_DEVICE_DEVICE_TYPE_VIRTUAL, "ABI mismatch"); static_assert((int)EIS_DEVICE_TYPE_PHYSICAL == EIS_DEVICE_DEVICE_TYPE_PHYSICAL, "ABI mismatch"); @@ -801,10 +797,6 @@ eis_device_add(struct eis_device *device) if (rc < 0) goto out; - rc = eis_device_event_capabilities(device, device->capabilities); - if (rc < 0) - goto out; - rc = eis_device_event_device_type(device, device->type); if (rc < 0) goto out; diff --git a/src/libeis-seat.c b/src/libeis-seat.c index d14e94d..df10093 100644 --- a/src/libeis-seat.c +++ b/src/libeis-seat.c @@ -87,24 +87,28 @@ client_msg_release(struct eis_seat *seat) } static struct brei_result * -client_msg_bind(struct eis_seat *seat, uint32_t caps) +client_msg_bind(struct eis_seat *seat, uint64_t caps) { - uint32_t allowed_caps = 0; + uint32_t capabilities = 0; - if (seat->proto_object.version >= EIS_DEVICE_CAPABILITIES_POINTER_SINCE_VERSION) - allowed_caps |= EIS_DEVICE_CAPABILITIES_POINTER; - if (seat->proto_object.version >= EIS_DEVICE_CAPABILITIES_POINTER_ABSOLUTE_SINCE_VERSION) - allowed_caps |= EIS_DEVICE_CAPABILITIES_POINTER_ABSOLUTE; - if (seat->proto_object.version >= EIS_DEVICE_CAPABILITIES_KEYBOARD_SINCE_VERSION) - allowed_caps |= EIS_DEVICE_CAPABILITIES_KEYBOARD; - if (seat->proto_object.version >= EIS_DEVICE_CAPABILITIES_TOUCHSCREEN_SINCE_VERSION) - allowed_caps |= EIS_DEVICE_CAPABILITIES_TOUCHSCREEN; - - if (caps & ~allowed_caps) + if (caps & ~seat->capabilities.proto_mask) return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_VALUE, - "Invalid capabilities %#x", caps); + "Invalid capabilities %#" PRIx64, caps); - eis_seat_bind(seat, caps); + /* Convert from protocol capabilities to our C API capabilities */ + if (caps & bit(EIS_POINTER_INTERFACE_INDEX)) + capabilities |= EIS_DEVICE_CAP_POINTER; + if (caps & bit(EIS_POINTER_ABSOLUTE_INTERFACE_INDEX)) + capabilities |= EIS_DEVICE_CAP_POINTER_ABSOLUTE; + if (caps & bit(EIS_KEYBOARD_INTERFACE_INDEX)) + capabilities |= EIS_DEVICE_CAP_KEYBOARD; + if (caps & bit(EIS_TOUCHSCREEN_INTERFACE_INDEX)) + capabilities |= EIS_DEVICE_CAP_TOUCH; + + /* Note: a client binding to button/scroll only + ends up with libeis capabilities 0. */ + + eis_seat_bind(seat, capabilities); return NULL; } @@ -162,7 +166,52 @@ eis_seat_add(struct eis_seat *seat) eis_client_register_object(client, &seat->proto_object); eis_client_add_seat(client, seat); eis_seat_event_name(seat, seat->name); - eis_seat_event_capabilities(seat, seat->capabilities_mask); + + if (seat->capabilities.c_mask & EIS_DEVICE_CAP_POINTER && + client->interface_versions.ei_pointer > 0) { + uint64_t mask = bit(EIS_POINTER_INTERFACE_INDEX); + eis_seat_event_capability(seat, mask, + EIS_POINTER_INTERFACE_NAME); + mask_add(seat->capabilities.proto_mask, mask); + } + + if (seat->capabilities.c_mask & EIS_DEVICE_CAP_POINTER_ABSOLUTE && + client->interface_versions.ei_pointer_absolute > 0) { + uint64_t mask = bit(EIS_POINTER_ABSOLUTE_INTERFACE_INDEX); + eis_seat_event_capability(seat, mask, + EIS_POINTER_ABSOLUTE_INTERFACE_NAME); + mask_add(seat->capabilities.proto_mask, mask); + } + + if (seat->capabilities.c_mask & (EIS_DEVICE_CAP_POINTER|EIS_DEVICE_CAP_POINTER_ABSOLUTE) && + (client->interface_versions.ei_pointer > 0 || client->interface_versions.ei_pointer_absolute > 0)) { + uint64_t mask = bit(EIS_SCROLL_INTERFACE_INDEX); + eis_seat_event_capability(seat, mask, + EIS_SCROLL_INTERFACE_NAME); + mask_add(seat->capabilities.proto_mask, mask); + + mask = bit(EIS_BUTTON_INTERFACE_INDEX); + eis_seat_event_capability(seat, mask, + EIS_BUTTON_INTERFACE_NAME); + mask_add(seat->capabilities.proto_mask, mask); + } + + if (seat->capabilities.c_mask & EIS_DEVICE_CAP_KEYBOARD && + client->interface_versions.ei_keyboard > 0) { + uint64_t mask = bit(EIS_KEYBOARD_INTERFACE_INDEX); + eis_seat_event_capability(seat, mask, + EIS_KEYBOARD_INTERFACE_NAME); + mask_add(seat->capabilities.proto_mask, mask); + } + + if (seat->capabilities.c_mask & EIS_DEVICE_CAP_TOUCH && + client->interface_versions.ei_touchscreen > 0) { + uint64_t mask = bit(EIS_TOUCHSCREEN_INTERFACE_INDEX); + eis_seat_event_capability(seat, mask, + EIS_TOUCHSCREEN_INTERFACE_NAME); + mask_add(seat->capabilities.proto_mask, mask); + } + eis_seat_event_done(seat); } @@ -184,7 +233,7 @@ eis_seat_bind(struct eis_seat *seat, uint32_t caps) return; } - caps &= seat->capabilities_mask; + caps &= seat->capabilities.c_mask; seat->state = EIS_SEAT_STATE_BOUND; eis_queue_seat_bind_event(seat, caps); @@ -250,7 +299,7 @@ eis_seat_configure_capability(struct eis_seat *seat, case EIS_DEVICE_CAP_POINTER_ABSOLUTE: case EIS_DEVICE_CAP_KEYBOARD: case EIS_DEVICE_CAP_TOUCH: - mask_add(seat->capabilities_mask, cap); + mask_add(seat->capabilities.c_mask, cap); break; } } @@ -264,7 +313,7 @@ eis_seat_has_capability(struct eis_seat *seat, case EIS_DEVICE_CAP_POINTER_ABSOLUTE: case EIS_DEVICE_CAP_KEYBOARD: case EIS_DEVICE_CAP_TOUCH: - return mask_all(seat->capabilities_mask, cap); + return mask_all(seat->capabilities.c_mask, cap); } return false; } diff --git a/src/libeis-seat.h b/src/libeis-seat.h index a08d520..8b64d57 100644 --- a/src/libeis-seat.h +++ b/src/libeis-seat.h @@ -48,7 +48,10 @@ struct eis_seat { enum eis_seat_state state; char *name; - uint32_t capabilities_mask; + struct { + uint32_t c_mask; /* this is the C API bitmask */ + uint64_t proto_mask; /* the protocol mask */ + } capabilities; struct list devices; }; diff --git a/test/eiproto.py.tmpl b/test/eiproto.py.tmpl index bcc65ab..77e84ce 100644 --- a/test/eiproto.py.tmpl +++ b/test/eiproto.py.tmpl @@ -147,7 +147,7 @@ class InterfaceName(StrEnum): {% endfor %} -@attr.s +@attr.s(eq=False) class Interface: object_id: int = attr.ib(repr=lambda id: f"{id:#x}") version: int = attr.ib() diff --git a/test/test_protocol.py b/test/test_protocol.py index 346645f..3d38865 100644 --- a/test/test_protocol.py +++ b/test/test_protocol.py @@ -20,6 +20,7 @@ # # Will run that test against whatever is providing that socket. +from functools import reduce from typing import Generator, Optional from pathlib import Path @@ -44,7 +45,6 @@ try: EiCallback, EiConnection, EiHandshake, - EiDevice, EiSeat, ) except ModuleNotFoundError as e: @@ -191,6 +191,28 @@ class Ei: return True + def seat_fill_capability_masks(self, seat: EiSeat): + """ + Set up the seat to fill the interface masks for each Capability + and add the bind_mask() helper function to compile a mask + from interface names. + """ + + def seat_cap(seat, mask, intf_name): + seat._interface_masks[intf_name] = mask + + seat._interface_masks = {} + seat.connect("Capability", seat_cap) + + def bind_mask(interfaces: list[InterfaceName]) -> int: + return reduce( + lambda mask, v: mask | v, + [seat._interface_masks[i] for i in interfaces], + 0, + ) + + seat.bind_mask = bind_mask + def recv(self) -> bytes: try: data = self.sock.recv(1024) @@ -257,7 +279,10 @@ class Ei: elif isinstance(interface, EiSeat): assert interface not in ei.seats - ei.seats.append(interface) + + seat = interface + ei.seat_fill_capability_masks(seat) + ei.seats.append(seat) def unregister_cb(interface: Interface) -> None: if interface == ei.connection: @@ -486,6 +511,9 @@ class TestEiProtocol: eis.terminate() + @pytest.mark.skipif( + not getattr(int, "bit_count", None), reason="int.bit_count() required" + ) def test_connect_receive_seat(self, eis): """ Ensure we get a seat object after setting our connection @@ -502,6 +530,10 @@ class TestEiProtocol: InterfaceName.EI_CONNECTION, InterfaceName.EI_CALLBACK, InterfaceName.EI_PINGPONG, + InterfaceName.EI_POINTER, + InterfaceName.EI_POINTER_ABSOLUTE, + InterfaceName.EI_KEYBOARD, + InterfaceName.EI_TOUCHSCREEN, ]: ei.send( setup.InterfaceVersion(interface, VERSION_V(1)) @@ -521,22 +553,29 @@ class TestEiProtocol: for seat in ei.seats: assert seat.version == 1 # we have 100, but the server only has 1 for call in seat.calllog: - if call.name == "Capabilities": - assert call.args["capabilities"] != 0 - if self.using_demo_server: - caps = call.args["capabilities"] - assert ( - caps - == EiDevice.EiCapabilities.POINTER - | EiDevice.EiCapabilities.POINTER_ABSOLUTE - | EiDevice.EiCapabilities.KEYBOARD - | EiDevice.EiCapabilities.TOUCHSCREEN + if call.name == "Capability": + assert call.args["mask"].bit_count() == 1 + assert InterfaceName(call.args["interface"]) + + if self.using_demo_server: + all_caps = [ + call.args["interface"] + for call in seat.calllog + if call.name == "Capability" + ] + assert sorted(all_caps) == sorted( + [ + i.value + for i in ( + InterfaceName.EI_POINTER, + InterfaceName.EI_POINTER_ABSOLUTE, + InterfaceName.EI_BUTTON, + InterfaceName.EI_SCROLL, + InterfaceName.EI_KEYBOARD, + InterfaceName.EI_TOUCHSCREEN, ) - break - else: - assert ( - False - ), f"Expected ei_seat.capabilities, but got none in {seat.calllog}" + ] + ) for call in seat.calllog: if call.name == "Name": @@ -606,7 +645,7 @@ class TestEiProtocol: connection = ei.connection assert connection is not None seat = ei.seats[0] - ei.send(seat.Bind(0x40)) # binding to invalid caps should get us disconnected + ei.send(seat.Bind(0x1)) # binding to invalid caps should get us disconnected try: ei.dispatch() time.sleep(0.1) @@ -647,7 +686,17 @@ class TestEiProtocol: seat.connect("Destroyed", destroyed_cb) if bind_first: - ei.send(seat.Bind(EiDevice.EiCapabilities.POINTER)) + ei.send( + seat.Bind( + seat.bind_mask( + [ + InterfaceName.EI_POINTER, + InterfaceName.EI_BUTTON, + InterfaceName.EI_SCROLL, + ] + ) + ) + ) ei.send(seat.Release()) ei.dispatch() @@ -767,7 +816,16 @@ class TestEiProtocol: seat = new_objects["seat"] assert seat is not None seat.connect("Device", on_device) - ei.send(seat.Bind(EiDevice.EiCapabilities.POINTER)) + + def on_done(seat): + if missing_interface != InterfaceName.EI_POINTER: + mask = seat.bind_mask([InterfaceName.EI_POINTER]) + else: + # Need to bind to *something* to get at least one device + mask = seat.bind_mask([InterfaceName.EI_KEYBOARD]) + ei.send(seat.Bind(mask)) + + seat.connect("Done", on_done) status.seats = True def on_disconnected(connection, last_serial, reason, explanation): @@ -927,6 +985,7 @@ class TestEiProtocol: def on_new_object(o: Interface): logger.debug("new object", object=o) if o.name == InterfaceName.EI_SEAT: + ei.seat_fill_capability_masks(o) o.connect("Device", on_new_device) ei.context.connect("register", on_new_object) @@ -934,12 +993,17 @@ class TestEiProtocol: ei.init_default_sender_connection() ei.wait_for_seat() + seat = ei.seats[0] ei.send( - ei.seats[0].Bind( - EiDevice.EiCapabilities.POINTER - | EiDevice.EiCapabilities.POINTER_ABSOLUTE - | EiDevice.EiCapabilities.KEYBOARD - | EiDevice.EiCapabilities.TOUCHSCREEN + seat.Bind( + seat.bind_mask( + [ + InterfaceName.EI_POINTER, + InterfaceName.EI_POINTER_ABSOLUTE, + InterfaceName.EI_KEYBOARD, + InterfaceName.EI_TOUCHSCREEN, + ] + ) ) ) @@ -951,7 +1015,7 @@ class TestEiProtocol: @pytest.mark.parametrize( "wanted_pointer", - (EiDevice.EiCapabilities.POINTER, EiDevice.EiCapabilities.POINTER_ABSOLUTE), + (InterfaceName.EI_POINTER, InterfaceName.EI_POINTER_ABSOLUTE), ) def test_connect_receive_pointer(self, eis, wanted_pointer): """ @@ -961,14 +1025,11 @@ class TestEiProtocol: @attr.s class Status: - pointers: dict[EiDevice.EiCapabilities, Interface] = attr.ib(default=attr.Factory(dict)) # type: ignore + pointers: dict[InterfaceName, Interface] = attr.ib(default=attr.Factory(dict)) # type: ignore all_caps: int = attr.ib(default=0) status = Status() - def on_capabilities(device, caps): - status.all_caps |= caps - def on_interface(device, object, name, version, new_objects): logger.debug( "new capability", @@ -977,21 +1038,17 @@ class TestEiProtocol: name=name, version=version, ) - if name == InterfaceName.EI_POINTER: - status.pointers[EiDevice.EiCapabilities.POINTER] = new_objects["object"] - elif name == InterfaceName.EI_POINTER_ABSOLUTE: - status.pointers[EiDevice.EiCapabilities.POINTER_ABSOLUTE] = new_objects[ - "object" - ] + if name in [InterfaceName.EI_POINTER, InterfaceName.EI_POINTER_ABSOLUTE]: + status.pointers[InterfaceName(name)] = new_objects["object"] def on_new_device(seat, device, version, new_objects): logger.debug("new device", object=new_objects["device"]) new_objects["device"].connect("Interface", on_interface) - new_objects["device"].connect("Capabilities", on_capabilities) def on_new_object(o: Interface): logger.debug("new object", object=o) if o.name == InterfaceName.EI_SEAT: + ei.seat_fill_capability_masks(o) o.connect("Device", on_new_device) ei.context.connect("register", on_new_object) @@ -999,10 +1056,10 @@ class TestEiProtocol: ei.init_default_sender_connection() ei.wait_for_seat() - ei.send(ei.seats[0].Bind(wanted_pointer)) + seat = ei.seats[0] + ei.send(seat.Bind(seat.bind_mask([wanted_pointer]))) ei.wait_for(lambda: status.pointers) assert status.pointers[wanted_pointer] is not None assert len(status.pointers) == 1 - assert status.all_caps & wanted_pointer