/* 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 #include #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; case EI_DEVICE_CAP_TEXT: return seat->capabilities.map[EI_TEXT_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]; case EI_DEVICE_CAP_TEXT: return seat->capabilities.map[EI_TEXT_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); }