mirror of
https://gitlab.freedesktop.org/libinput/libei.git
synced 2026-04-06 15:30:37 +02:00
clang-format taken from libinput, except for ColumnLimit: 100 and some macro definitions (which don't all have an effect anyway...) It's not perfect but good enough and at least consistent. Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/383>
390 lines
10 KiB
C
390 lines
10 KiB
C
/* SPDX-License-Identifier: MIT */
|
|
/*
|
|
* Copyright © 2020 Red Hat, Inc.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice (including the next
|
|
* paragraph) shall be included in all copies or substantial portions of the
|
|
* Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
|
|
#include "util-bits.h"
|
|
#include "util-io.h"
|
|
#include "util-macros.h"
|
|
#include "util-mem.h"
|
|
#include "util-strings.h"
|
|
|
|
#include "ei-proto.h"
|
|
#include "libei-private.h"
|
|
|
|
static void
|
|
ei_seat_destroy(struct ei_seat *seat)
|
|
{
|
|
free(seat->name);
|
|
}
|
|
|
|
_public_ OBJECT_IMPLEMENT_REF(ei_seat);
|
|
_public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_seat);
|
|
|
|
static OBJECT_IMPLEMENT_CREATE(ei_seat);
|
|
static OBJECT_IMPLEMENT_PARENT(ei_seat, ei);
|
|
_public_
|
|
OBJECT_IMPLEMENT_GETTER(ei_seat, name, const char *);
|
|
_public_
|
|
OBJECT_IMPLEMENT_SETTER(ei_seat, user_data, void *);
|
|
OBJECT_IMPLEMENT_GETTER_AS_REF(ei_seat, proto_object, const struct brei_object *);
|
|
|
|
_public_ struct ei *
|
|
ei_seat_get_context(struct ei_seat *seat)
|
|
{
|
|
assert(seat);
|
|
return ei_seat_parent(seat);
|
|
}
|
|
|
|
object_id_t
|
|
ei_seat_get_id(struct ei_seat *seat)
|
|
{
|
|
return seat->proto_object.id;
|
|
}
|
|
|
|
static struct brei_result *
|
|
handle_msg_destroyed(struct ei_seat *seat, uint32_t serial)
|
|
{
|
|
struct ei *ei = ei_seat_get_context(seat);
|
|
|
|
ei_update_serial(ei, serial);
|
|
|
|
log_debug(ei, "server removed seat %s", seat->name);
|
|
ei_seat_remove(seat);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct brei_result *
|
|
handle_msg_name(struct ei_seat *seat, const char *name)
|
|
{
|
|
if (seat->name != NULL)
|
|
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
|
"EIS sent the seat name twice");
|
|
|
|
seat->name = xstrdup(name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct brei_result *
|
|
handle_msg_capability(struct ei_seat *seat, uint64_t mask, const char *interface)
|
|
{
|
|
struct ei *ei = ei_seat_get_context(seat);
|
|
|
|
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 *
|
|
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'", seat->name);
|
|
|
|
ei_queue_seat_added_event(seat);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#define DISCONNECT_IF_INVALID_ID(seat_, id_) do { \
|
|
if (!brei_is_server_id(id_)) { \
|
|
struct ei *ei_ = ei_seat_get_context(seat_); \
|
|
log_bug(ei_, "Received invalid object id %#" PRIx64 ". Disconnecting", id_); \
|
|
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Received invalid object id %#" PRIx64 ".", id_); \
|
|
} \
|
|
} while(0)
|
|
|
|
static struct brei_result *
|
|
handle_msg_device(struct ei_seat *seat, object_id_t id, uint32_t version)
|
|
{
|
|
DISCONNECT_IF_INVALID_ID(seat, id);
|
|
|
|
struct ei *ei = ei_seat_get_context(seat);
|
|
|
|
DISCONNECT_IF_INVALID_VERSION(ei, ei_device, id, version);
|
|
|
|
log_debug(ei, "Added device %#" PRIx64 "@v%u", id, version);
|
|
/* device is in the seat's device list */
|
|
struct ei_device *device = ei_device_new(seat, id, version);
|
|
/* this list "owns" the ref for this device */
|
|
list_append(&seat->devices, &device->link);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static const struct ei_seat_interface interface = {
|
|
.destroyed = handle_msg_destroyed,
|
|
.name = handle_msg_name,
|
|
.capability = handle_msg_capability,
|
|
.done = handle_msg_done,
|
|
.device = handle_msg_device,
|
|
};
|
|
|
|
const struct ei_seat_interface *
|
|
ei_seat_get_interface(struct ei_seat *seat)
|
|
{
|
|
return &interface;
|
|
}
|
|
|
|
struct ei_seat *
|
|
ei_seat_new(struct ei *ei, object_id_t id, uint32_t version)
|
|
{
|
|
struct ei_seat *seat = ei_seat_create(&ei->object);
|
|
|
|
seat->proto_object.id = id;
|
|
seat->proto_object.implementation = seat;
|
|
seat->proto_object.interface = &ei_seat_proto_interface;
|
|
seat->proto_object.version = version;
|
|
ei_register_object(ei, &seat->proto_object);
|
|
|
|
seat->state = EI_SEAT_STATE_NEW;
|
|
seat->capabilities.bound = 0;
|
|
|
|
list_init(&seat->devices);
|
|
list_init(&seat->devices_removed);
|
|
list_init(&seat->link);
|
|
|
|
return seat; /* ref owned by caller */
|
|
}
|
|
|
|
void
|
|
ei_seat_remove(struct ei_seat *seat)
|
|
{
|
|
/* Sigh, this is terrible and needs to be fixed:
|
|
* if our fd is broken, trying to send any event causes an ei_disconnect(),
|
|
* which eventually calls in here. So we need to guard this function
|
|
* against nested callers. */
|
|
if (seat->state == EI_SEAT_STATE_REMOVED)
|
|
return;
|
|
|
|
struct ei_device *d;
|
|
|
|
/* If the server disconnects us before processing a new device, we
|
|
* need to clean this up in the library */
|
|
list_for_each_safe(d, &seat->devices, link) {
|
|
/* remove the device */
|
|
ei_device_close(d);
|
|
/* And pretend to process the removed message from
|
|
* the server */
|
|
ei_device_removed_by_server(d);
|
|
}
|
|
|
|
/* Check the seat state again, because the above device removal may
|
|
* have triggered ei_disconnect() */
|
|
if (seat->state != EI_SEAT_STATE_REMOVED) {
|
|
seat->state = EI_SEAT_STATE_REMOVED;
|
|
list_remove(&seat->link);
|
|
list_init(&seat->link);
|
|
ei_queue_seat_removed_event(seat);
|
|
|
|
struct ei *ei = ei_seat_get_context(seat);
|
|
ei_unregister_object(ei, &seat->proto_object);
|
|
|
|
ei_seat_unref(seat);
|
|
}
|
|
}
|
|
|
|
_public_ bool
|
|
ei_seat_has_capability(struct ei_seat *seat, enum ei_device_capability cap)
|
|
{
|
|
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 seat->capabilities.map[EI_TOUCHSCREEN_INTERFACE_INDEX] != 0;
|
|
case EI_DEVICE_CAP_SCROLL:
|
|
return seat->capabilities.map[EI_SCROLL_INTERFACE_INDEX] != 0;
|
|
case EI_DEVICE_CAP_BUTTON:
|
|
return seat->capabilities.map[EI_BUTTON_INTERFACE_INDEX] != 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static int
|
|
ei_seat_send_bind(struct ei_seat *seat, uint64_t capabilities)
|
|
{
|
|
struct ei *ei = ei_seat_get_context(seat);
|
|
|
|
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
|
|
return 0;
|
|
|
|
int rc = ei_seat_request_bind(seat, capabilities);
|
|
if (rc)
|
|
ei_disconnect(ei);
|
|
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];
|
|
case EI_DEVICE_CAP_POINTER:
|
|
return seat->capabilities.map[EI_POINTER_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];
|
|
case EI_DEVICE_CAP_BUTTON:
|
|
return seat->capabilities.map[EI_BUTTON_INTERFACE_INDEX];
|
|
case EI_DEVICE_CAP_SCROLL:
|
|
return seat->capabilities.map[EI_SCROLL_INTERFACE_INDEX];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
_public_ void
|
|
ei_seat_bind_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 = 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) {
|
|
mask_add(mask, ei_seat_cap_mask(seat, cap));
|
|
}
|
|
va_end(args);
|
|
|
|
if (seat->capabilities.bound == mask)
|
|
return;
|
|
|
|
seat->capabilities.bound = mask;
|
|
ei_seat_send_bind(seat, seat->capabilities.bound);
|
|
}
|
|
|
|
_public_ void
|
|
ei_seat_unbind_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 = 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) {
|
|
mask_remove(mask, ei_seat_cap_mask(seat, cap));
|
|
}
|
|
va_end(args);
|
|
|
|
if (seat->capabilities.bound == mask)
|
|
return;
|
|
|
|
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))
|
|
ei_device_close(device);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|