mirror of
https://gitlab.freedesktop.org/libinput/libei.git
synced 2026-01-16 14:40:41 +01:00
proto: add support for requesting devices
Add support for a client to request the creation of a new device from the EIS implementation. This is necessary in situations where the devices created by the EIS implementation are not (or no longer) suitable for the client to function correctly. The primary use-case of this is the upcoming tablet tool support where a client may need to create multiple tablet tools in response to a new physical tool brought into proximity locally. Other use-cases include a client closing a device but requiring that device (or one with similar capabilities) later. The implementation in libei is straightforward - on the client side we have a new function to request the new device: ei_seat_request_device_with_capabilities() - on the server side we have a new event EIS_EVENT_SEAT_DEVICE_REQUESTED that can make use of the existing eis_event_seat_has_capability() API Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/345>
This commit is contained in:
parent
08d7d5918f
commit
5f9e181073
13 changed files with 202 additions and 15 deletions
|
|
@ -420,7 +420,7 @@
|
|||
<!-- ei_pingpong events version 1 -->
|
||||
</interface>
|
||||
|
||||
<interface name="ei_seat" version="1">
|
||||
<interface name="ei_seat" version="2">
|
||||
<description summary="set of input devices that logically belong together">
|
||||
An ei_seat represents a set of input devices that logically belong together. In most
|
||||
cases only one seat is present and all input devices on that seat share the same
|
||||
|
|
@ -470,6 +470,39 @@
|
|||
<arg name="capabilities" type="uint64" summary="bitmask of the capabilities"/>
|
||||
</request>
|
||||
|
||||
<!-- ei_seat client requests version 2 -->
|
||||
|
||||
<request name="request_device" since="2">
|
||||
<description summary="Request a new device with capabilities">
|
||||
Request a new device from the EIS implementation with the given capability
|
||||
mask. If the EIS implementation creates a new device in response to this
|
||||
request that device is announced via the ei_seat.device event as any other
|
||||
device.
|
||||
|
||||
The capabilities argument is a bitmask of one or more of the
|
||||
masks representing an interface as provided in the ei_seat.capability event.
|
||||
See the ei_seat.capability event documentation for examples.
|
||||
|
||||
The newly created device(s), if any, may not provide all requested capabilities.
|
||||
The EIS implementation may create multiple devices to accommodate all requested
|
||||
capabilities. For example, a client requesting a device with the ei_keyboard and
|
||||
ei_pointer capability may instead see one device with the ei_keyboard and one
|
||||
device with the ei_pointer capability being created.
|
||||
|
||||
If possible, the EIS implementation should create devices immediately in
|
||||
response to this request so a client can utilize ei_connection.sync to
|
||||
help identify those devices. This is a suggestion to help the client and not
|
||||
a requirement.
|
||||
|
||||
Requesting masks that are not supported in the ei_device's interface version
|
||||
is a client bug and may result in disconnection.
|
||||
|
||||
Requesting masks that are not currently bound via the most recent ei_seat.bind
|
||||
is a client bug and may result in disconnection.
|
||||
</description>
|
||||
<arg name="capabilities" type="uint64" summary="bitmask of the capabilities"/>
|
||||
</request>
|
||||
|
||||
<!-- ei_seat events version 1 -->
|
||||
|
||||
<event name="destroyed" type="destructor" since="1">
|
||||
|
|
|
|||
|
|
@ -349,3 +349,37 @@ ei_seat_unbind_capabilities(struct ei_seat *seat, ...)
|
|||
|
||||
ei_seat_send_bind(seat, seat->capabilities.bound);
|
||||
}
|
||||
|
||||
_public_ void
|
||||
ei_seat_request_device_with_capabilities(struct ei_seat *seat, ...)
|
||||
{
|
||||
switch (seat->state) {
|
||||
case EI_SEAT_STATE_DONE:
|
||||
break;
|
||||
case EI_SEAT_STATE_NEW:
|
||||
case EI_SEAT_STATE_REMOVED:
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t mask = 0;
|
||||
enum ei_device_capability cap;
|
||||
|
||||
va_list args;
|
||||
va_start(args, seat);
|
||||
while ((cap = va_arg(args, enum ei_device_capability)) > 0) {
|
||||
mask_add(mask, ei_seat_cap_mask(seat, cap));
|
||||
}
|
||||
va_end(args);
|
||||
|
||||
if (mask == 0)
|
||||
return;
|
||||
|
||||
/* Check if requested capabilities are a subset of bound capabilities */
|
||||
if (!mask_all(seat->capabilities.bound, mask)) {
|
||||
struct ei *ei = ei_seat_get_context(seat);
|
||||
log_bug_client(ei, "Requested capabilities are not a subset of the bound capabilities");
|
||||
return;
|
||||
}
|
||||
|
||||
ei_seat_request_request_device(seat, mask);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ ei_create_context(bool is_sender, void *user_data)
|
|||
.ei_handshake = VERSION_V(1),
|
||||
.ei_callback = VERSION_V(1),
|
||||
.ei_pingpong = VERSION_V(1),
|
||||
.ei_seat = VERSION_V(1),
|
||||
.ei_seat = VERSION_V(2),
|
||||
.ei_device = VERSION_V(3),
|
||||
.ei_pointer = VERSION_V(1),
|
||||
.ei_pointer_absolute = VERSION_V(1),
|
||||
|
|
|
|||
25
src/libei.h
25
src/libei.h
|
|
@ -1089,6 +1089,31 @@ void
|
|||
ei_seat_unbind_capabilities(struct ei_seat *seat, ...)
|
||||
__attribute__((sentinel));
|
||||
|
||||
/**
|
||||
* @ingroup libei-seat
|
||||
*
|
||||
* Request a new device with (a subset of) the given capabilities from the EIS
|
||||
* implementation. If the EIS implementation creates a device in response
|
||||
* to this request the device will arrive via an event of type @ref
|
||||
* EI_EVENT_DEVICE_ADDED.
|
||||
*
|
||||
* The device's capabilities may not match the requested capabilities. For
|
||||
* example requesting a pointer + keyboard device may result in the creation
|
||||
* of a pointer-only device or it may result in the creation of a pointer +
|
||||
* keyboard + touch device. It is up to the caller to handle capability
|
||||
* mismatches.
|
||||
*
|
||||
* This function should be used by a caller when the current set of devices
|
||||
* is insufficient for the functionality the client requires. For example
|
||||
* a client that has previously called ei_device_close() on a device may
|
||||
* need a device again with similar capabilities.
|
||||
*
|
||||
* The capabilities must be a subset of the capabilities requested in
|
||||
* ei_seat_bind_capabilities().
|
||||
*/
|
||||
void
|
||||
ei_seat_request_device_with_capabilities(struct ei_seat *seat, ...)
|
||||
__attribute__((sentinel));
|
||||
|
||||
/**
|
||||
* @ingroup libei-seat
|
||||
|
|
|
|||
|
|
@ -521,7 +521,7 @@ eis_client_new(struct eis *eis, int fd)
|
|||
.ei_handshake = VERSION_V(1),
|
||||
.ei_callback = VERSION_V(1),
|
||||
.ei_pingpong = VERSION_V(1),
|
||||
.ei_seat = VERSION_V(1),
|
||||
.ei_seat = VERSION_V(2),
|
||||
.ei_device = flag_is_set(eis->flags, EIS_FLAG_DEVICE_READY) ? VERSION_V(3) : VERSION_V(2),
|
||||
.ei_pointer = VERSION_V(1),
|
||||
.ei_pointer_absolute = VERSION_V(1),
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ eis_event_destroy(struct eis_event *event)
|
|||
case EIS_EVENT_CLIENT_CONNECT:
|
||||
case EIS_EVENT_CLIENT_DISCONNECT:
|
||||
case EIS_EVENT_SEAT_BIND:
|
||||
case EIS_EVENT_SEAT_DEVICE_REQUESTED:
|
||||
case EIS_EVENT_DEVICE_CLOSED:
|
||||
case EIS_EVENT_DEVICE_START_EMULATING:
|
||||
case EIS_EVENT_DEVICE_STOP_EMULATING:
|
||||
|
|
@ -208,7 +209,10 @@ eis_event_pong_get_ping(struct eis_event *event)
|
|||
_public_ bool
|
||||
eis_event_seat_has_capability(struct eis_event *event, enum eis_device_capability cap)
|
||||
{
|
||||
require_event_type(event, false, EIS_EVENT_SEAT_BIND);
|
||||
require_event_type(event,
|
||||
false,
|
||||
EIS_EVENT_SEAT_BIND,
|
||||
EIS_EVENT_SEAT_DEVICE_REQUESTED);
|
||||
|
||||
switch (cap) {
|
||||
case EIS_DEVICE_CAP_POINTER:
|
||||
|
|
|
|||
|
|
@ -103,6 +103,9 @@ eis_queue_sync_event(struct eis_client *client, object_id_t newid, uint32_t vers
|
|||
void
|
||||
eis_queue_seat_bind_event(struct eis_seat *seat, uint32_t capabilities);
|
||||
|
||||
void
|
||||
eis_queue_seat_device_requested_event(struct eis_seat *seat, uint32_t capabilities);
|
||||
|
||||
void
|
||||
eis_queue_device_closed_event(struct eis_device *device);
|
||||
|
||||
|
|
|
|||
|
|
@ -86,15 +86,11 @@ client_msg_release(struct eis_seat *seat)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static struct brei_result *
|
||||
client_msg_bind(struct eis_seat *seat, uint64_t caps)
|
||||
static inline uint32_t
|
||||
convert_capabilities(uint64_t caps)
|
||||
{
|
||||
uint32_t capabilities = 0;
|
||||
|
||||
if (caps & ~seat->capabilities.proto_mask)
|
||||
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_VALUE,
|
||||
"Invalid capabilities %#" PRIx64, caps);
|
||||
|
||||
/* Convert from protocol capabilities to our C API capabilities */
|
||||
if (caps & bit(EIS_POINTER_INTERFACE_INDEX))
|
||||
capabilities |= EIS_DEVICE_CAP_POINTER;
|
||||
|
|
@ -109,14 +105,41 @@ client_msg_bind(struct eis_seat *seat, uint64_t caps)
|
|||
if (caps & bit(EIS_SCROLL_INTERFACE_INDEX))
|
||||
capabilities |= EIS_DEVICE_CAP_SCROLL;
|
||||
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
static struct brei_result *
|
||||
client_msg_bind(struct eis_seat *seat, uint64_t caps)
|
||||
{
|
||||
if (caps & ~seat->capabilities.proto_mask)
|
||||
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_VALUE,
|
||||
"Invalid capabilities %#" PRIx64, caps);
|
||||
|
||||
uint32_t capabilities = convert_capabilities(caps);
|
||||
|
||||
eis_seat_bind(seat, capabilities);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct brei_result *
|
||||
client_msg_request_device(struct eis_seat *seat, uint64_t caps)
|
||||
{
|
||||
if (caps == 0 || caps & ~seat->capabilities.proto_mask)
|
||||
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_VALUE,
|
||||
"Invalid capabilities %#" PRIx64, caps);
|
||||
|
||||
uint32_t capabilities = convert_capabilities(caps);
|
||||
|
||||
eis_queue_seat_device_requested_event(seat, capabilities);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct eis_seat_interface interface = {
|
||||
.release = client_msg_release,
|
||||
.bind = client_msg_bind,
|
||||
.request_device = client_msg_request_device,
|
||||
};
|
||||
|
||||
const struct eis_seat_interface *
|
||||
|
|
|
|||
11
src/libeis.c
11
src/libeis.c
|
|
@ -137,6 +137,7 @@ eis_event_type_to_string(enum eis_event_type type)
|
|||
CASE_RETURN_STRING(EIS_EVENT_CLIENT_CONNECT);
|
||||
CASE_RETURN_STRING(EIS_EVENT_CLIENT_DISCONNECT);
|
||||
CASE_RETURN_STRING(EIS_EVENT_SEAT_BIND);
|
||||
CASE_RETURN_STRING(EIS_EVENT_SEAT_DEVICE_REQUESTED);
|
||||
CASE_RETURN_STRING(EIS_EVENT_DEVICE_CLOSED);
|
||||
CASE_RETURN_STRING(EIS_EVENT_DEVICE_READY);
|
||||
CASE_RETURN_STRING(EIS_EVENT_PONG);
|
||||
|
|
@ -268,6 +269,16 @@ eis_queue_seat_bind_event(struct eis_seat *seat, uint32_t capabilities)
|
|||
eis_queue_event(e);
|
||||
}
|
||||
|
||||
void
|
||||
eis_queue_seat_device_requested_event(struct eis_seat *seat,
|
||||
uint32_t capabilities)
|
||||
{
|
||||
struct eis_event *e = eis_event_new_for_seat(seat);
|
||||
e->type = EIS_EVENT_SEAT_DEVICE_REQUESTED;
|
||||
e->bind.capabilities = capabilities;
|
||||
eis_queue_event(e);
|
||||
}
|
||||
|
||||
void
|
||||
eis_queue_device_closed_event(struct eis_device *device)
|
||||
{
|
||||
|
|
|
|||
17
src/libeis.h
17
src/libeis.h
|
|
@ -286,6 +286,14 @@ enum eis_event_type {
|
|||
*/
|
||||
EIS_EVENT_DEVICE_READY,
|
||||
|
||||
/**
|
||||
* The client requested a device with the given capabilities
|
||||
* on this seat.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
EIS_EVENT_SEAT_DEVICE_REQUESTED,
|
||||
|
||||
/**
|
||||
* Returned in response to eis_ping().
|
||||
*/
|
||||
|
|
@ -779,11 +787,12 @@ struct eis_ping *
|
|||
eis_event_pong_get_ping(struct eis_event *event);
|
||||
|
||||
/**
|
||||
* For an event of type @ref EIS_EVENT_SEAT_BIND, return the capabilities
|
||||
* requested by the client.
|
||||
* For an event of type @ref EIS_EVENT_SEAT_BIND or @ref
|
||||
* EIS_EVENT_SEAT_DEVICE_REQUESTED, return the capabilities requested by the
|
||||
* client.
|
||||
*
|
||||
* This is the set of *all* capabilities bound by the client as of this event,
|
||||
* not just the changed ones.
|
||||
* For events of type @ref EIS_EVENT_SEAT_BIND this is the set of *all*
|
||||
* capabilities bound by the client as of this event, not just the changed ones.
|
||||
*/
|
||||
bool
|
||||
eis_event_seat_has_capability(struct eis_event *event, enum eis_device_capability cap);
|
||||
|
|
|
|||
|
|
@ -1117,6 +1117,8 @@ _peck_dispatch_eis(struct peck *peck, int lineno)
|
|||
else
|
||||
process_event = tristate_no;
|
||||
break;
|
||||
case EIS_EVENT_SEAT_DEVICE_REQUESTED:
|
||||
break;
|
||||
case EIS_EVENT_DEVICE_CLOSED:
|
||||
if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_HANDLE_CLOSE_DEVICE))
|
||||
process_event = tristate_yes;
|
||||
|
|
@ -1686,6 +1688,7 @@ peck_eis_event_type_name(enum eis_event_type type)
|
|||
CASE_STRING(CLIENT_CONNECT);
|
||||
CASE_STRING(CLIENT_DISCONNECT);
|
||||
CASE_STRING(SEAT_BIND);
|
||||
CASE_STRING(SEAT_DEVICE_REQUESTED);
|
||||
CASE_STRING(DEVICE_CLOSED);
|
||||
CASE_STRING(PONG);
|
||||
CASE_STRING(SYNC);
|
||||
|
|
|
|||
|
|
@ -173,3 +173,43 @@ MUNIT_TEST(test_ei_seat_bind_unbind_immediately)
|
|||
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
MUNIT_TEST(test_ei_seat_request_device)
|
||||
{
|
||||
_unref_(peck) *peck = peck_new();
|
||||
|
||||
peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
|
||||
|
||||
peck_dispatch_until_stable(peck);
|
||||
|
||||
with_client(peck) {
|
||||
struct ei_seat *seat = peck_ei_get_default_seat(peck);
|
||||
ei_seat_request_device_with_capabilities(seat,
|
||||
EI_DEVICE_CAP_POINTER,
|
||||
EI_DEVICE_CAP_KEYBOARD,
|
||||
NULL);
|
||||
ei_seat_request_device_with_capabilities(seat,
|
||||
EI_DEVICE_CAP_TOUCH,
|
||||
EI_DEVICE_CAP_POINTER_ABSOLUTE,
|
||||
NULL);
|
||||
}
|
||||
|
||||
peck_dispatch_until_stable(peck);
|
||||
|
||||
with_server(peck) {
|
||||
_unref_(eis_event) *e1 = peck_eis_next_event(eis, EIS_EVENT_SEAT_DEVICE_REQUESTED);
|
||||
_unref_(eis_event) *e2 = peck_eis_next_event(eis, EIS_EVENT_SEAT_DEVICE_REQUESTED);
|
||||
|
||||
munit_assert(eis_event_seat_has_capability(e1, EIS_DEVICE_CAP_POINTER));
|
||||
munit_assert(eis_event_seat_has_capability(e1, EIS_DEVICE_CAP_KEYBOARD));
|
||||
munit_assert(!eis_event_seat_has_capability(e1, EIS_DEVICE_CAP_TOUCH));
|
||||
munit_assert(!eis_event_seat_has_capability(e1, EIS_DEVICE_CAP_POINTER_ABSOLUTE));
|
||||
|
||||
munit_assert(!eis_event_seat_has_capability(e2, EIS_DEVICE_CAP_POINTER));
|
||||
munit_assert(!eis_event_seat_has_capability(e2, EIS_DEVICE_CAP_KEYBOARD));
|
||||
munit_assert(eis_event_seat_has_capability(e2, EIS_DEVICE_CAP_TOUCH));
|
||||
munit_assert(eis_event_seat_has_capability(e2, EIS_DEVICE_CAP_POINTER_ABSOLUTE));
|
||||
}
|
||||
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -590,7 +590,9 @@ class TestEiProtocol:
|
|||
|
||||
assert ei.seats
|
||||
for seat in ei.seats:
|
||||
assert seat.version == 1 # we have 100, but the server only has 1
|
||||
assert seat.version == VERSION_V(
|
||||
2
|
||||
) # we have 100, but the server only has 1
|
||||
for call in seat.calllog:
|
||||
if call.name == "Capability":
|
||||
assert call.args["mask"].bit_count() == 1
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue