/* * 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 "libeis-private.h" #include "libeis-proto.h" #include "brei-shared.h" static void eis_client_destroy(struct eis_client *client) { 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(eis_client); #define _cleanup_eis_client_ _cleanup_(eis_client_cleanup) _public_ OBJECT_IMPLEMENT_GETTER(eis_client, name, const char*); _public_ struct eis* eis_client_get_context(struct eis_client *client) { return eis_client_parent(client); } static int client_send_disconnect(struct eis_client *client) { return eis_proto_send_disconnect(client); } static int client_send_connect(struct eis_client *client) { return eis_proto_send_connect(client); } static int client_send_device_added(struct eis_client *client, struct eis_device *device) { return eis_proto_send_device_added(client, device); } static int client_send_device_removed(struct eis_client *client, struct eis_device *device) { return eis_proto_send_device_removed(client, device); } static int client_send_device_suspended(struct eis_client *client, struct eis_device *device) { return eis_proto_send_device_suspended(client, device); } static int client_send_device_resumed(struct eis_client *client, struct eis_device *device) { return eis_proto_send_device_resumed(client, device); } _public_ void eis_client_connect(struct eis_client *client) { switch(client->state) { case EIS_CLIENT_STATE_CONNECTING: break; default: return; } int rc = client_send_connect(client); if (rc) { log_debug(eis_client_get_context(client), "Message failed to send: %s\n", 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_device *d, *tmp; list_for_each_safe(d, tmp, &client->devices, link) eis_device_disconnect(d); } 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_new_device(struct eis_client *client, uint32_t id, const char *name, uint32_t capabilities, const struct dimensions *dim_pointer, const struct dimensions *dim_touch, enum eis_keymap_type keymap_type, int keymap_fd, size_t keymap_sz) { /* Check for duplicate IDs */ struct eis_device *d; list_for_each(d, &client->devices, link) { if (d->id == id) return -EINVAL; } if (capabilities == 0 || capabilities & ~(bit(EIS_DEVICE_CAP_POINTER) | bit(EIS_DEVICE_CAP_POINTER_ABSOLUTE) | bit(EIS_DEVICE_CAP_KEYBOARD) | bit(EIS_DEVICE_CAP_TOUCH))) return -EINVAL; struct eis_device *device = eis_device_new(client, id, name, capabilities); list_append(&client->devices, &device->link); if (flag_is_set(capabilities, EIS_DEVICE_CAP_POINTER_ABSOLUTE)) eis_device_set_pointer_range(device, dim_pointer->width, dim_pointer->height); if (flag_is_set(capabilities, EIS_DEVICE_CAP_TOUCH)) eis_device_set_touch_range(device, dim_touch->width, dim_touch->height); eis_device_set_client_keymap(device, keymap_type, keymap_fd, keymap_sz); log_debug(eis_client_parent(client), "New device %d '%s' caps: %s%s%s%s\n", id, name, eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER) ? "p" : "", eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE) ? "a" : "", eis_device_has_capability(device, EIS_DEVICE_CAP_KEYBOARD) ? "k" : "", eis_device_has_capability(device, EIS_DEVICE_CAP_TOUCH) ? "t" : ""); eis_queue_added_event(device); return 0; } static int client_remove_device(struct eis_client *client, uint32_t deviceid) { struct eis_device *device; list_for_each(device, &client->devices, link) { if (device->id == deviceid) { eis_queue_removed_event(device); break; } } return 0; } static int client_pointer_rel(struct eis_client *client, uint32_t deviceid, double x, double y) { struct eis_device *device; list_for_each(device, &client->devices, link) { if (device->id == deviceid) { return eis_device_pointer_rel(device, x, y); } } return -EINVAL; } static int client_pointer_button(struct eis_client *client, uint32_t deviceid, uint32_t button, bool state) { struct eis_device *device; list_for_each(device, &client->devices, link) { if (device->id == deviceid) { return eis_device_pointer_button(device, button, state); } } return -EINVAL; } static int client_keyboard_key(struct eis_client *client, uint32_t deviceid, uint32_t key, bool state) { struct eis_device *device; list_for_each(device, &client->devices, link) { if (device->id == deviceid) { return eis_device_keyboard_key(device, key, state); } } return -EINVAL; } static int client_configure_name(struct eis_client *client, const char *name) { /* We silently ignore wrong configure messages */ if (client->state != EIS_CLIENT_STATE_NEW || client->name) return 0; client->name = xstrdup(name); return 0; } static int client_configure_caps(struct eis_client *client, bool policy_is_allow, uint32_t allowed_caps, uint32_t denied_caps) { if (policy_is_allow) { /* We can only manage allow masks while we're in * default-allow policy. * first call to set allow masks is taken as-is, from then onwards * we can only restrict */ if (client->restrictions.cap_allow_mask == ~0U) client->restrictions.cap_allow_mask = allowed_caps; else client->restrictions.cap_allow_mask &= allowed_caps; } else { client->restrictions.cap_policy = CLIENT_CAP_POLICY_DENY; } client->restrictions.cap_deny_mask |= denied_caps; /* FIXME: if something is disallowed now, we should disconnect * accordingly. */ return 0; } static int client_new_handle_msg(struct eis_client *client, struct message *msg) { int rc = 0; switch (msg->type) { case MESSAGE_CONNECT: eis_queue_connect_event(client); if (client->name == NULL) client->name = steal(&msg->connect.name); client->state = EIS_CLIENT_STATE_CONNECTING; break; case MESSAGE_DISCONNECT: rc = -ECANCELED; break; case MESSAGE_ADD_DEVICE: case MESSAGE_REMOVE_DEVICE: case MESSAGE_POINTER_REL: case MESSAGE_POINTER_BUTTON: case MESSAGE_KEYBOARD_KEY: rc = -EPROTO; break; case MESSAGE_CONFIGURE_NAME: rc = client_configure_name(client, msg->configure_name.name); break; case MESSAGE_CONFIGURE_CAPABILITIES: rc = client_configure_caps(client, msg->configure_caps.policy_is_allow, msg->configure_caps.caps_allow, msg->configure_caps.caps_deny); break; } return rc; } static int client_connecting_handle_msg(struct eis_client *client, const struct message *msg) { int rc = 0; switch (msg->type) { case MESSAGE_CONNECT: rc = -EPROTO; break; case MESSAGE_DISCONNECT: rc = -ECANCELED; break; case MESSAGE_ADD_DEVICE: case MESSAGE_REMOVE_DEVICE: case MESSAGE_POINTER_REL: case MESSAGE_POINTER_BUTTON: case MESSAGE_KEYBOARD_KEY: rc = -EPROTO; break; case MESSAGE_CONFIGURE_NAME: rc = client_configure_name(client, msg->configure_name.name); break; case MESSAGE_CONFIGURE_CAPABILITIES: rc = client_configure_caps(client, msg->configure_caps.policy_is_allow, msg->configure_caps.caps_allow, msg->configure_caps.caps_deny); break; } return rc; } static int client_connected_handle_msg(struct eis_client *client, const struct message *msg) { int rc = 0; switch (msg->type) { case MESSAGE_CONNECT: rc = -EPROTO; break; case MESSAGE_DISCONNECT: rc = -ECANCELED; break; case MESSAGE_ADD_DEVICE: rc = client_new_device(client, msg->add_device.deviceid, msg->add_device.name, msg->add_device.capabilities, &msg->add_device.dim_pointer, &msg->add_device.dim_touch, msg->add_device.keymap_type, msg->add_device.keymap_fd, msg->add_device.keymap_size); break; case MESSAGE_REMOVE_DEVICE: rc = client_remove_device(client, msg->remove_device.deviceid); break; case MESSAGE_POINTER_REL: rc = client_pointer_rel(client, msg->pointer_rel.deviceid, msg->pointer_rel.x, msg->pointer_rel.y); break; case MESSAGE_POINTER_BUTTON: rc = client_pointer_button(client, msg->pointer_button.deviceid, msg->pointer_button.button, msg->pointer_button.state); break; case MESSAGE_KEYBOARD_KEY: rc = client_keyboard_key(client, msg->keyboard_key.deviceid, msg->keyboard_key.key, msg->keyboard_key.state); break; case MESSAGE_CONFIGURE_NAME: rc = client_configure_name(client, msg->configure_name.name); break; case MESSAGE_CONFIGURE_CAPABILITIES: rc = client_configure_caps(client, msg->configure_caps.policy_is_allow, msg->configure_caps.caps_allow, msg->configure_caps.caps_deny); break; } return rc; } static int client_message_callback(struct brei_message *bmsg, void *userdata) { struct eis_client *client = userdata; size_t consumed; _cleanup_message_ struct message *msg = eis_proto_parse_message(bmsg, &consumed); if (!msg) return -EBADMSG; int rc = 0; switch (client->state) { case EIS_CLIENT_STATE_NEW: rc = client_new_handle_msg(client, msg); break; case EIS_CLIENT_STATE_CONNECTING: /* Client is waiting for us, shouldn't send anything * but disconnect */ rc = client_connecting_handle_msg(client, msg); break; case EIS_CLIENT_STATE_CONNECTED: rc = client_connected_handle_msg(client, msg); break; case EIS_CLIENT_STATE_DISCONNECTED: abort(); } if (rc < 0) return rc; else return consumed; } static void client_dispatch(struct source *source, void *userdata) { _cleanup_eis_client_ struct eis_client *client = eis_client_ref(userdata); enum eis_client_state old_state = client->state; int rc = brei_dispatch(source_get_fd(source), client_message_callback, client); if (rc < 0) eis_client_disconnect(client); static const char *client_states[] = { "NEW", "CONNECTING", "CONNECTED", "DISCONNECTED", }; if (rc == -ECANCELED) log_info(eis_client_parent(client), "Disconnected\n"); else if (rc) log_warn(eis_client_parent(client), "Client error: %s\n", strerror(-rc)); if (old_state != client->state) { log_debug(eis_client_parent(client), "Client dispatch: %s -> %s\n", 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->id = ++client_id; list_init(&client->devices); 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_policy = CLIENT_CAP_POLICY_ALLOW; client->restrictions.cap_allow_mask = ~0U; client->restrictions.cap_deny_mask = 0; eis_add_client(eis, eis_client_ref(client)); source_unref(s); return client; } void eis_client_connect_device(struct eis_client *client, struct eis_device *device) { client_send_device_added(client, device); } void eis_client_disconnect_device(struct eis_client *client, struct eis_device *device) { client_send_device_removed(client, device); eis_queue_removed_event(device); list_remove(&device->link); eis_device_unref(device); } void eis_client_suspend_device(struct eis_client *client, struct eis_device *device) { client_send_device_suspended(client, device); } void eis_client_resume_device(struct eis_client *client, struct eis_device *device) { client_send_device_resumed(client, device); }