/* 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 #include #include "util-bits.h" #include "util-io.h" #include "util-macros.h" #include "util-mem.h" #include "util-sources.h" #include "util-strings.h" #include "util-structs.h" #include "util-tristate.h" #include "util-time.h" #include "libeis-private.h" #include "libeis-proto.h" #include "brei-shared.h" #include "eis-proto.h" DEFINE_TRISTATE(started, finished, connected); static void eis_client_destroy(struct eis_client *client) { eis_connection_unref(client->connection); free(client->name); source_remove(client->source); source_unref(client->source); list_remove(&client->link); } static OBJECT_IMPLEMENT_CREATE(eis_client); static OBJECT_IMPLEMENT_PARENT(eis_client, eis); _public_ OBJECT_IMPLEMENT_REF(eis_client); _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_client); _public_ OBJECT_IMPLEMENT_GETTER(eis_client, name, const char*); _public_ OBJECT_IMPLEMENT_SETTER(eis_client, user_data, void*); _public_ OBJECT_IMPLEMENT_GETTER(eis_client, user_data, void*); _public_ struct eis* eis_client_get_context(struct eis_client *client) { return eis_client_parent(client); } const struct brei_object * eis_client_get_proto_object(struct eis_client *client) { return &client->connection->proto_object; } uint32_t eis_client_get_new_id(struct eis_client *client) { static const int offset = 0xff000000; return offset | (client->next_object_id++ & ~offset); } void eis_client_register_object(struct eis_client *client, struct brei_object *object) { struct eis *eis = eis_client_get_context(client); log_debug(eis, "registering %s v%u object %#x", object->interface->name, object->version, object->id); list_append(&client->proto_objects, &object->link); } void eis_client_unregister_object(struct eis_client *client, struct brei_object *object) { struct eis *eis = eis_client_get_context(client); log_debug(eis, "deregistering %s v%u object %#x", object->interface->name, object->version, object->id); list_remove(&object->link); } struct eis_client * eis_client_get_client(struct eis_client *client) { return client; } _public_ bool eis_client_is_sender(struct eis_client *client) { return client->is_sender; } _public_ bool eis_client_has_capability(struct eis_client *client, enum eis_device_capability cap) { switch (cap) { case EIS_DEVICE_CAP_POINTER: case EIS_DEVICE_CAP_POINTER_ABSOLUTE: case EIS_DEVICE_CAP_KEYBOARD: case EIS_DEVICE_CAP_TOUCH: return flag_is_set(client->restrictions.cap_allow_mask, cap); } /* Quietly ignore other capabilities, we don't want to future-proof this */ if (cap == 0) log_bug_client(eis_client_get_context(client), "Check for invalid capability 0"); return false; } static struct eis_device * eis_client_find_device(struct eis_client *client, uint32_t deviceid) { struct eis_seat *seat; list_for_each(seat, &client->seats, link) { struct eis_device *device; list_for_each(device, &seat->devices, link) { if (device->id == deviceid) return device; } } return NULL; } static struct eis_seat * eis_client_find_seat(struct eis_client *client, uint32_t seatid) { struct eis_seat *seat; list_for_each(seat, &client->seats, link) { if (seat->id == seatid) return seat; } return NULL; } int eis_client_send_message(struct eis_client *client, uint32_t object_id, uint32_t opcode, const char *signature, size_t nargs, ...) { struct eis *eis = eis_client_get_context(client); int fd = source_get_fd(client->source); log_debug(eis, "sending: %#x:%u signature '%s'", object_id, opcode, signature); va_list args; va_start(args, nargs); int rc = brei_send_message(fd, object_id, opcode, signature, nargs, args); va_end(args); return rc < 0 ? rc : 0; } static int client_send_version(struct eis_client *client, uint32_t version) { return eis_connection_event_version(client->connection, version); } static int client_send_disconnect(struct eis_client *client) { return eis_connection_event_disconnected(client->connection); } static int client_send_connect(struct eis_client *client) { return eis_connection_event_connected(client->connection); } static int client_send_seat_added(struct eis_client *client, struct eis_seat *seat) { return eis_connection_event_seat_added(client->connection, seat->id, seat->capabilities_mask, seat->name); } static int client_send_seat_removed(struct eis_client *client, struct eis_seat *seat) { return eis_connection_event_seat_removed(client->connection, seat->id); } static int client_send_device_added(struct eis_client *client, struct eis_device *device) { struct eis_seat *seat = eis_device_get_seat(device); int rc = eis_connection_event_device_added(client->connection, device->id, device->capabilities, device->name, seat->id, device->type, device->width, device->height); if (rc >= 0 && device->keymap) rc = eis_connection_event_device_keymap(client->connection, device->id, device->keymap->type, device->keymap->size, device->keymap->fd); if (rc >= 0 && device->type == EIS_DEVICE_TYPE_VIRTUAL) { struct eis_region *r; list_for_each(r, &device->regions, link) { rc = eis_connection_event_device_region(client->connection, device->id, r->x, r->y, r->width, r->height, r->physical_scale); } } if (rc >= 0) rc = eis_connection_event_device_done(client->connection, device->id); return rc; } static int client_send_device_removed(struct eis_client *client, struct eis_device *device) { return eis_connection_event_device_removed(client->connection, device->id); } static int client_send_device_paused(struct eis_client *client, struct eis_device *device) { return eis_connection_event_device_paused(client->connection, device->id); } static int client_send_device_resumed(struct eis_client *client, struct eis_device *device) { return eis_connection_event_device_resumed(client->connection, device->id); } static int client_send_keyboard_modifiers(struct eis_client *client, struct eis_device *device, const struct eis_xkb_modifiers *mods) { return eis_connection_event_keyboard_modifiers(client->connection, device->id, mods->depressed, mods->locked, mods->latched, mods->group); } _public_ void eis_client_connect(struct eis_client *client) { switch(client->state) { case EIS_CLIENT_STATE_DISCONNECTED: return; case EIS_CLIENT_STATE_CONNECTING: break; default: log_bug_client(eis_client_get_context(client), "%s: client already connected", __func__); return; } int rc = client_send_connect(client); if (rc) { log_debug(eis_client_get_context(client), "Message failed to send: %s", strerror(-rc)); eis_client_disconnect(client); } else { client->state = EIS_CLIENT_STATE_CONNECTED; } } _public_ void eis_client_disconnect(struct eis_client *client) { switch(client->state) { case EIS_CLIENT_STATE_DISCONNECTED: /* Client already disconnected? don't bother sending an * event */ return; case EIS_CLIENT_STATE_CONNECTING: case EIS_CLIENT_STATE_CONNECTED: { struct eis_seat *s; list_for_each_safe(s, &client->seats, link) { eis_seat_drop(s); } } eis_queue_disconnect_event(client); _fallthrough_; case EIS_CLIENT_STATE_NEW: client_send_disconnect(client); client->state = EIS_CLIENT_STATE_DISCONNECTED; source_remove(client->source); break; } eis_client_unref(client); } static int client_msg_close_device(struct eis_connection *connection, uint32_t deviceid) { struct eis_client *client = eis_connection_get_client(connection); struct eis_device *device = eis_client_find_device(client, deviceid); if (device) eis_device_closed_by_client(device); return 0; } static int client_msg_bind_seat(struct eis_connection *connection, uint32_t seatid, uint32_t caps) { struct eis_client *client = eis_connection_get_client(connection); struct eis_seat *seat = eis_client_find_seat(client, seatid); if (seat) eis_seat_bind(seat, caps); return seat ? 0 : -EINVAL; } #define DISCONNECT_IF_RECEIVER_CONTEXT(client_) do { \ if (!(client_)->is_sender) { \ struct eis *_ctx = eis_client_get_context(client_); \ log_bug_client(_ctx, "Invalid event from receiver ei context. Disconnecting client"); \ return -EINVAL; \ } \ } while(0) static int client_msg_start_emulating(struct eis_connection *connection, uint32_t deviceid, uint32_t sequence) { struct eis_client *client = eis_connection_get_client(connection); DISCONNECT_IF_RECEIVER_CONTEXT(client); struct eis_device *device = eis_client_find_device(client, deviceid); if (device) eis_device_event_start_emulating(device, sequence); return 0; } static int client_msg_stop_emulating(struct eis_connection *connection, uint32_t deviceid) { struct eis_client *client = eis_connection_get_client(connection); DISCONNECT_IF_RECEIVER_CONTEXT(client); struct eis_device *device = eis_client_find_device(client, deviceid); if (device) eis_device_event_stop_emulating(device); return 0; } static int client_msg_frame(struct eis_connection *connection, uint32_t deviceid, uint32_t time, uint32_t micros) { struct eis_client *client = eis_connection_get_client(connection); DISCONNECT_IF_RECEIVER_CONTEXT(client); struct eis_device *device = eis_client_find_device(client, deviceid); if (device) return eis_device_event_frame(device, ms2us(time) + micros); return 0; } static int client_msg_pointer_rel(struct eis_connection *connection, uint32_t deviceid, float x, float y) { struct eis_client *client = eis_connection_get_client(connection); DISCONNECT_IF_RECEIVER_CONTEXT(client); struct eis_device *device = eis_client_find_device(client, deviceid); if (device) return eis_device_event_pointer_rel(device, x, y); return -EINVAL; } static int client_msg_pointer_abs(struct eis_connection *connection, uint32_t deviceid, float x, float y) { struct eis_client *client = eis_connection_get_client(connection); DISCONNECT_IF_RECEIVER_CONTEXT(client); struct eis_device *device = eis_client_find_device(client, deviceid); if (device) return eis_device_event_pointer_abs(device, x, y); return -EINVAL; } static int client_msg_pointer_button(struct eis_connection *connection, uint32_t deviceid, uint32_t button, uint32_t state) { struct eis_client *client = eis_connection_get_client(connection); DISCONNECT_IF_RECEIVER_CONTEXT(client); struct eis_device *device = eis_client_find_device(client, deviceid); if (device) return eis_device_event_pointer_button(device, button, !!state); return -EINVAL; } static int client_msg_pointer_scroll(struct eis_connection *connection, uint32_t deviceid, float x, float y) { struct eis_client *client = eis_connection_get_client(connection); DISCONNECT_IF_RECEIVER_CONTEXT(client); struct eis_device *device = eis_client_find_device(client, deviceid); if (device) return eis_device_event_pointer_scroll(device, x, y); return -EINVAL; } static int client_msg_pointer_scroll_discrete(struct eis_connection *connection, uint32_t deviceid, int32_t x, int32_t y) { struct eis_client *client = eis_connection_get_client(connection); DISCONNECT_IF_RECEIVER_CONTEXT(client); struct eis_device *device = eis_client_find_device(client, deviceid); if (device) return eis_device_event_pointer_scroll_discrete(device, x, y); return -EINVAL; } static int client_msg_pointer_scroll_stop(struct eis_connection *connection, uint32_t deviceid, uint32_t x, uint32_t y, uint32_t is_cancel) { struct eis_client *client = eis_connection_get_client(connection); DISCONNECT_IF_RECEIVER_CONTEXT(client); struct eis_device *device = eis_client_find_device(client, deviceid); if (device) { if (is_cancel) return eis_device_event_pointer_scroll_cancel(device, !!x, !!y); else return eis_device_event_pointer_scroll_stop(device, !!x, !!y); } return -EINVAL; } static int client_msg_keyboard_key(struct eis_connection *connection, uint32_t deviceid, uint32_t key, uint32_t state) { struct eis_client *client = eis_connection_get_client(connection); DISCONNECT_IF_RECEIVER_CONTEXT(client); struct eis_device *device = eis_client_find_device(client, deviceid); if (device) return eis_device_event_keyboard_key(device, key, !!state); return -EINVAL; } static int client_msg_touch_down(struct eis_connection *connection, uint32_t deviceid, uint32_t touchid, float x, float y) { struct eis_client *client = eis_connection_get_client(connection); DISCONNECT_IF_RECEIVER_CONTEXT(client); struct eis_device *device = eis_client_find_device(client, deviceid); if (device) return eis_device_event_touch_down(device, touchid, x, y); return -EINVAL; } static int client_msg_touch_motion(struct eis_connection *connection, uint32_t deviceid, uint32_t touchid, float x, float y) { struct eis_client *client = eis_connection_get_client(connection); DISCONNECT_IF_RECEIVER_CONTEXT(client); struct eis_device *device = eis_client_find_device(client, deviceid); if (device) return eis_device_event_touch_motion(device, touchid, x, y); return -EINVAL; } static int client_msg_touch_up(struct eis_connection *connection, uint32_t deviceid, uint32_t touchid) { struct eis_client *client = eis_connection_get_client(connection); DISCONNECT_IF_RECEIVER_CONTEXT(client); struct eis_device *device = eis_client_find_device(client, deviceid); if (device) return eis_device_event_touch_up(device, touchid); return -EINVAL; } static int client_msg_connect(struct eis_connection *connection, uint32_t version, const char *name, uint32_t is_sender) { struct eis_client *client = eis_connection_get_client(connection); if (client->version > EIS_PROTOCOL_VERSION) return -EPROTO; client->version = version; if (client->name == NULL) client->name = xstrdup(name); client->is_sender = !!is_sender; return 0; } static int client_msg_connect_done(struct eis_connection *connection) { struct eis_client *client = eis_connection_get_client(connection); eis_queue_connect_event(client); client->state = EIS_CLIENT_STATE_CONNECTING; return 0; } static int client_msg_disconnect(struct eis_connection *connection) { return -ECANCELED; } static int client_msg_get_version(struct eis_connection *connection) { struct eis_client *client = eis_connection_get_client(connection); return client_send_version(client, EI_PROTOCOL_VERSION); } static const struct eis_connection_interface intf_state_new = { .connect = client_msg_connect, .connect_done = client_msg_connect_done, .disconnect = client_msg_disconnect, .get_version = client_msg_get_version, }; /* Client is waiting for us, shouldn't send anything except disconnect */ static const struct eis_connection_interface intf_state_connecting = { .disconnect = client_msg_disconnect, .get_version = client_msg_get_version, }; static const struct eis_connection_interface intf_state_connected = { .disconnect = client_msg_disconnect, .bind_seat = client_msg_bind_seat, .close_device = client_msg_close_device, .get_version = client_msg_get_version, /* events */ .start_emulating = client_msg_start_emulating, .stop_emulating = client_msg_stop_emulating, .pointer_relative = client_msg_pointer_rel, .pointer_absolute = client_msg_pointer_abs, .pointer_button = client_msg_pointer_button, .pointer_scroll = client_msg_pointer_scroll, .pointer_scroll_stop = client_msg_pointer_scroll_stop, .pointer_scroll_discrete = client_msg_pointer_scroll_discrete, .keyboard_key = client_msg_keyboard_key, .touch_down = client_msg_touch_down, .touch_motion = client_msg_touch_motion, .touch_up = client_msg_touch_up, .frame = client_msg_frame, }; static const struct eis_connection_interface *interfaces[] = { [EIS_CLIENT_STATE_NEW] = &intf_state_new, [EIS_CLIENT_STATE_CONNECTING] = &intf_state_connecting, [EIS_CLIENT_STATE_CONNECTED] = &intf_state_connected, [EIS_CLIENT_STATE_DISCONNECTED] = NULL, }; const struct eis_connection_interface * eis_client_get_interface(struct eis_client *client) { assert(client->state < ARRAY_LENGTH(interfaces)); return interfaces[client->state]; } static int lookup_object(uint32_t object_id, struct brei_object **object, void *userdata) { struct eis_client *client = userdata; struct brei_object *obj; list_for_each(obj, &client->proto_objects, link) { if (obj->id == object_id) { *object = obj; return 0; } } return -ENOENT; } static void client_dispatch(struct source *source, void *userdata) { _unref_(eis_client) *client = eis_client_ref(userdata); enum eis_client_state old_state = client->state; int rc = brei_dispatch(source_get_fd(source), lookup_object, client); if (rc < 0) { brei_drain_fd(source_get_fd(source)); eis_client_disconnect(client); } static const char *client_states[] = { "NEW", "CONFIGURING", "CONFIGURED", "CONNECTING", "CONNECTED", "DISCONNECTED", }; if (rc == -ECANCELED) log_info(eis_client_get_context(client), "Disconnected"); else if (rc) log_warn(eis_client_get_context(client), "Client error: %s", strerror(-rc)); if (old_state != client->state) { assert(old_state < ARRAY_LENGTH(client_states)); assert(client->state < ARRAY_LENGTH(client_states)); log_debug(eis_client_get_context(client), "Client dispatch: %s -> %s", client_states[old_state], client_states[client->state]); } } struct eis_client * eis_client_new(struct eis *eis, int fd) { static uint32_t client_id; struct eis_client *client = eis_client_create(&eis->object); client->is_sender = true; client->id = ++client_id; list_init(&client->seats); list_init(&client->seats_pending); list_init(&client->proto_objects); client->connection = eis_connection_new(client); struct source *s = source_new(fd, client_dispatch, client); int rc = sink_add_source(eis->sink, s); if (rc != 0) { source_unref(s); return NULL; } client->source = source_ref(s); client->state = EIS_CLIENT_STATE_NEW; client->restrictions.cap_allow_mask = bit(EIS_DEVICE_CAP_KEYBOARD) | bit(EIS_DEVICE_CAP_POINTER) | bit(EIS_DEVICE_CAP_POINTER_ABSOLUTE) | bit(EIS_DEVICE_CAP_TOUCH); eis_add_client(eis, eis_client_ref(client)); source_unref(s); /* Immediately send our version so the client doesn't need a roundtrip */ client_send_version(client, EI_PROTOCOL_VERSION); return client; } void eis_client_add_seat(struct eis_client *client, struct eis_seat *seat) { /* remove from the pending list first */ list_remove(&seat->link); /* We own this seat now */ eis_seat_ref(seat); list_append(&client->seats, &seat->link); client_send_seat_added(client, seat); } void eis_client_remove_seat(struct eis_client *client, struct eis_seat *seat) { client_send_seat_removed(client, seat); seat->state = EIS_SEAT_STATE_REMOVED; } void eis_client_add_device(struct eis_client *client, struct eis_device *device) { client_send_device_added(client, device); } void eis_client_remove_device(struct eis_client *client, struct eis_device *device) { client_send_device_removed(client, device); } void eis_client_pause_device(struct eis_client *client, struct eis_device *device) { client_send_device_paused(client, device); } void eis_client_resume_device(struct eis_client *client, struct eis_device *device) { client_send_device_resumed(client, device); } void eis_client_keyboard_modifiers(struct eis_client *client, struct eis_device *device, uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group) { struct eis_xkb_modifiers mods = { .depressed = depressed, .locked = locked, .latched = latched, .group = group, }; client_send_keyboard_modifiers(client, device, &mods); }