/* 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 "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "libei-private.h" static const char * ei_device_state_to_string(enum ei_device_state state) { switch (state) { CASE_RETURN_STRING(EI_DEVICE_STATE_NEW); CASE_RETURN_STRING(EI_DEVICE_STATE_PAUSED); CASE_RETURN_STRING(EI_DEVICE_STATE_RESUMED); CASE_RETURN_STRING(EI_DEVICE_STATE_EMULATING); CASE_RETURN_STRING(EI_DEVICE_STATE_REMOVED_FROM_CLIENT); CASE_RETURN_STRING(EI_DEVICE_STATE_REMOVED_FROM_SERVER); CASE_RETURN_STRING(EI_DEVICE_STATE_DEAD); } assert(!"Unhandled device state"); } static void ei_device_set_state(struct ei_device *device, enum ei_device_state state) { enum ei_device_state old_state = device->state; device->state = state; log_debug(ei_device_get_context(device), "device %#x: %s → %s\n", device->id, ei_device_state_to_string(old_state), ei_device_state_to_string(state)); } static void ei_device_destroy(struct ei_device *device) { struct ei_seat *seat = ei_device_get_seat(device); struct ei_region *region; assert(device->state == EI_DEVICE_STATE_DEAD); list_for_each_safe(region, &device->regions, link) ei_region_unref(region); list_remove(&device->link); ei_keymap_unref(device->keymap); ei_seat_unref(seat); free(device->name); } _public_ OBJECT_IMPLEMENT_REF(ei_device); _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_device); static OBJECT_IMPLEMENT_CREATE(ei_device); static OBJECT_IMPLEMENT_PARENT(ei_device, ei_seat); _public_ OBJECT_IMPLEMENT_GETTER(ei_device, name, const char *); _public_ OBJECT_IMPLEMENT_GETTER(ei_device, user_data, void *); _public_ OBJECT_IMPLEMENT_SETTER(ei_device, user_data, void *); _public_ struct ei_seat * ei_device_get_seat(struct ei_device *device) { return ei_device_parent(device); } _public_ struct ei* ei_device_get_context(struct ei_device *device) { assert(device); return ei_seat_get_context(ei_device_get_seat(device)); } struct ei_device * ei_device_new(struct ei_seat *seat, uint32_t deviceid) { struct ei_device *device = ei_device_create(&seat->object); device->capabilities = 0; device->id = deviceid; device->state = EI_DEVICE_STATE_NEW; device->name = xaprintf("unnamed device %#x", device->id); list_init(&device->regions); /* We have a ref to the seat to make sure our seat doesn't get * destroyed while a ref to the device is still alive. * And the seat has a ref to the device in the seat->devices list. * dropped when the device is removed. */ ei_seat_ref(seat); /* this list "owns" the ref for this device */ ei_device_ref(device); list_append(&seat->devices, &device->link); return device; } void ei_device_done(struct ei_device *device) { ei_device_paused(device); } void ei_device_add_region(struct ei_device *device, struct ei_region *region) { if (device->state != EI_DEVICE_STATE_NEW) return; ei_region_ref(region); list_append(&device->regions, ®ion->link); } _public_ OBJECT_IMPLEMENT_REF(ei_keymap); _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_keymap); _public_ OBJECT_IMPLEMENT_GETTER(ei_keymap, type, enum ei_keymap_type); _public_ OBJECT_IMPLEMENT_GETTER(ei_keymap, fd, int); _public_ OBJECT_IMPLEMENT_GETTER(ei_keymap, size, size_t); _public_ OBJECT_IMPLEMENT_GETTER(ei_keymap, device, struct ei_device *); _public_ OBJECT_IMPLEMENT_GETTER(ei_keymap, user_data, void *); _public_ OBJECT_IMPLEMENT_SETTER(ei_keymap, user_data, void *); static void ei_keymap_destroy(struct ei_keymap *keymap) { xclose(keymap->fd); } static OBJECT_IMPLEMENT_CREATE(ei_keymap); _public_ struct ei_keymap * ei_device_keyboard_get_keymap(struct ei_device *device) { return device->keymap; } static struct ei_keymap * ei_keymap_new(enum ei_keymap_type type, int fd, size_t size) { _unref_(ei_keymap) *keymap = ei_keymap_create(NULL); switch (type) { case EI_KEYMAP_TYPE_XKB: break; default: return NULL; } if (fd < 0 || size == 0) return NULL; int newfd = dup(fd); if (newfd < 0) return NULL; keymap->fd = newfd; keymap->type = type; keymap->size = size; return ei_keymap_ref(keymap); } void ei_device_set_keymap(struct ei_device *device, enum ei_keymap_type type, int keymap_fd, size_t size) { device->keymap = ei_keymap_unref(device->keymap); if (!type) return; _unref_(ei_keymap) *keymap = ei_keymap_new(type, keymap_fd, size); if (!keymap) { log_bug(ei_device_get_context(device), "Failed to apply server-requested keymap"); return; /* FIXME: ei_device_remove() here */ } keymap->device = device; device->keymap = ei_keymap_ref(keymap); } _public_ void ei_device_close(struct ei_device *device) { switch (device->state) { case EI_DEVICE_STATE_NEW: case EI_DEVICE_STATE_DEAD: case EI_DEVICE_STATE_REMOVED_FROM_CLIENT: case EI_DEVICE_STATE_REMOVED_FROM_SERVER: break; case EI_DEVICE_STATE_EMULATING: ei_send_stop_emulating(device); _fallthrough_; case EI_DEVICE_STATE_PAUSED: case EI_DEVICE_STATE_RESUMED: ei_device_set_state(device, EI_DEVICE_STATE_REMOVED_FROM_CLIENT); ei_send_close_device(device); break; } } void ei_device_removed_by_server(struct ei_device *device) { struct ei_seat *seat = ei_device_get_seat(device); switch (device->state) { case EI_DEVICE_STATE_NEW: case EI_DEVICE_STATE_DEAD: case EI_DEVICE_STATE_REMOVED_FROM_SERVER: break; case EI_DEVICE_STATE_REMOVED_FROM_CLIENT: case EI_DEVICE_STATE_PAUSED: case EI_DEVICE_STATE_RESUMED: case EI_DEVICE_STATE_EMULATING: ei_queue_device_removed_event(device); ei_device_set_state(device, EI_DEVICE_STATE_DEAD); /* Device is dead now. Move it from the devices list to * removed and drop the seat's ref to the device. * This should be the last ref to the device that libei has * (not counting any queued events). Device is kept alive by * any client refs but once those drop, the device can be * destroyed. */ list_remove(&device->link); list_append(&seat->devices_removed, &device->link); ei_device_unref(device); break; } } void ei_device_resumed(struct ei_device *device) { ei_device_set_state(device, EI_DEVICE_STATE_RESUMED); } void ei_device_paused(struct ei_device *device) { ei_device_set_state(device, EI_DEVICE_STATE_PAUSED); } void ei_device_added(struct ei_device *device) { } void ei_device_set_name(struct ei_device *device, const char *name) { free(device->name); device->name = xstrdup(name); } void ei_device_set_capabilities(struct ei_device *device, uint32_t capabilities) { device->capabilities = capabilities; } _public_ bool ei_device_has_capability(struct ei_device *device, 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 flag_is_set(device->capabilities, cap); } return false; } _public_ void ei_device_start_emulating(struct ei_device *device) { if (device->state != EI_DEVICE_STATE_RESUMED) return; device->state = EI_DEVICE_STATE_EMULATING; ei_send_start_emulating(device); } _public_ void ei_device_stop_emulating(struct ei_device *device) { if (device->state != EI_DEVICE_STATE_EMULATING) return; device->state = EI_DEVICE_STATE_RESUMED; ei_send_stop_emulating(device); } _public_ struct ei_region * ei_device_get_region(struct ei_device *device, size_t index) { return list_nth_entry(struct ei_region, &device->regions, link, index); } _public_ void ei_device_pointer_motion(struct ei_device *device, double x, double y) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER)) { log_bug_client(ei_device_get_context(device), "%s: device is not a pointer\n", __func__); return; } if (device->state != EI_DEVICE_STATE_EMULATING) return; ei_send_pointer_rel(device, x, y); } _public_ void ei_device_pointer_motion_absolute(struct ei_device *device, double x, double y) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE)) { log_bug_client(ei_device_get_context(device), "%s: device is not an absolute pointer\n", __func__); return; } if (device->state != EI_DEVICE_STATE_EMULATING) return; struct ei_region *r; list_for_each(r, &device->regions, link) { if (!ei_region_contains(r, x, y)) { return; } } ei_send_pointer_abs(device, x, y); } _public_ void ei_device_pointer_button(struct ei_device *device, uint32_t button, bool is_press) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER)) { log_bug_client(ei_device_get_context(device), "%s: device is not a pointer\n", __func__); return; } if (device->state != EI_DEVICE_STATE_EMULATING) return; /* Ignore anything < BTN_MOUSE. Avoids the common error of sending * numerical buttons instead of BTN_LEFT and friends. */ if (button < 0x110) { log_bug_client(ei_device_get_context(device), "%s: button code must be one of BTN_*\n", __func__); return; } ei_send_pointer_button(device, button, is_press); } static inline void ei_device_resume_scrolling(struct ei_device *device, double x, double y) { if (x) { device->scroll.x_is_stopped = false; device->scroll.x_is_cancelled = false; } if (y) { device->scroll.y_is_stopped = false; device->scroll.y_is_cancelled = false; } } _public_ void ei_device_pointer_scroll(struct ei_device *device, double x, double y) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER) && !ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE)) { log_bug_client(ei_device_get_context(device), "%s: device is not a (absolute) pointer\n", __func__); } if (device->state != EI_DEVICE_STATE_EMULATING) return; ei_device_resume_scrolling(device, x, y); ei_send_pointer_scroll(device, x, y); } _public_ void ei_device_pointer_scroll_stop(struct ei_device *device, bool x, bool y) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER) && !ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE)) { log_bug_client(ei_device_get_context(device), "%s: device is not a (absolute) pointer\n", __func__); } if (device->state != EI_DEVICE_STATE_EMULATING) return; /* Filter out duplicate scroll stop requests */ if (x && !device->scroll.x_is_stopped) device->scroll.x_is_stopped = true; else x = false; if (y && !device->scroll.y_is_stopped) device->scroll.y_is_stopped = true; else y = false; if (x || y) ei_send_pointer_scroll_stop(device, x, y); } _public_ void ei_device_pointer_scroll_cancel(struct ei_device *device, bool x, bool y) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER) && !ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE)) { log_bug_client(ei_device_get_context(device), "%s: device is not a (absolute) pointer\n", __func__); } if (device->state != EI_DEVICE_STATE_EMULATING) return; /* Filter out duplicate scroll cancelled requests */ if (x && !device->scroll.x_is_cancelled) { device->scroll.x_is_stopped = true; device->scroll.x_is_cancelled = true; } else { x = false; } if (y && !device->scroll.y_is_cancelled) { device->scroll.y_is_stopped = true; device->scroll.y_is_cancelled = true; } else { y = false; } if (x || y) ei_send_pointer_scroll_cancel(device, x, y); } _public_ void ei_device_pointer_scroll_discrete(struct ei_device *device, int32_t x, int32_t y) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER) && !ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE)) { log_bug_client(ei_device_get_context(device), "%s: device is not a (absolute) pointer\n", __func__); } if (device->state != EI_DEVICE_STATE_EMULATING) return; ei_device_resume_scrolling(device, x, y); ei_send_pointer_scroll_discrete(device, x, y); } _public_ void ei_device_keyboard_key(struct ei_device *device, uint32_t key, bool is_press) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD)) { log_bug_client(ei_device_get_context(device), "%s: device is not a keyboard\n", __func__); return; } if (device->state != EI_DEVICE_STATE_EMULATING) return; ei_send_keyboard_key(device, key, is_press); } _public_ OBJECT_IMPLEMENT_REF(ei_touch); _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_touch); _public_ OBJECT_IMPLEMENT_GETTER(ei_touch, device, struct ei_device*); _public_ OBJECT_IMPLEMENT_GETTER(ei_touch, user_data, void *); _public_ OBJECT_IMPLEMENT_SETTER(ei_touch, user_data, void *); static void ei_touch_destroy(struct ei_touch *touch) { ei_touch_up(touch); ei_device_unref(touch->device); } static OBJECT_IMPLEMENT_CREATE(ei_touch); _public_ struct ei_touch * ei_device_touch_new(struct ei_device *device) { static uint32_t tracking_id = 0; /* Not using the device as parent object because we need a ref * to it */ struct ei_touch *touch = ei_touch_create(NULL); touch->device = ei_device_ref(device); touch->state = TOUCH_IS_NEW; touch->tracking_id = ++tracking_id; return touch; } _public_ void ei_touch_down(struct ei_touch *touch, double x, double y) { struct ei_device *device = ei_touch_get_device(touch); if (touch->state != TOUCH_IS_NEW) { log_bug_client(ei_device_get_context(device), "%s: device is not a keyboard\n", __func__); return; } struct ei_region *r; list_for_each(r, &device->regions, link) { if (!ei_region_contains(r, x, y)) { log_bug_client(ei_device_get_context(device), "%s: invalid x/y coordinates\n", __func__); touch->state = TOUCH_IS_UP; return; } } touch->state = TOUCH_IS_DOWN; ei_send_touch_down(device, touch->tracking_id, x, y); } _public_ void ei_touch_motion(struct ei_touch *touch, double x, double y) { if (touch->state != TOUCH_IS_DOWN) return; struct ei_device *device = ei_touch_get_device(touch); struct ei_region *r; list_for_each(r, &device->regions, link) { if (!ei_region_contains(r, x, y)) { log_bug_client(ei_device_get_context(device), "%s: invalid x/y coordinates\n", __func__); ei_touch_up(touch); return; } } ei_send_touch_motion(touch->device, touch->tracking_id, x, y); } _public_ void ei_touch_up(struct ei_touch *touch) { if (touch->state != TOUCH_IS_DOWN) { struct ei_device *device = ei_touch_get_device(touch); log_bug_client(ei_device_get_context(device), "%s: touch is not currently down\n", __func__); return; } touch->state = TOUCH_IS_UP; ei_send_touch_up(touch->device, touch->tracking_id); } _public_ void ei_device_frame(struct ei_device *device) { if (device->state != EI_DEVICE_STATE_RESUMED) return; ei_send_frame(device); } void ei_device_event_start_emulating(struct ei_device *device) { switch (device->state) { case EI_DEVICE_STATE_DEAD: case EI_DEVICE_STATE_NEW: case EI_DEVICE_STATE_PAUSED: case EI_DEVICE_STATE_EMULATING: break; case EI_DEVICE_STATE_RESUMED: ei_queue_device_start_emulating_event(device); device->state = EI_DEVICE_STATE_EMULATING; break; case EI_DEVICE_STATE_REMOVED_FROM_CLIENT: case EI_DEVICE_STATE_REMOVED_FROM_SERVER: break; } } void ei_device_event_stop_emulating(struct ei_device *device) { switch (device->state) { case EI_DEVICE_STATE_DEAD: case EI_DEVICE_STATE_NEW: case EI_DEVICE_STATE_PAUSED: case EI_DEVICE_STATE_RESUMED: break; case EI_DEVICE_STATE_EMULATING: ei_queue_device_stop_emulating_event(device); device->state = EI_DEVICE_STATE_RESUMED; break; case EI_DEVICE_STATE_REMOVED_FROM_CLIENT: case EI_DEVICE_STATE_REMOVED_FROM_SERVER: break; } } int ei_device_event_frame(struct ei_device *device) { if (device->state != EI_DEVICE_STATE_RESUMED) return -EINVAL; ei_queue_frame_event(device); return 0; } int ei_device_event_pointer_rel(struct ei_device *device, double x, double y) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER)) { log_bug_client(ei_device_get_context(device), "%s: device is not a pointer\n", __func__); return -EINVAL; } if (device->state != EI_DEVICE_STATE_EMULATING) return -EINVAL; ei_queue_pointer_rel_event(device, x, y); return 0; } static inline bool ei_device_in_region(struct ei_device *device, double x, double y) { struct ei_region *r; list_for_each(r, &device->regions, link) { if (ei_region_contains(r, x, y)) return true; } return false; } int ei_device_event_pointer_abs(struct ei_device *device, double x, double y) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE)) { log_bug_client(ei_device_get_context(device), "%s: device is not an absolute pointer\n", __func__); return -EINVAL; } if (device->state != EI_DEVICE_STATE_EMULATING) return -EINVAL; if (!ei_device_in_region(device, x, y)) return -EINVAL; ei_queue_pointer_abs_event(device, x, y); return 0; } int ei_device_event_pointer_button(struct ei_device *device, uint32_t button, bool is_press) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER)) { log_bug_client(ei_device_get_context(device), "%s: device is not a pointer\n", __func__); return -EINVAL; } if (device->state != EI_DEVICE_STATE_EMULATING) return -EINVAL; ei_queue_pointer_button_event(device, button, is_press); return 0; } int ei_device_event_pointer_scroll(struct ei_device *device, double x, double y) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER) && !ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE)) { log_bug_client(ei_device_get_context(device), "%s: device is not a (absolute) pointer\n", __func__); return -EINVAL; } if (device->state != EI_DEVICE_STATE_EMULATING) return -EINVAL; ei_queue_pointer_scroll_event(device, x, y); return 0; } int ei_device_event_pointer_scroll_discrete(struct ei_device *device, int32_t x, int32_t y) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER) && !ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE)) { log_bug_client(ei_device_get_context(device), "%s: device is not a (absolute) pointer\n", __func__); return -EINVAL; } if (device->state != EI_DEVICE_STATE_EMULATING) return -EINVAL; ei_queue_pointer_scroll_discrete_event(device, x, y); return 0; } int ei_device_event_pointer_scroll_stop(struct ei_device *device, bool x, bool y) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER) && !ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE)) { log_bug_client(ei_device_get_context(device), "%s: device is not a (absolute) pointer\n", __func__); return -EINVAL; } if (device->state != EI_DEVICE_STATE_EMULATING) return -EINVAL; ei_queue_pointer_scroll_stop_event(device, x, y); return 0; } int ei_device_event_pointer_scroll_cancel(struct ei_device *device, bool x, bool y) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER) && !ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE)) { log_bug_client(ei_device_get_context(device), "%s: device is not a (absolute) pointer\n", __func__); return -EINVAL; } if (device->state != EI_DEVICE_STATE_EMULATING) return -EINVAL; ei_queue_pointer_scroll_cancel_event(device, x, y); return 0; } int ei_device_event_keyboard_key(struct ei_device *device, uint32_t key, bool is_press) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD)) { log_bug_client(ei_device_get_context(device), "%s: device is not a keyboard\n", __func__); return -EINVAL; } if (device->state != EI_DEVICE_STATE_EMULATING) return -EINVAL; ei_queue_keyboard_key_event(device, key, is_press); return 0; } int ei_device_event_touch_down(struct ei_device *device, uint32_t touchid, double x, double y) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_TOUCH)) { log_bug_client(ei_device_get_context(device), "%s: device is not a touch device\n", __func__); return -EINVAL; } if (device->state != EI_DEVICE_STATE_EMULATING) return -EINVAL; ei_queue_touch_down_event(device, touchid, x, y); return 0; } int ei_device_event_touch_motion(struct ei_device *device, uint32_t touchid, double x, double y) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_TOUCH)) { log_bug_client(ei_device_get_context(device), "%s: device is not a touch device\n", __func__); return -EINVAL; } if (device->state != EI_DEVICE_STATE_EMULATING) return -EINVAL; ei_queue_touch_motion_event(device, touchid, x, y); return 0; } int ei_device_event_touch_up(struct ei_device *device, uint32_t touchid) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_TOUCH)) { log_bug_client(ei_device_get_context(device), "%s: device is not a touch device\n", __func__); return -EINVAL; } if (device->state != EI_DEVICE_STATE_EMULATING) return -EINVAL; ei_queue_touch_up_event(device, touchid); return 0; }