libei/src/libei-seat.c
Peter Hutterer 9c5c3c890f ei: de-duplicate ei_seat_unbind_capability
ei_seat_unbind_capabilities does the same, so let's use that.
2023-04-14 10:52:39 +10:00

338 lines
8.3 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-macros.h"
#include "util-mem.h"
#include "util-io.h"
#include "util-strings.h"
#include "libei-private.h"
#include "ei-proto.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_capabilities(struct ei_seat *seat, uint32_t capabilities)
{
if (seat->capabilities != 0)
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"EIS sent the seat capabilities twice");
seat->capabilities = capabilities;
return 0;
}
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' with caps %#x", seat->name, seat->capabilities);
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);
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,
.capabilities = handle_msg_capabilities,
.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:
case EI_DEVICE_CAP_POINTER_ABSOLUTE:
case EI_DEVICE_CAP_KEYBOARD:
case EI_DEVICE_CAP_TOUCH:
return mask_all(seat->capabilities, cap);
}
return false;
}
_public_ void
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)
{
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;
}
_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;
}
uint32_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 &= seat->capabilities;
if (seat->capabilities_bound == mask)
return;
seat->capabilities_bound = mask;
ei_seat_send_bind(seat, seat->capabilities_bound);
}
_public_ void
ei_seat_unbind_capability(struct ei_seat *seat,
enum ei_device_capability cap)
{
ei_seat_unbind_capabilities(seat, cap, NULL);
}
_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;
}
uint32_t old_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);
}
if (seat->capabilities_bound == old_mask)
return;
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);
}