mirror of
https://gitlab.freedesktop.org/libinput/libei.git
synced 2025-12-20 15:00:06 +01:00
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:
parent
bf88b34918
commit
a902d5dbd8
11 changed files with 316 additions and 189 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}} **/
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
124
src/libei-seat.c
124
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;
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue