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 <peter.hutterer@who-t.net>
This commit is contained in:
Peter Hutterer 2023-04-14 07:46:44 +10:00
parent bf88b34918
commit a902d5dbd8
11 changed files with 316 additions and 189 deletions

View file

@ -458,22 +458,17 @@
<request name="bind" since="1">
<description summary="Seat binding">
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.
</description>
<arg name="capabilities" type="uint32" enum="ei_device.capabilities"/>
<arg name="capabilities" type="uint64" summary="bitmask of the capabilities"/>
</request>
<!-- ei_seat events version 1 -->
@ -500,31 +495,38 @@
<arg name="name" type="string" summary="the seat name"/>
</event>
<event name="capabilities" since="1">
<event name="capability" since="1">
<description summary="Seat capability notification">
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.
</description>
<arg name="capabilities" type="uint32" enum="ei_device.capabilities" summary="the seat capabilities"/>
<arg name="mask" type="uint64" summary="the mask representing this capability"/>
<arg name="interface" type="string" summary="the interface name for this capability"/>
</event>
<event name="done" since="1">
@ -669,39 +671,6 @@
<arg name="name" type="string" summary="the device name"/>
</event>
<enum name="capabilities" since="1" bitfield="true">
<description summary="Device capabilities">
A set of capabilities available on this device.
</description>
<entry name="pointer" value="1" summary="the relative pointer capability"/>
<entry name="pointer_absolute" value="2" summary="the absolute pointer capability"/>
<entry name="keyboard" value="4" summary="the keyboard capability"/>
<entry name="touchscreen" value="8" summary="the touchscreen capability"/>
<entry name="scroll" value="16" summary="the scroll capability"/>
<entry name="button" value="32" summary="the button capability"/>
</enum>
<event name="capabilities" since="1">
<description summary="device capability notification">
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.
</description>
<arg name="capabilities" type="uint32" enum="capabilities" summary="the device name"/>
</event>
<enum name="device_type" since="1">
<description summary="device type">
If the device type is ei_device.device_type.virtual, the device is a

View file

@ -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}} **/

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 call.name == "Capability":
assert call.args["mask"].bit_count() == 1
assert InterfaceName(call.args["interface"])
if self.using_demo_server:
caps = call.args["capabilities"]
assert (
caps
== EiDevice.EiCapabilities.POINTER
| EiDevice.EiCapabilities.POINTER_ABSOLUTE
| EiDevice.EiCapabilities.KEYBOARD
| EiDevice.EiCapabilities.TOUCHSCREEN
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