| /* 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 <stdint.h> |
| |
| #include "util-bits.h" |
| #include "util-io.h" |
| #include "util-macros.h" |
| #include "util-time.h" |
| |
| #include "eis-proto.h" |
| #include "libeis-private.h" |
| |
| static_assert((int)EIS_DEVICE_TYPE_VIRTUAL == EIS_DEVICE_DEVICE_TYPE_VIRTUAL, "ABI mismatch"); |
| static_assert((int)EIS_DEVICE_TYPE_PHYSICAL == EIS_DEVICE_DEVICE_TYPE_PHYSICAL, "ABI mismatch"); |
| |
| _public_ OBJECT_IMPLEMENT_REF(eis_keymap); |
| _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_keymap); |
| _public_ OBJECT_IMPLEMENT_GETTER(eis_keymap, type, enum eis_keymap_type); |
| _public_ |
| OBJECT_IMPLEMENT_GETTER(eis_keymap, fd, int); |
| _public_ |
| OBJECT_IMPLEMENT_GETTER(eis_keymap, size, size_t); |
| _public_ |
| OBJECT_IMPLEMENT_GETTER(eis_keymap, device, struct eis_device *); |
| _public_ |
| OBJECT_IMPLEMENT_GETTER(eis_keymap, user_data, void *); |
| _public_ |
| OBJECT_IMPLEMENT_SETTER(eis_keymap, user_data, void *); |
| |
| static void |
| eis_keymap_destroy(struct eis_keymap *keymap) |
| { |
| if (!keymap->assigned) |
| eis_device_unref(keymap->device); |
| xclose(keymap->fd); |
| } |
| |
| static OBJECT_IMPLEMENT_CREATE(eis_keymap); |
| |
| _public_ struct eis_keymap * |
| eis_device_new_keymap(struct eis_device *device, enum eis_keymap_type type, int fd, size_t size) |
| { |
| switch (type) { |
| case EIS_KEYMAP_TYPE_XKB: |
| break; |
| default: |
| return NULL; |
| } |
| |
| if (fd < 0 || size == 0) |
| return NULL; |
| |
| int newfd = xdup(fd); |
| if (newfd < 0) |
| return NULL; |
| |
| struct eis_keymap *keymap = eis_keymap_create(NULL); |
| keymap->device = eis_device_ref(device); |
| keymap->fd = newfd; |
| keymap->type = type; |
| keymap->size = size; |
| |
| return keymap; |
| } |
| |
| _public_ struct eis * |
| eis_device_get_context(struct eis_device *device) |
| { |
| return eis_client_get_context(eis_device_get_client(device)); |
| } |
| |
| _public_ void |
| eis_keymap_add(struct eis_keymap *keymap) |
| { |
| struct eis_device *device = eis_keymap_get_device(keymap); |
| |
| if (device->state != EIS_DEVICE_STATE_NEW) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: device already (dis)connected", |
| __func__); |
| return; |
| } |
| |
| if (device->keymap) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: only one keymap can only be assigned and only once", |
| __func__); |
| return; |
| } |
| |
| /* New keymap holds ref to the device, for assigned keymap the device |
| * holds the ref to the keymap instead */ |
| device->keymap = eis_keymap_ref(keymap); |
| keymap->assigned = true; |
| eis_device_unref(keymap->device); |
| } |
| |
| _public_ struct eis_keymap * |
| eis_device_keyboard_get_keymap(struct eis_device *device) |
| { |
| return device->keymap; |
| } |
| |
| static void |
| eis_device_destroy(struct eis_device *device) |
| { |
| struct eis_region *r; |
| struct eis_event *event; |
| |
| list_for_each_safe(r, &device->regions, link) |
| eis_region_unref(r); |
| |
| /* regions_new does not own a ref */ |
| |
| eis_keymap_unref(device->keymap); |
| |
| list_for_each_safe(event, &device->pending_event_queue, link) { |
| list_remove(&event->link); |
| eis_event_unref(event); |
| } |
| |
| eis_pointer_unref(device->pointer); |
| eis_touchscreen_unref(device->touchscreen); |
| eis_keyboard_unref(device->keyboard); |
| eis_text_unref(device->text); |
| |
| free(device->name); |
| } |
| |
| _public_ OBJECT_IMPLEMENT_REF(eis_device); |
| _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_device); |
| static OBJECT_IMPLEMENT_CREATE(eis_device); |
| static OBJECT_IMPLEMENT_PARENT(eis_device, eis_seat); |
| _public_ |
| OBJECT_IMPLEMENT_GETTER(eis_device, user_data, void *); |
| _public_ |
| OBJECT_IMPLEMENT_SETTER(eis_device, user_data, void *); |
| _public_ |
| OBJECT_IMPLEMENT_GETTER(eis_device, name, const char *); |
| _public_ OBJECT_IMPLEMENT_GETTER(eis_device, type, enum eis_device_type); |
| _public_ |
| OBJECT_IMPLEMENT_GETTER(eis_device, width, uint32_t); |
| _public_ |
| OBJECT_IMPLEMENT_GETTER(eis_device, height, uint32_t); |
| OBJECT_IMPLEMENT_GETTER_AS_REF(eis_device, proto_object, const struct brei_object *); |
| |
| object_id_t |
| eis_device_get_id(struct eis_device *device) |
| { |
| return device->proto_object.id; |
| } |
| |
| _public_ struct eis_seat * |
| eis_device_get_seat(struct eis_device *device) |
| { |
| return eis_device_parent(device); |
| } |
| |
| _public_ struct eis_region * |
| eis_device_get_region(struct eis_device *device, size_t index) |
| { |
| return list_nth_entry(struct eis_region, &device->regions, link, index); |
| } |
| |
| _public_ struct eis_region * |
| eis_device_get_region_at(struct eis_device *device, double x, double y) |
| { |
| struct eis_region *r; |
| |
| list_for_each(r, &device->regions, link) { |
| if (eis_region_contains(r, x, y)) |
| return r; |
| } |
| return NULL; |
| } |
| |
| _public_ struct eis_client * |
| eis_device_get_client(struct eis_device *device) |
| { |
| return eis_seat_get_client(eis_device_get_seat(device)); |
| } |
| |
| static inline bool |
| eis_device_in_region(struct eis_device *device, double x, double y) |
| { |
| struct eis_region *r; |
| |
| if (list_empty(&device->regions)) |
| return true; |
| |
| list_for_each(r, &device->regions, link) { |
| if (eis_region_contains(r, x, y)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static struct brei_result * |
| client_msg_release(struct eis_device *device) |
| { |
| eis_device_closed_by_client(device); |
| return NULL; |
| } |
| |
| #define DISCONNECT_IF_RECEIVER_CONTEXT(device_) do { \ |
| struct eis_client *client_ = eis_device_get_client(device_); \ |
| if (!eis_client_is_sender(client_)) { \ |
| struct eis *_ctx = eis_client_get_context(client_); \ |
| log_bug_client(_ctx, "Invalid event from receiver ei context. Disconnecting client"); \ |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_MODE, "Invalid event from receiver ei context"); \ |
| } \ |
| } while(0) |
| |
| static struct brei_result * |
| client_msg_start_emulating(struct eis_device *device, uint32_t serial, uint32_t sequence) |
| { |
| struct brei_result *result = NULL; |
| |
| eis_client_update_client_serial(eis_device_get_client(device), serial); |
| |
| DISCONNECT_IF_RECEIVER_CONTEXT(device); |
| |
| switch (device->state) { |
| case EIS_DEVICE_STATE_DEAD: |
| case EIS_DEVICE_STATE_CLOSED_BY_CLIENT: |
| case EIS_DEVICE_STATE_NEW: |
| case EIS_DEVICE_STATE_AWAITING_READY: |
| case EIS_DEVICE_STATE_EMULATING: |
| result = brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "Invalid device state %u for a start_emulating event", |
| device->state); |
| break; |
| case EIS_DEVICE_STATE_RESUMED: |
| eis_queue_device_start_emulating_event(device, sequence); |
| device->state = EIS_DEVICE_STATE_EMULATING; |
| break; |
| case EIS_DEVICE_STATE_PAUSED: |
| /* race condition, that's fine */ |
| break; |
| } |
| |
| return result; |
| } |
| |
| static struct brei_result * |
| client_msg_stop_emulating(struct eis_device *device, uint32_t serial) |
| { |
| struct brei_result *result = NULL; |
| |
| eis_client_update_client_serial(eis_device_get_client(device), serial); |
| |
| DISCONNECT_IF_RECEIVER_CONTEXT(device); |
| |
| switch (device->state) { |
| case EIS_DEVICE_STATE_DEAD: |
| case EIS_DEVICE_STATE_CLOSED_BY_CLIENT: |
| case EIS_DEVICE_STATE_NEW: |
| case EIS_DEVICE_STATE_AWAITING_READY: |
| case EIS_DEVICE_STATE_RESUMED: |
| result = brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "Invalid device state %u for a stop_emulating event", |
| device->state); |
| break; |
| case EIS_DEVICE_STATE_EMULATING: |
| eis_queue_device_stop_emulating_event(device); |
| device->state = EIS_DEVICE_STATE_RESUMED; |
| break; |
| case EIS_DEVICE_STATE_PAUSED: |
| /* race condition, that's fine */ |
| break; |
| } |
| |
| return result; |
| } |
| |
| static struct brei_result * |
| maybe_error_on_device_state(struct eis_device *device, const char *event_type) |
| { |
| switch (device->state) { |
| case EIS_DEVICE_STATE_RESUMED: |
| /* could be a race condition, but it's unlikely unless the |
| * EIS implementation pauses and resumes immediately without |
| * giving the client a chance to catch up. So let's |
| * treat this as error until we see real issues. |
| */ |
| break; |
| case EIS_DEVICE_STATE_PAUSED: |
| /* we paused the device but the client sent us an event |
| * - most likely a race condition, so let's ignore it */ |
| return NULL; |
| case EIS_DEVICE_STATE_EMULATING: |
| return NULL; |
| case EIS_DEVICE_STATE_NEW: |
| case EIS_DEVICE_STATE_AWAITING_READY: |
| case EIS_DEVICE_STATE_CLOSED_BY_CLIENT: |
| case EIS_DEVICE_STATE_DEAD: |
| break; |
| } |
| |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "Invalid device state %u for a %s event", |
| device->state, |
| event_type); |
| } |
| |
| static struct brei_result * |
| client_msg_frame(struct eis_device *device, uint32_t serial, uint64_t time) |
| { |
| eis_client_update_client_serial(eis_device_get_client(device), serial); |
| |
| DISCONNECT_IF_RECEIVER_CONTEXT(device); |
| |
| if (device->state == EIS_DEVICE_STATE_EMULATING) { |
| eis_queue_frame_event(device, time); |
| return NULL; |
| } |
| |
| return maybe_error_on_device_state(device, "frame"); |
| } |
| |
| static struct brei_result * |
| client_msg_ready(struct eis_device *device) |
| { |
| if (device->state != EIS_DEVICE_STATE_AWAITING_READY) |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "Invalid device state %u for a ready event", |
| device->state); |
| |
| device->state = EIS_DEVICE_STATE_PAUSED; |
| eis_queue_device_ready_event(device); |
| |
| return NULL; |
| } |
| |
| static const struct eis_device_interface interface = { |
| .release = client_msg_release, |
| .start_emulating = client_msg_start_emulating, |
| .stop_emulating = client_msg_stop_emulating, |
| .frame = client_msg_frame, |
| .ready = client_msg_ready, |
| }; |
| |
| const struct eis_device_interface * |
| eis_device_get_interface(struct eis_device *device) |
| { |
| return &interface; |
| } |
| |
| static struct brei_result * |
| client_msg_pointer_rel(struct eis_pointer *pointer, float x, float y) |
| { |
| struct eis_device *device = eis_pointer_get_device(pointer); |
| |
| DISCONNECT_IF_RECEIVER_CONTEXT(device); |
| |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER)) { |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "Pointer rel event for non-pointer device"); |
| } |
| |
| if (device->state == EIS_DEVICE_STATE_EMULATING) { |
| eis_queue_pointer_rel_event(device, x, y); |
| return NULL; |
| } |
| |
| return maybe_error_on_device_state(device, "pointer rel"); |
| } |
| |
| static struct brei_result * |
| client_msg_pointer_abs(struct eis_pointer_absolute *pointer, float x, float y) |
| { |
| struct eis_device *device = eis_pointer_absolute_get_device(pointer); |
| |
| DISCONNECT_IF_RECEIVER_CONTEXT(device); |
| |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE)) { |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "Pointer abs event for non-pointer device"); |
| } |
| |
| if (device->state == EIS_DEVICE_STATE_EMULATING) { |
| if (eis_device_in_region(device, x, y)) |
| eis_queue_pointer_abs_event(device, x, y); |
| return NULL; |
| } |
| |
| return maybe_error_on_device_state(device, "pointer abs"); |
| } |
| |
| static struct brei_result * |
| client_msg_button(struct eis_button *button, uint32_t btn, uint32_t state) |
| { |
| struct eis_device *device = eis_button_get_device(button); |
| |
| DISCONNECT_IF_RECEIVER_CONTEXT(device); |
| |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_BUTTON)) { |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "Button event for non-button device"); |
| } |
| |
| if (btn >= KEY_CNT) |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "Button event for invalid button %x (KEY_CNT is %#x)", |
| btn, |
| KEY_CNT); |
| |
| if (!eis_device_update_key_button_state(device, btn, state)) |
| return NULL; |
| |
| if (device->state == EIS_DEVICE_STATE_EMULATING) { |
| eis_queue_pointer_button_event(device, btn, !!state); |
| return NULL; |
| } |
| |
| return maybe_error_on_device_state(device, "pointer button"); |
| } |
| |
| static struct brei_result * |
| client_msg_scroll(struct eis_scroll *scroll, float x, float y) |
| { |
| struct eis_device *device = eis_scroll_get_device(scroll); |
| |
| DISCONNECT_IF_RECEIVER_CONTEXT(device); |
| |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_SCROLL)) { |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "Scroll event for non-scroll device"); |
| } |
| |
| if (device->state == EIS_DEVICE_STATE_EMULATING) { |
| eis_queue_pointer_scroll_event(device, x, y); |
| return NULL; |
| } |
| |
| return maybe_error_on_device_state(device, "pointer scroll"); |
| } |
| |
| static struct brei_result * |
| client_msg_scroll_discrete(struct eis_scroll *scroll, int32_t x, int32_t y) |
| { |
| struct eis_device *device = eis_scroll_get_device(scroll); |
| |
| DISCONNECT_IF_RECEIVER_CONTEXT(device); |
| |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_SCROLL)) { |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "Scroll discrete event for non-scroll device"); |
| } |
| |
| if (device->state == EIS_DEVICE_STATE_EMULATING) { |
| eis_queue_pointer_scroll_discrete_event(device, x, y); |
| return NULL; |
| } |
| |
| return maybe_error_on_device_state(device, "pointer scroll discrete"); |
| } |
| |
| static struct brei_result * |
| client_msg_scroll_stop(struct eis_scroll *scroll, uint32_t x, uint32_t y, uint32_t is_cancel) |
| { |
| struct eis_device *device = eis_scroll_get_device(scroll); |
| |
| DISCONNECT_IF_RECEIVER_CONTEXT(device); |
| |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_SCROLL)) { |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "Scroll stop event for non-scroll device"); |
| } |
| |
| if (device->state == EIS_DEVICE_STATE_EMULATING) { |
| if (is_cancel) |
| eis_queue_pointer_scroll_cancel_event(device, !!x, !!y); |
| else |
| eis_queue_pointer_scroll_stop_event(device, !!x, !!y); |
| return NULL; |
| } |
| |
| return maybe_error_on_device_state(device, "pointer scroll stop"); |
| } |
| |
| static struct brei_result * |
| client_msg_pointer_release(struct eis_pointer *pointer) |
| { |
| struct eis_device *device = eis_pointer_get_device(pointer); |
| eis_pointer_event_destroyed(device->pointer, |
| eis_client_get_next_serial(eis_device_get_client(device))); |
| eis_pointer_unref(steal(&device->pointer)); |
| return 0; |
| } |
| |
| static struct brei_result * |
| client_msg_pointer_absolute_release(struct eis_pointer_absolute *pointer) |
| { |
| struct eis_device *device = eis_pointer_absolute_get_device(pointer); |
| eis_pointer_absolute_event_destroyed( |
| device->pointer_absolute, |
| eis_client_get_next_serial(eis_device_get_client(device))); |
| eis_pointer_absolute_unref(steal(&device->pointer_absolute)); |
| return 0; |
| } |
| |
| static struct brei_result * |
| client_msg_scroll_release(struct eis_scroll *scroll) |
| { |
| struct eis_device *device = eis_scroll_get_device(scroll); |
| eis_scroll_event_destroyed(device->scroll, |
| eis_client_get_next_serial(eis_device_get_client(device))); |
| eis_scroll_unref(steal(&device->scroll)); |
| return 0; |
| } |
| |
| static struct brei_result * |
| client_msg_button_release(struct eis_button *button) |
| { |
| struct eis_device *device = eis_button_get_device(button); |
| eis_button_event_destroyed(device->button, |
| eis_client_get_next_serial(eis_device_get_client(device))); |
| eis_button_unref(steal(&device->button)); |
| return 0; |
| } |
| |
| static const struct eis_pointer_interface pointer_interface = { |
| .release = client_msg_pointer_release, |
| .motion_relative = client_msg_pointer_rel, |
| }; |
| |
| static const struct eis_pointer_absolute_interface pointer_absolute_interface = { |
| .release = client_msg_pointer_absolute_release, |
| .motion_absolute = client_msg_pointer_abs, |
| }; |
| |
| static const struct eis_scroll_interface scroll_interface = { |
| .release = client_msg_scroll_release, |
| .scroll = client_msg_scroll, |
| .scroll_discrete = client_msg_scroll_discrete, |
| .scroll_stop = client_msg_scroll_stop, |
| }; |
| |
| static const struct eis_button_interface button_interface = { |
| .release = client_msg_button_release, |
| .button = client_msg_button, |
| }; |
| |
| const struct eis_pointer_interface * |
| eis_device_get_pointer_interface(struct eis_device *device) |
| { |
| return &pointer_interface; |
| } |
| |
| const struct eis_pointer_absolute_interface * |
| eis_device_get_pointer_absolute_interface(struct eis_device *device) |
| { |
| return &pointer_absolute_interface; |
| } |
| |
| const struct eis_scroll_interface * |
| eis_device_get_scroll_interface(struct eis_device *device) |
| { |
| return &scroll_interface; |
| } |
| |
| const struct eis_button_interface * |
| eis_device_get_button_interface(struct eis_device *device) |
| { |
| return &button_interface; |
| } |
| |
| static struct brei_result * |
| client_msg_keyboard_key(struct eis_keyboard *keyboard, uint32_t key, uint32_t state) |
| { |
| struct eis_device *device = eis_keyboard_get_device(keyboard); |
| |
| DISCONNECT_IF_RECEIVER_CONTEXT(device); |
| |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_KEYBOARD)) { |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "Key event for non-keyboard device"); |
| } |
| |
| if (key >= KEY_CNT) |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "Key event for invalid key %x (KEY_CNT is %#x)", |
| key, |
| KEY_CNT); |
| |
| if (!eis_device_update_key_button_state(device, key, state)) |
| return NULL; |
| |
| if (device->state == EIS_DEVICE_STATE_EMULATING) { |
| eis_queue_keyboard_key_event(device, key, !!state); |
| return NULL; |
| } |
| |
| return maybe_error_on_device_state(device, "key"); |
| } |
| |
| static struct brei_result * |
| client_msg_keyboard_release(struct eis_keyboard *keyboard) |
| { |
| struct eis_device *device = eis_keyboard_get_device(keyboard); |
| eis_keyboard_event_destroyed(device->keyboard, |
| eis_client_get_next_serial(eis_device_get_client(device))); |
| eis_keyboard_unref(steal(&device->keyboard)); |
| return 0; |
| } |
| |
| static const struct eis_keyboard_interface keyboard_interface = { |
| .release = client_msg_keyboard_release, |
| .key = client_msg_keyboard_key, |
| }; |
| |
| const struct eis_keyboard_interface * |
| eis_device_get_keyboard_interface(struct eis_device *device) |
| { |
| return &keyboard_interface; |
| } |
| |
| /* Returns true and the position of the touch with the given ID, or |
| * false and the first position that is available |
| */ |
| static bool |
| find_touch(struct eis_device *device, uint32_t touchid, size_t *index) |
| { |
| ssize_t first_available = -1; |
| for (size_t i = 0; i < ARRAY_LENGTH(device->touch_state.down); i++) { |
| if (device->touch_state.down[i] != UINT64_MAX) { |
| if (device->touch_state.down[i] == touchid) { |
| if (index) |
| *index = i; |
| return true; |
| } |
| } else if (first_available < 0) { |
| first_available = i; |
| } |
| } |
| |
| if (index) { |
| if (first_available < 0) |
| *index = EIS_MAX_TOUCHES; |
| else |
| *index = (size_t)first_available; |
| } |
| |
| return false; |
| } |
| |
| static struct brei_result * |
| client_msg_touch_down(struct eis_touchscreen *touchscreen, uint32_t touchid, float x, float y) |
| { |
| struct eis_device *device = eis_touchscreen_get_device(touchscreen); |
| |
| DISCONNECT_IF_RECEIVER_CONTEXT(device); |
| |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_TOUCH)) { |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "Touch down event for non-touch device"); |
| } |
| |
| if (device->state == EIS_DEVICE_STATE_EMULATING) { |
| size_t first_available; |
| if (find_touch(device, touchid, &first_available)) { |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "Touch down event for duplicated touch ID"); |
| } |
| |
| if (first_available >= EIS_MAX_TOUCHES) |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_ERROR, |
| "Too many simultaneous touch events"); |
| |
| device->touch_state.down[first_available] = touchid; |
| eis_queue_touch_down_event(device, touchid, x, y); |
| return NULL; |
| } |
| |
| return maybe_error_on_device_state(device, "touch down"); |
| } |
| |
| static struct brei_result * |
| client_msg_touch_motion(struct eis_touchscreen *touchscreen, uint32_t touchid, float x, float y) |
| { |
| struct eis_device *device = eis_touchscreen_get_device(touchscreen); |
| |
| DISCONNECT_IF_RECEIVER_CONTEXT(device); |
| |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_TOUCH)) { |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "Touch motion event for non-touch device"); |
| } |
| |
| if (device->state == EIS_DEVICE_STATE_EMULATING) { |
| /* Silently ignore motion for non-existing touches */ |
| if (find_touch(device, touchid, NULL)) |
| eis_queue_touch_motion_event(device, touchid, x, y); |
| return NULL; |
| } |
| |
| return maybe_error_on_device_state(device, "touch motion"); |
| } |
| |
| static bool |
| release_touch(struct eis_device *device, uint32_t touchid) |
| { |
| size_t index; |
| bool rc = find_touch(device, touchid, &index); |
| if (rc) |
| device->touch_state.down[index] = UINT64_MAX; |
| |
| return rc; |
| } |
| |
| static struct brei_result * |
| client_msg_touch_up(struct eis_touchscreen *touchscreen, uint32_t touchid) |
| { |
| struct eis_device *device = eis_touchscreen_get_device(touchscreen); |
| |
| DISCONNECT_IF_RECEIVER_CONTEXT(device); |
| |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_TOUCH)) { |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "Touch up event for non-touch device"); |
| } |
| |
| /* End the touch locally even if we're not emulating, but |
| * silently ignore touch end/cancel for non-existing touches */ |
| if (release_touch(device, touchid)) { |
| if (device->state == EIS_DEVICE_STATE_EMULATING) |
| eis_queue_touch_up_event(device, touchid); |
| return NULL; |
| } |
| |
| return maybe_error_on_device_state(device, "touch up"); |
| } |
| |
| static struct brei_result * |
| client_msg_touch_cancel(struct eis_touchscreen *touchscreen, uint32_t touchid) |
| { |
| struct eis_device *device = eis_touchscreen_get_device(touchscreen); |
| |
| DISCONNECT_IF_RECEIVER_CONTEXT(device); |
| |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_TOUCH)) { |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "Touch cancel event for non-touch device"); |
| } |
| |
| struct eis_client *client = eis_device_get_client(device); |
| if (client->interface_versions.ei_touchscreen < |
| EIS_TOUCHSCREEN_EVENT_CANCEL_SINCE_VERSION) { |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "Touch cancel event for touchscreen version v1"); |
| } |
| |
| /* End the touch locally even if we're not emulating, but |
| * silently ignore touch end/cancel for non-existing touches */ |
| if (release_touch(device, touchid)) { |
| if (device->state == EIS_DEVICE_STATE_EMULATING) |
| eis_queue_touch_cancel_event(device, touchid); |
| return NULL; |
| } |
| |
| return maybe_error_on_device_state(device, "touch cancel"); |
| } |
| |
| static struct brei_result * |
| client_msg_touchscreen_release(struct eis_touchscreen *touchscreen) |
| { |
| struct eis_device *device = eis_touchscreen_get_device(touchscreen); |
| eis_touchscreen_event_destroyed(device->touchscreen, |
| eis_client_get_next_serial(eis_device_get_client(device))); |
| eis_touchscreen_unref(steal(&device->touchscreen)); |
| return NULL; |
| } |
| |
| static const struct eis_touchscreen_interface touchscreen_interface = { |
| .release = client_msg_touchscreen_release, |
| .down = client_msg_touch_down, |
| .motion = client_msg_touch_motion, |
| .up = client_msg_touch_up, |
| .cancel = client_msg_touch_cancel, |
| }; |
| |
| const struct eis_touchscreen_interface * |
| eis_device_get_touchscreen_interface(struct eis_device *device) |
| { |
| return &touchscreen_interface; |
| } |
| |
| static struct brei_result * |
| client_msg_text_release(struct eis_text *text) |
| { |
| struct eis_device *device = eis_text_get_device(text); |
| eis_text_event_destroyed(device->text, |
| eis_client_get_next_serial(eis_device_get_client(device))); |
| eis_text_unref(steal(&device->text)); |
| return NULL; |
| } |
| |
| static struct brei_result * |
| client_msg_text_keysym(struct eis_text *text, uint32_t keysym, uint32_t state) |
| { |
| struct eis_device *device = eis_text_get_device(text); |
| |
| DISCONNECT_IF_RECEIVER_CONTEXT(device); |
| |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_TEXT)) { |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "keysym event for non-text device"); |
| } |
| |
| if (device->state == EIS_DEVICE_STATE_EMULATING) { |
| eis_queue_text_keysym_event(device, keysym, !!state); |
| return NULL; |
| } |
| |
| return maybe_error_on_device_state(device, "text keysym"); |
| } |
| |
| static struct brei_result * |
| client_msg_text_utf8(struct eis_text *text, const char *utf8) |
| { |
| struct eis_device *device = eis_text_get_device(text); |
| |
| DISCONNECT_IF_RECEIVER_CONTEXT(device); |
| |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_TEXT)) { |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "utf8 event for non-text device"); |
| } |
| |
| if (!utf8 || utf8[0] == '\0') { |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "utf8 text is NULL or the empty string"); |
| } |
| |
| if (strlen(utf8) > 254) { |
| return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, |
| "utf8 string exceeds max length of 255 bytes"); |
| } |
| |
| if (device->state == EIS_DEVICE_STATE_EMULATING) { |
| eis_queue_text_utf8_event(device, utf8); |
| return NULL; |
| } |
| |
| return maybe_error_on_device_state(device, "text utf8"); |
| } |
| |
| static const struct eis_text_interface text_interface = { |
| .release = client_msg_text_release, |
| .keysym = client_msg_text_keysym, |
| .utf8 = client_msg_text_utf8, |
| }; |
| |
| const struct eis_text_interface * |
| eis_device_get_text_interface(struct eis_device *device) |
| { |
| return &text_interface; |
| } |
| |
| _public_ struct eis_device * |
| eis_seat_new_device(struct eis_seat *seat) |
| { |
| struct eis_device *device = eis_device_create(&seat->object); |
| struct eis_client *client = eis_seat_get_client(seat); |
| |
| device->proto_object.id = eis_client_get_new_id(client); |
| device->proto_object.implementation = device; |
| device->proto_object.interface = &eis_device_proto_interface; |
| device->proto_object.version = client->interface_versions.ei_device; |
| assert(device->proto_object.version != 0); |
| list_init(&device->proto_object.link); |
| |
| device->name = xstrdup("unnamed device"); |
| device->capabilities = 0; |
| device->state = EIS_DEVICE_STATE_NEW; |
| device->type = EIS_DEVICE_TYPE_VIRTUAL; |
| list_init(&device->regions); |
| list_init(&device->regions_new); |
| list_init(&device->pending_event_queue); |
| |
| list_append(&seat->devices, &device->link); |
| |
| for (size_t i = 0; i < ARRAY_LENGTH(device->touch_state.down); i++) { |
| device->touch_state.down[i] = UINT64_MAX; |
| } |
| |
| return eis_device_ref(device); |
| } |
| |
| _public_ void |
| eis_device_configure_type(struct eis_device *device, enum eis_device_type type) |
| { |
| if (device->state != EIS_DEVICE_STATE_NEW) |
| return; |
| |
| switch (type) { |
| case EIS_DEVICE_TYPE_VIRTUAL: |
| case EIS_DEVICE_TYPE_PHYSICAL: |
| break; |
| default: |
| log_bug_client(eis_device_get_context(device), "Invalid device type %u", type); |
| return; |
| } |
| |
| device->type = type; |
| } |
| |
| _public_ void |
| eis_device_configure_name(struct eis_device *device, const char *name) |
| { |
| if (device->state != EIS_DEVICE_STATE_NEW) |
| return; |
| |
| free(device->name); |
| device->name = xstrdup(name); |
| } |
| |
| _public_ void |
| eis_device_configure_capability(struct eis_device *device, enum eis_device_capability cap) |
| { |
| if (device->state != EIS_DEVICE_STATE_NEW) |
| return; |
| |
| if (!eis_seat_has_capability(eis_device_get_seat(device), cap)) |
| return; |
| |
| switch (cap) { |
| case EIS_DEVICE_CAP_POINTER: |
| case EIS_DEVICE_CAP_POINTER_ABSOLUTE: |
| case EIS_DEVICE_CAP_KEYBOARD: |
| case EIS_DEVICE_CAP_TOUCH: |
| case EIS_DEVICE_CAP_BUTTON: |
| case EIS_DEVICE_CAP_SCROLL: |
| case EIS_DEVICE_CAP_TEXT: |
| mask_add(device->capabilities, cap); |
| break; |
| } |
| } |
| |
| _public_ void |
| eis_device_configure_size(struct eis_device *device, uint32_t width, uint32_t height) |
| { |
| if (device->type != EIS_DEVICE_TYPE_PHYSICAL) { |
| log_bug_client(eis_device_get_context(device), |
| "Device type physical required for size"); |
| return; |
| } |
| |
| if (width > 2000 || height > 2000) |
| log_warn(eis_device_get_context(device), |
| "Suspicious device size: %ux%umm", |
| width, |
| height); |
| |
| device->width = width; |
| device->height = height; |
| } |
| |
| _public_ void |
| eis_device_add(struct eis_device *device) |
| { |
| struct eis_client *client = eis_device_get_client(device); |
| struct eis_seat *seat = eis_device_get_seat(device); |
| |
| if (device->state != EIS_DEVICE_STATE_NEW) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: device already (dis)connected", |
| __func__); |
| return; |
| } |
| |
| if (!device->capabilities) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: adding device without capabilities", |
| __func__); |
| } |
| |
| device->state = EIS_DEVICE_STATE_AWAITING_READY; |
| eis_client_register_object(client, &device->proto_object); |
| eis_seat_event_device(seat, device->proto_object.id, device->proto_object.version); |
| int rc = eis_device_event_name(device, device->name); |
| if (rc < 0) |
| goto out; |
| |
| rc = eis_device_event_device_type(device, device->type); |
| if (rc < 0) |
| goto out; |
| |
| if (device->type == EIS_DEVICE_TYPE_PHYSICAL) { |
| rc = eis_device_event_dimensions(device, device->width, device->height); |
| if (rc < 0) |
| goto out; |
| } |
| if (device->type == EIS_DEVICE_TYPE_VIRTUAL) { |
| struct eis_region *r; |
| list_for_each(r, &device->regions, link) { |
| if (r->mapping_id) { |
| if (client->interface_versions.ei_device >= |
| EIS_DEVICE_EVENT_REGION_MAPPING_ID_SINCE_VERSION) { |
| rc = eis_device_event_region_mapping_id(device, |
| r->mapping_id); |
| if (rc < 0) |
| goto out; |
| } else { |
| /* If our client doesn't support mapping_id, drop it */ |
| free(r->mapping_id); |
| r->mapping_id = NULL; |
| } |
| } |
| rc = eis_device_event_region(device, |
| r->x, |
| r->y, |
| r->width, |
| r->height, |
| r->physical_scale); |
| if (rc < 0) |
| goto out; |
| } |
| } |
| if (eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER)) { |
| device->pointer = eis_pointer_new(device); |
| rc = eis_device_event_interface(device, |
| eis_pointer_get_id(device->pointer), |
| EIS_POINTER_INTERFACE_NAME, |
| eis_pointer_get_version(device->pointer)); |
| if (rc < 0) |
| goto out; |
| } |
| if (eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE)) { |
| device->pointer_absolute = eis_pointer_absolute_new(device); |
| rc = eis_device_event_interface( |
| device, |
| eis_pointer_absolute_get_id(device->pointer_absolute), |
| EIS_POINTER_ABSOLUTE_INTERFACE_NAME, |
| eis_pointer_absolute_get_version(device->pointer_absolute)); |
| if (rc < 0) |
| goto out; |
| } |
| if (eis_device_has_capability(device, EIS_DEVICE_CAP_SCROLL)) { |
| device->scroll = eis_scroll_new(device); |
| rc = eis_device_event_interface(device, |
| eis_scroll_get_id(device->scroll), |
| EIS_SCROLL_INTERFACE_NAME, |
| eis_scroll_get_version(device->scroll)); |
| if (rc < 0) |
| goto out; |
| } |
| if (eis_device_has_capability(device, EIS_DEVICE_CAP_BUTTON)) { |
| device->button = eis_button_new(device); |
| rc = eis_device_event_interface(device, |
| eis_button_get_id(device->button), |
| EIS_BUTTON_INTERFACE_NAME, |
| eis_button_get_version(device->button)); |
| if (rc < 0) |
| goto out; |
| } |
| if (eis_device_has_capability(device, EIS_DEVICE_CAP_KEYBOARD)) { |
| device->keyboard = eis_keyboard_new(device); |
| rc = eis_device_event_interface(device, |
| eis_keyboard_get_id(device->keyboard), |
| EIS_KEYBOARD_INTERFACE_NAME, |
| eis_keyboard_get_version(device->keyboard)); |
| if (rc < 0) |
| goto out; |
| |
| if (device->keymap) |
| rc = eis_keyboard_event_keymap(device->keyboard, |
| device->keymap->type, |
| device->keymap->size, |
| device->keymap->fd); |
| if (rc < 0) |
| goto out; |
| } |
| if (eis_device_has_capability(device, EIS_DEVICE_CAP_TOUCH)) { |
| device->touchscreen = eis_touchscreen_new(device); |
| rc = eis_device_event_interface(device, |
| eis_touchscreen_get_id(device->touchscreen), |
| EIS_TOUCHSCREEN_INTERFACE_NAME, |
| eis_touchscreen_get_version(device->touchscreen)); |
| if (rc < 0) |
| goto out; |
| } |
| if (eis_device_has_capability(device, EIS_DEVICE_CAP_TEXT)) { |
| device->text = eis_text_new(device); |
| rc = eis_device_event_interface(device, |
| eis_text_get_id(device->text), |
| EIS_TEXT_INTERFACE_NAME, |
| eis_text_get_version(device->text)); |
| if (rc < 0) |
| goto out; |
| } |
| |
| rc = eis_device_event_done(device); |
| if (rc < 0) |
| goto out; |
| |
| struct eis *eis = eis_device_get_context(device); |
| if (client->interface_versions.ei_device < EIS_DEVICE_REQUEST_READY_SINCE_VERSION) { |
| device->state = EIS_DEVICE_STATE_PAUSED; |
| if (flag_is_set(eis->flags, EIS_FLAG_DEVICE_READY)) |
| eis_queue_device_ready_event(device); |
| } |
| |
| out: |
| if (rc < 0) { |
| log_error(eis_client_get_context(client), |
| "Failed to add device, disconnecting client"); |
| eis_client_disconnect(client); |
| } |
| return; |
| } |
| |
| _public_ void |
| eis_device_remove(struct eis_device *device) |
| { |
| struct eis_client *client = eis_device_get_client(device); |
| |
| if (device->state == EIS_DEVICE_STATE_DEAD) |
| return; |
| |
| if (device->state == EIS_DEVICE_STATE_EMULATING && |
| !eis_client_is_sender(eis_device_get_client(device))) |
| eis_device_stop_emulating(device); |
| |
| if (device->pointer) { |
| eis_pointer_event_destroyed(device->pointer, eis_client_get_next_serial(client)); |
| eis_pointer_unref(steal(&device->pointer)); |
| } |
| if (device->pointer_absolute) { |
| eis_pointer_absolute_event_destroyed(device->pointer_absolute, |
| eis_client_get_next_serial(client)); |
| eis_pointer_absolute_unref(steal(&device->pointer_absolute)); |
| } |
| if (device->button) { |
| eis_button_event_destroyed(device->button, eis_client_get_next_serial(client)); |
| eis_button_unref(steal(&device->button)); |
| } |
| if (device->scroll) { |
| eis_scroll_event_destroyed(device->scroll, eis_client_get_next_serial(client)); |
| eis_scroll_unref(steal(&device->scroll)); |
| } |
| if (device->touchscreen) { |
| eis_touchscreen_event_destroyed(device->touchscreen, |
| eis_client_get_next_serial(client)); |
| eis_touchscreen_unref(steal(&device->touchscreen)); |
| } |
| if (device->keyboard) { |
| eis_keyboard_event_destroyed(device->keyboard, eis_client_get_next_serial(client)); |
| eis_keyboard_unref(steal(&device->keyboard)); |
| } |
| if (device->text) { |
| eis_text_event_destroyed(device->text, eis_client_get_next_serial(client)); |
| eis_text_unref(steal(&device->text)); |
| } |
| |
| if (device->state != EIS_DEVICE_STATE_NEW) |
| eis_device_event_destroyed(device, eis_client_get_next_serial(client)); |
| |
| struct eis_event *event; |
| list_for_each_safe(event, &device->pending_event_queue, link) { |
| list_remove(&event->link); |
| eis_event_unref(event); |
| } |
| |
| device->state = EIS_DEVICE_STATE_DEAD; |
| eis_client_unregister_object(client, &device->proto_object); |
| list_remove(&device->link); |
| eis_device_unref(device); |
| } |
| |
| _public_ bool |
| eis_device_has_capability(struct eis_device *device, 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: |
| case EIS_DEVICE_CAP_BUTTON: |
| case EIS_DEVICE_CAP_SCROLL: |
| case EIS_DEVICE_CAP_TEXT: |
| return mask_all(device->capabilities, cap); |
| } |
| return false; |
| } |
| |
| static void |
| eis_device_frame_now(struct eis_device *device) |
| { |
| uint64_t now = eis_now(eis_device_get_context(device)); |
| |
| eis_device_frame(device, now); |
| } |
| |
| static void |
| _flush_frame(struct eis_device *device, const char *func) |
| { |
| if (device->send_frame_event) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: missing call to eis_device_frame()", |
| func); |
| eis_device_frame_now(device); |
| } |
| } |
| #define eis_device_flush_frame(d_) _flush_frame(d_, __func__) |
| |
| _public_ void |
| eis_device_start_emulating(struct eis_device *device, uint32_t sequence) |
| { |
| struct eis_client *client = eis_device_get_client(device); |
| |
| if (device->state != EIS_DEVICE_STATE_RESUMED) |
| return; |
| |
| assert(!device->send_frame_event); |
| |
| device->state = EIS_DEVICE_STATE_EMULATING; |
| |
| eis_device_event_start_emulating(device, eis_client_get_next_serial(client), sequence); |
| } |
| |
| _public_ void |
| eis_device_stop_emulating(struct eis_device *device) |
| { |
| struct eis_client *client = eis_device_get_client(device); |
| |
| if (device->state != EIS_DEVICE_STATE_EMULATING) |
| return; |
| |
| eis_device_flush_frame(device); |
| |
| device->state = EIS_DEVICE_STATE_RESUMED; |
| |
| eis_device_event_stop_emulating(device, eis_client_get_next_serial(client)); |
| } |
| |
| _public_ void |
| eis_device_pointer_motion(struct eis_device *device, double x, double y) |
| { |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER)) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: device is not a pointer", |
| __func__); |
| return; |
| } |
| |
| if (device->state != EIS_DEVICE_STATE_EMULATING) |
| return; |
| |
| device->send_frame_event = true; |
| |
| eis_pointer_event_motion_relative(device->pointer, x, y); |
| } |
| |
| _public_ void |
| eis_device_pointer_motion_absolute(struct eis_device *device, double x, double y) |
| { |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE)) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: device is not an absolute pointer", |
| __func__); |
| return; |
| } |
| |
| if (device->state != EIS_DEVICE_STATE_EMULATING) |
| return; |
| |
| if (!eis_device_in_region(device, x, y)) |
| return; |
| |
| device->send_frame_event = true; |
| |
| eis_pointer_absolute_event_motion_absolute(device->pointer_absolute, x, y); |
| } |
| |
| _public_ void |
| eis_device_button_button(struct eis_device *device, uint32_t button, bool is_press) |
| { |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_BUTTON)) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: device is not a button device", |
| __func__); |
| return; |
| } |
| |
| if (device->state != EIS_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(eis_device_get_context(device), |
| "%s: button code must be one of BTN_*", |
| __func__); |
| return; |
| } |
| |
| device->send_frame_event = true; |
| |
| eis_button_event_button(device->button, button, is_press); |
| } |
| |
| static inline void |
| eis_device_resume_scrolling(struct eis_device *device, double x, double y) |
| { |
| if (x) { |
| device->scroll_state.x_is_stopped = false; |
| device->scroll_state.x_is_cancelled = false; |
| } |
| if (y) { |
| device->scroll_state.y_is_stopped = false; |
| device->scroll_state.y_is_cancelled = false; |
| } |
| } |
| |
| _public_ void |
| eis_device_scroll_delta(struct eis_device *device, double x, double y) |
| { |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_SCROLL)) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: device is not a scroll device", |
| __func__); |
| return; |
| } |
| |
| if (device->state != EIS_DEVICE_STATE_EMULATING) |
| return; |
| |
| eis_device_resume_scrolling(device, x, y); |
| |
| device->send_frame_event = true; |
| |
| eis_scroll_event_scroll(device->scroll, x, y); |
| } |
| |
| _public_ void |
| eis_device_scroll_stop(struct eis_device *device, bool x, bool y) |
| { |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_SCROLL)) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: device is not a scroll device", |
| __func__); |
| return; |
| } |
| |
| if (device->state != EIS_DEVICE_STATE_EMULATING) |
| return; |
| |
| /* Filter out duplicate scroll stop requests */ |
| if (x && !device->scroll_state.x_is_stopped) |
| device->scroll_state.x_is_stopped = true; |
| else |
| x = false; |
| |
| if (y && !device->scroll_state.y_is_stopped) |
| device->scroll_state.y_is_stopped = true; |
| else |
| y = false; |
| |
| if (x || y) { |
| device->send_frame_event = true; |
| eis_scroll_event_scroll_stop(device->scroll, x, y, false); |
| } |
| } |
| |
| _public_ void |
| eis_device_scroll_cancel(struct eis_device *device, bool x, bool y) |
| { |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_SCROLL)) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: device is not a scroll device", |
| __func__); |
| return; |
| } |
| |
| if (device->state != EIS_DEVICE_STATE_EMULATING) |
| return; |
| |
| /* Filter out duplicate scroll cancelled requests */ |
| if (x && !device->scroll_state.x_is_cancelled) { |
| device->scroll_state.x_is_stopped = true; |
| device->scroll_state.x_is_cancelled = true; |
| } else { |
| x = false; |
| } |
| |
| if (y && !device->scroll_state.y_is_cancelled) { |
| device->scroll_state.y_is_stopped = true; |
| device->scroll_state.y_is_cancelled = true; |
| } else { |
| y = false; |
| } |
| |
| if (x || y) { |
| device->send_frame_event = true; |
| eis_scroll_event_scroll_stop(device->scroll, x, y, true); |
| } |
| } |
| |
| _public_ void |
| eis_device_scroll_discrete(struct eis_device *device, int32_t x, int32_t y) |
| { |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_SCROLL)) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: device is not a scroll device", |
| __func__); |
| return; |
| } |
| |
| if (device->state != EIS_DEVICE_STATE_EMULATING) |
| return; |
| |
| if (abs(x) == 1 || abs(y) == 1) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: suspicious discrete event value 1, did you mean 120?", |
| __func__); |
| } |
| |
| eis_device_resume_scrolling(device, x, y); |
| |
| device->send_frame_event = true; |
| |
| eis_scroll_event_scroll_discrete(device->scroll, x, y); |
| } |
| |
| _public_ void |
| eis_device_keyboard_key(struct eis_device *device, uint32_t key, bool is_press) |
| { |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_KEYBOARD)) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: device is not a keyboard", |
| __func__); |
| return; |
| } |
| |
| if (device->state != EIS_DEVICE_STATE_EMULATING) |
| return; |
| |
| device->send_frame_event = true; |
| |
| eis_keyboard_event_key(device->keyboard, key, is_press); |
| } |
| |
| _public_ OBJECT_IMPLEMENT_REF(eis_touch); |
| _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_touch); |
| _public_ |
| OBJECT_IMPLEMENT_GETTER(eis_touch, device, struct eis_device *); |
| _public_ |
| OBJECT_IMPLEMENT_GETTER(eis_touch, user_data, void *); |
| _public_ |
| OBJECT_IMPLEMENT_SETTER(eis_touch, user_data, void *); |
| |
| static void |
| eis_touch_destroy(struct eis_touch *touch) |
| { |
| if (touch->state == TOUCH_IS_DOWN) |
| eis_touch_up(touch); |
| /* Enforce a frame, otherwise we're just pending. If the client |
| * doesn't want this, it needs to eis_touch_up() */ |
| eis_device_frame_now(touch->device); |
| eis_device_unref(touch->device); |
| } |
| |
| static OBJECT_IMPLEMENT_CREATE(eis_touch); |
| |
| _public_ struct eis_touch * |
| eis_device_touch_new(struct eis_device *device) |
| { |
| static uint32_t tracking_id = 0; |
| |
| /* Not using the device as parent object because we need a ref |
| * to it */ |
| struct eis_touch *touch = eis_touch_create(NULL); |
| |
| touch->device = eis_device_ref(device); |
| touch->state = TOUCH_IS_NEW; |
| touch->tracking_id = ++tracking_id; |
| |
| return touch; |
| } |
| |
| _public_ void |
| eis_touch_down(struct eis_touch *touch, double x, double y) |
| { |
| struct eis_device *device = eis_touch_get_device(touch); |
| |
| if (touch->state != TOUCH_IS_NEW) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: touch %u already down or up", |
| __func__, |
| touch->tracking_id); |
| return; |
| } |
| |
| if (!eis_device_in_region(device, x, y)) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: touch %u has invalid x/y coordinates", |
| __func__, |
| touch->tracking_id); |
| touch->state = TOUCH_IS_UP; |
| return; |
| } |
| |
| touch->state = TOUCH_IS_DOWN; |
| device->send_frame_event = true; |
| |
| eis_touchscreen_event_down(device->touchscreen, touch->tracking_id, x, y); |
| } |
| |
| _public_ void |
| eis_touch_motion(struct eis_touch *touch, double x, double y) |
| { |
| if (touch->state != TOUCH_IS_DOWN) |
| return; |
| |
| struct eis_device *device = eis_touch_get_device(touch); |
| if (!eis_device_in_region(device, x, y)) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: touch %u has invalid x/y coordinates", |
| __func__, |
| touch->tracking_id); |
| eis_touch_up(touch); |
| return; |
| } |
| |
| device->send_frame_event = true; |
| |
| eis_touchscreen_event_motion(device->touchscreen, touch->tracking_id, x, y); |
| } |
| |
| _public_ void |
| eis_touch_up(struct eis_touch *touch) |
| { |
| struct eis_device *device = eis_touch_get_device(touch); |
| |
| if (touch->state != TOUCH_IS_DOWN) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: touch %u is not currently down", |
| __func__, |
| touch->tracking_id); |
| return; |
| } |
| |
| touch->state = TOUCH_IS_UP; |
| device->send_frame_event = true; |
| |
| eis_touchscreen_event_up(device->touchscreen, touch->tracking_id); |
| } |
| |
| _public_ void |
| eis_touch_cancel(struct eis_touch *touch) |
| { |
| struct eis_device *device = eis_touch_get_device(touch); |
| |
| if (touch->state != TOUCH_IS_DOWN) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: touch %u is not currently down", |
| __func__, |
| touch->tracking_id); |
| return; |
| } |
| |
| touch->state = TOUCH_IS_UP; |
| device->send_frame_event = true; |
| |
| struct eis_client *client = eis_device_get_client(device); |
| if (client->interface_versions.ei_touchscreen >= EIS_TOUCHSCREEN_EVENT_CANCEL_SINCE_VERSION) |
| eis_touchscreen_event_cancel(device->touchscreen, touch->tracking_id); |
| else |
| eis_touchscreen_event_up(device->touchscreen, touch->tracking_id); |
| } |
| |
| _public_ void |
| eis_device_text_keysym(struct eis_device *device, uint32_t keysym, bool is_press) |
| { |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_TEXT)) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: device is not a text device", |
| __func__); |
| return; |
| } |
| |
| if (device->state != EIS_DEVICE_STATE_EMULATING) |
| return; |
| |
| device->send_frame_event = true; |
| |
| eis_text_event_keysym(device->text, keysym, is_press); |
| } |
| |
| _public_ void |
| eis_device_text_utf8(struct eis_device *device, const char *utf8) |
| { |
| eis_device_text_utf8_with_length(device, utf8, utf8 ? strlen(utf8) : 0); |
| } |
| |
| _public_ void |
| eis_device_text_utf8_with_length(struct eis_device *device, const char *utf8, size_t length) |
| { |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_TEXT)) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: device is not a text device", |
| __func__); |
| return; |
| } |
| |
| if (device->state != EIS_DEVICE_STATE_EMULATING) |
| return; |
| |
| if (!utf8) { |
| log_bug_client(eis_device_get_context(device), "%s: utf8 is NULL", __func__); |
| return; |
| } |
| |
| if (length == 0) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: zero-length utf8 string", |
| __func__); |
| return; |
| } |
| |
| if (length > 254) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: utf8 string exceeds max length of 255 bytes", |
| __func__); |
| return; |
| } |
| |
| device->send_frame_event = true; |
| |
| char buf[255] = { 0 }; |
| memcpy(buf, utf8, length); |
| eis_text_event_utf8(device->text, buf); |
| } |
| |
| _public_ void |
| eis_device_frame(struct eis_device *device, uint64_t time) |
| { |
| struct eis_client *client = eis_device_get_client(device); |
| |
| if (device->state != EIS_DEVICE_STATE_EMULATING) |
| return; |
| |
| if (!device->send_frame_event) |
| return; |
| |
| device->send_frame_event = false; |
| |
| eis_device_event_frame(device, eis_client_get_next_serial(client), time); |
| } |
| |
| void |
| eis_device_closed_by_client(struct eis_device *device) |
| { |
| switch (device->state) { |
| case EIS_DEVICE_STATE_DEAD: |
| case EIS_DEVICE_STATE_CLOSED_BY_CLIENT: |
| /* libei bug, ignore */ |
| break; |
| case EIS_DEVICE_STATE_EMULATING: |
| if (!eis_client_is_sender(eis_device_get_client(device))) |
| eis_queue_device_stop_emulating_event(device); |
| _fallthrough_; |
| case EIS_DEVICE_STATE_AWAITING_READY: |
| eis_queue_device_ready_event(device); |
| _fallthrough_; |
| case EIS_DEVICE_STATE_NEW: |
| case EIS_DEVICE_STATE_PAUSED: |
| case EIS_DEVICE_STATE_RESUMED: |
| eis_queue_device_closed_event(device); |
| device->state = EIS_DEVICE_STATE_CLOSED_BY_CLIENT; |
| break; |
| } |
| } |
| |
| _public_ void |
| eis_device_pause(struct eis_device *device) |
| { |
| struct eis_client *client = eis_device_get_client(device); |
| |
| switch (device->state) { |
| case EIS_DEVICE_STATE_RESUMED: |
| case EIS_DEVICE_STATE_EMULATING: |
| break; |
| default: |
| return; |
| } |
| |
| device->state = EIS_DEVICE_STATE_PAUSED; |
| eis_device_event_paused(device, eis_client_get_next_serial(client)); |
| |
| memset(device->key_button_state.down, 0, sizeof(device->key_button_state.down)); |
| |
| for (size_t i = 0; i < ARRAY_LENGTH(device->touch_state.down); i++) { |
| device->touch_state.down[i] = UINT64_MAX; |
| } |
| } |
| |
| _public_ void |
| eis_device_resume(struct eis_device *device) |
| { |
| struct eis_client *client = eis_device_get_client(device); |
| |
| if (device->state == EIS_DEVICE_STATE_AWAITING_READY) { |
| log_bug_client(eis_client_get_context(client), |
| "Attempting to resume a device before DEVICE_READY"); |
| return; |
| } |
| |
| if (device->state != EIS_DEVICE_STATE_PAUSED) |
| return; |
| |
| device->state = EIS_DEVICE_STATE_RESUMED; |
| eis_device_event_resumed(device, eis_client_get_next_serial(client)); |
| } |
| |
| _public_ void |
| eis_device_keyboard_send_xkb_modifiers(struct eis_device *device, |
| uint32_t depressed, |
| uint32_t latched, |
| uint32_t locked, |
| uint32_t group) |
| { |
| if (!eis_device_has_capability(device, EIS_DEVICE_CAP_KEYBOARD)) { |
| log_bug_client(eis_device_get_context(device), |
| "%s: device is not a keyboard", |
| __func__); |
| return; |
| } |
| |
| eis_keyboard_event_modifiers(device->keyboard, |
| eis_client_get_next_serial(eis_device_get_client(device)), |
| depressed, |
| locked, |
| latched, |
| group); |
| } |