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