mirror of
https://gitlab.freedesktop.org/libinput/libei.git
synced 2025-12-30 14:10:08 +01:00
The text capability allows for two types of events on that interface: - XKB keysym events, e.g. XK_ssharp (0x00df) with a press/release state - UTF8 strings Keysym events are useful for scenarious where the hardware keycode is unsuitable due to potentially different key mappings on the client and server side and/or the client just not wanting to worry about those mappings. For example a client may want to send Ctrl+C instead of what effectively is now keycodes for what may or may not be a C key. UTF8 strings take this a step further and provide a full string (with implementation-defined size limits to avoid OOM). Unlike e.g. the wayland text input protocols the assumption is here that the interaction required to generate that string has already been performed before the final string is sent over the wire. Closes #73
1107 lines
26 KiB
C
1107 lines
26 KiB
C
/* 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 <fcntl.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
|
|
#include "util-io.h"
|
|
#include "util-macros.h"
|
|
#include "util-object.h"
|
|
#include "util-sources.h"
|
|
#include "util-strings.h"
|
|
#include "util-time.h"
|
|
#include "util-version.h"
|
|
|
|
#include "libei.h"
|
|
#include "libei-private.h"
|
|
#include "brei-shared.h"
|
|
#include "ei-proto.h"
|
|
|
|
_Static_assert(sizeof(enum ei_device_capability) == sizeof(int), "Invalid enum size");
|
|
_Static_assert(sizeof(enum ei_keymap_type) == sizeof(int), "Invalid enum size");
|
|
_Static_assert(sizeof(enum ei_event_type) == sizeof(int), "Invalid enum size");
|
|
_Static_assert(sizeof(enum ei_log_priority) == sizeof(int), "Invalid enum size");
|
|
|
|
static void
|
|
ei_unsent_free(struct ei_unsent *unsent);
|
|
|
|
static void
|
|
ei_defunct_object_free(struct ei_defunct_object *obj);
|
|
|
|
static void
|
|
ei_destroy(struct ei *ei)
|
|
{
|
|
ei_disconnect(ei);
|
|
|
|
struct ei_event *e;
|
|
while ((e = ei_get_event(ei)) != NULL)
|
|
ei_event_unref(e);
|
|
|
|
struct ei_unsent *unsent;
|
|
list_for_each_safe(unsent, &ei->unsent_queue, node) {
|
|
ei_unsent_free(unsent);
|
|
}
|
|
|
|
if (ei->backend_interface.destroy)
|
|
ei->backend_interface.destroy(ei, ei->backend);
|
|
|
|
ei->backend = NULL;
|
|
ei_handshake_unref(ei->handshake);
|
|
ei_connection_unref(ei->connection);
|
|
brei_context_unref(ei->brei);
|
|
|
|
sink_unref(ei->sink);
|
|
free(ei->name);
|
|
|
|
struct ei_defunct_object *obj;
|
|
list_for_each_safe(obj, &ei->defunct_objects, node) {
|
|
ei_defunct_object_free(obj);
|
|
}
|
|
}
|
|
|
|
static
|
|
OBJECT_IMPLEMENT_CREATE(ei);
|
|
_public_
|
|
OBJECT_IMPLEMENT_REF(ei);
|
|
_public_
|
|
OBJECT_IMPLEMENT_UNREF_CLEANUP(ei);
|
|
_public_
|
|
OBJECT_IMPLEMENT_SETTER(ei, user_data, void *);
|
|
_public_
|
|
OBJECT_IMPLEMENT_GETTER(ei, user_data, void *);
|
|
OBJECT_IMPLEMENT_GETTER(ei, connection, struct ei_connection *);
|
|
OBJECT_IMPLEMENT_GETTER(ei, serial, uint32_t);
|
|
|
|
DEFINE_UNREF_CLEANUP_FUNC(brei_result);
|
|
DEFINE_UNREF_CLEANUP_FUNC(ei_pingpong);
|
|
|
|
struct ei *
|
|
ei_get_context(struct ei *ei)
|
|
{
|
|
return ei; /* for the protocol bindings */
|
|
}
|
|
|
|
static struct ei *
|
|
ei_create_context(bool is_sender, void *user_data)
|
|
{
|
|
_unref_(ei) *ei = ei_create(NULL);
|
|
|
|
ei->state = EI_STATE_NEW;
|
|
list_init(&ei->event_queue);
|
|
list_init(&ei->seats);
|
|
list_init(&ei->proto_objects);
|
|
list_init(&ei->unsent_queue);
|
|
list_init(&ei->defunct_objects);
|
|
|
|
ei->interface_versions = (struct ei_interface_versions){
|
|
.ei_connection = VERSION_V(1),
|
|
.ei_handshake = VERSION_V(1),
|
|
.ei_callback = VERSION_V(1),
|
|
.ei_pingpong = VERSION_V(1),
|
|
.ei_seat = VERSION_V(1),
|
|
.ei_device = VERSION_V(3),
|
|
.ei_pointer = VERSION_V(1),
|
|
.ei_pointer_absolute = VERSION_V(1),
|
|
.ei_scroll = VERSION_V(1),
|
|
.ei_button = VERSION_V(1),
|
|
.ei_keyboard = VERSION_V(1),
|
|
.ei_text = VERSION_V(1),
|
|
.ei_touchscreen = VERSION_V(2),
|
|
};
|
|
/* This must be v1 until the server tells us otherwise */
|
|
ei->handshake = ei_handshake_new(ei, VERSION_V(1));
|
|
ei->next_object_id = 1;
|
|
ei->brei = brei_context_new(ei);
|
|
brei_context_set_log_context(ei->brei, ei);
|
|
brei_context_set_log_func(ei->brei, (brei_logfunc_t)ei_log_msg_va);
|
|
|
|
ei_log_set_handler(ei, NULL);
|
|
ei_log_set_priority(ei, EI_LOG_PRIORITY_INFO);
|
|
ei->sink = sink_new();
|
|
if (!ei->sink)
|
|
return NULL;
|
|
|
|
ei->user_data = user_data;
|
|
ei->backend = NULL;
|
|
ei->is_sender = is_sender;
|
|
|
|
return steal(&ei);
|
|
}
|
|
|
|
object_id_t
|
|
ei_get_new_id(struct ei *ei)
|
|
{
|
|
static const uint64_t server_range = 0xff00000000000000;
|
|
return ei->next_object_id++ & ~server_range;
|
|
}
|
|
|
|
void
|
|
ei_update_serial(struct ei *ei, uint32_t serial)
|
|
{
|
|
ei->serial = serial;
|
|
}
|
|
|
|
void
|
|
ei_register_object(struct ei *ei, struct brei_object *object)
|
|
{
|
|
log_debug(ei, "registering %s v%u object %#" PRIx64 "", object->interface->name, object->version, object->id);
|
|
list_append(&ei->proto_objects, &object->link);
|
|
}
|
|
|
|
static void
|
|
ei_defunct_object_free(struct ei_defunct_object *obj)
|
|
{
|
|
list_remove(&obj->node);
|
|
free(obj);
|
|
}
|
|
|
|
void
|
|
ei_unregister_object(struct ei *ei, struct brei_object *object)
|
|
{
|
|
log_debug(ei, "deregistering %s v%u object %#" PRIx64 "", object->interface->name, object->version, object->id);
|
|
list_remove(&object->link);
|
|
|
|
struct ei_defunct_object *obj = xalloc(sizeof *obj);
|
|
obj->object_id = object->id;
|
|
obj->time = ei_now(ei);
|
|
list_append(&ei->defunct_objects, &obj->node);
|
|
}
|
|
|
|
_public_ bool
|
|
ei_is_sender(struct ei *ei)
|
|
{
|
|
return ei->is_sender;
|
|
}
|
|
|
|
_public_ struct ei *
|
|
ei_new(void *user_data)
|
|
{
|
|
return ei_new_sender(user_data);
|
|
}
|
|
|
|
_public_ struct ei *
|
|
ei_new_sender(void *user_data)
|
|
{
|
|
return ei_create_context(true, user_data);
|
|
}
|
|
|
|
_public_ struct ei *
|
|
ei_new_receiver(void *user_data)
|
|
{
|
|
return ei_create_context(false, user_data);
|
|
}
|
|
|
|
_public_ int
|
|
ei_get_fd(struct ei *ei)
|
|
{
|
|
return sink_get_fd(ei->sink);
|
|
}
|
|
|
|
_public_ void
|
|
ei_dispatch(struct ei *ei)
|
|
{
|
|
sink_dispatch(ei->sink);
|
|
}
|
|
|
|
static void
|
|
update_event_timestamp(struct ei_event *event, uint64_t time)
|
|
{
|
|
switch (event->type) {
|
|
case EI_EVENT_POINTER_MOTION:
|
|
case EI_EVENT_POINTER_MOTION_ABSOLUTE:
|
|
case EI_EVENT_BUTTON_BUTTON:
|
|
case EI_EVENT_SCROLL_DELTA:
|
|
case EI_EVENT_SCROLL_STOP:
|
|
case EI_EVENT_SCROLL_CANCEL:
|
|
case EI_EVENT_SCROLL_DISCRETE:
|
|
case EI_EVENT_KEYBOARD_KEY:
|
|
case EI_EVENT_TOUCH_DOWN:
|
|
case EI_EVENT_TOUCH_UP:
|
|
case EI_EVENT_TOUCH_MOTION:
|
|
case EI_EVENT_TEXT_KEYSYM:
|
|
case EI_EVENT_TEXT_UTF8:
|
|
if (event->timestamp != 0) {
|
|
log_bug(ei_event_get_context(event),
|
|
"Unexpected timestamp for event of type: %s",
|
|
ei_event_type_to_string(event->type));
|
|
return;
|
|
}
|
|
event->timestamp = time;
|
|
break;
|
|
default:
|
|
log_bug(ei_event_get_context(event),
|
|
"Unexpected event %s in pending queue event",
|
|
ei_event_type_to_string(event->type));
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void
|
|
queue_event(struct ei *ei, struct ei_event *event)
|
|
{
|
|
struct ei_device *device = ei_event_get_device(event);
|
|
struct list *queue = &ei->event_queue;
|
|
const char *prefix = "";
|
|
|
|
switch (event->type) {
|
|
case EI_EVENT_POINTER_MOTION:
|
|
case EI_EVENT_POINTER_MOTION_ABSOLUTE:
|
|
case EI_EVENT_BUTTON_BUTTON:
|
|
case EI_EVENT_SCROLL_DELTA:
|
|
case EI_EVENT_SCROLL_STOP:
|
|
case EI_EVENT_SCROLL_CANCEL:
|
|
case EI_EVENT_SCROLL_DISCRETE:
|
|
case EI_EVENT_KEYBOARD_KEY:
|
|
case EI_EVENT_TOUCH_DOWN:
|
|
case EI_EVENT_TOUCH_UP:
|
|
case EI_EVENT_TOUCH_MOTION:
|
|
case EI_EVENT_TEXT_KEYSYM:
|
|
case EI_EVENT_TEXT_UTF8:
|
|
prefix = "pending ";
|
|
queue = &device->pending_event_queue;
|
|
break;
|
|
case EI_EVENT_FRAME:
|
|
/* silently discard empty frames */
|
|
if (list_empty(&device->pending_event_queue))
|
|
return;
|
|
|
|
struct ei_event *pending;
|
|
list_for_each_safe(pending, &device->pending_event_queue, link) {
|
|
update_event_timestamp(pending, event->timestamp);
|
|
list_remove(&pending->link);
|
|
list_append(&ei->event_queue, &pending->link);
|
|
}
|
|
break;
|
|
default:
|
|
if (device && !list_empty(&device->pending_event_queue))
|
|
ei_queue_frame_event(device, ei_now(ei));
|
|
break;
|
|
}
|
|
|
|
log_debug(ei, "queuing %sevent type %s (%u)",
|
|
prefix,
|
|
ei_event_type_to_string(event->type), event->type);
|
|
|
|
list_append(queue, &event->link);
|
|
}
|
|
|
|
void
|
|
ei_queue_connect_event(struct ei *ei)
|
|
{
|
|
struct ei_event *e = ei_event_new(ei);
|
|
e->type = EI_EVENT_CONNECT;
|
|
|
|
queue_event(ei, e);
|
|
}
|
|
|
|
void
|
|
ei_queue_disconnect_event(struct ei *ei)
|
|
{
|
|
struct ei_event *e = ei_event_new(ei);
|
|
e->type = EI_EVENT_DISCONNECT;
|
|
|
|
queue_event(ei, e);
|
|
}
|
|
|
|
void
|
|
ei_queue_pong_event(struct ei *ei, struct ei_ping *ping)
|
|
{
|
|
struct ei_event *e = ei_event_new(ei);
|
|
e->type = EI_EVENT_PONG;
|
|
e->pong.ping = ei_ping_ref(ping);
|
|
|
|
queue_event(ei, e);
|
|
}
|
|
|
|
void
|
|
ei_sync_event_send_done(struct ei_event *e)
|
|
{
|
|
struct ei *ei = ei_event_get_context(e);
|
|
_unref_(ei_pingpong) *pp = steal(&e->sync.pingpong);
|
|
log_debug(ei_event_get_context(e),
|
|
"object %#" PRIx64 ": ping pong done",
|
|
ei_pingpong_get_id(pp));
|
|
|
|
if (ei->state < EI_STATE_DISCONNECTED)
|
|
ei_pingpong_request_done(pp, 0);
|
|
}
|
|
|
|
void
|
|
ei_queue_sync_event(struct ei *ei, struct ei_pingpong *ping)
|
|
{
|
|
struct ei_event *e = ei_event_new(ei);
|
|
e->type = EI_EVENT_SYNC;
|
|
e->sync.pingpong = ei_pingpong_ref(ping);
|
|
|
|
queue_event(ei, e);
|
|
}
|
|
|
|
void
|
|
ei_queue_seat_added_event(struct ei_seat *seat)
|
|
{
|
|
struct ei *ei= ei_seat_get_context(seat);
|
|
|
|
struct ei_event *e = ei_event_new(ei);
|
|
e->type = EI_EVENT_SEAT_ADDED;
|
|
e->seat = ei_seat_ref(seat);
|
|
|
|
queue_event(ei, e);
|
|
}
|
|
|
|
void
|
|
ei_queue_seat_removed_event(struct ei_seat *seat)
|
|
{
|
|
struct ei *ei= ei_seat_get_context(seat);
|
|
|
|
struct ei_event *e = ei_event_new(ei);
|
|
e->type = EI_EVENT_SEAT_REMOVED;
|
|
e->seat = ei_seat_ref(seat);
|
|
|
|
queue_event(ei, e);
|
|
}
|
|
|
|
static void
|
|
queue_device_added_event(struct ei_device *device)
|
|
{
|
|
struct ei *ei= ei_device_get_context(device);
|
|
struct ei_event *e = ei_event_new_for_device(device);
|
|
|
|
e->type = EI_EVENT_DEVICE_ADDED;
|
|
|
|
queue_event(ei, e);
|
|
}
|
|
|
|
static void
|
|
queue_device_removed_event(struct ei_device *device)
|
|
{
|
|
struct ei *ei= ei_device_get_context(device);
|
|
struct ei_event *e = ei_event_new_for_device(device);
|
|
|
|
e->type = EI_EVENT_DEVICE_REMOVED;
|
|
|
|
queue_event(ei, e);
|
|
}
|
|
|
|
void
|
|
ei_queue_device_paused_event(struct ei_device *device)
|
|
{
|
|
struct ei *ei= ei_device_get_context(device);
|
|
struct ei_event *e = ei_event_new_for_device(device);
|
|
|
|
e->type = EI_EVENT_DEVICE_PAUSED;
|
|
|
|
queue_event(ei, e);
|
|
}
|
|
|
|
void
|
|
ei_queue_device_resumed_event(struct ei_device *device)
|
|
{
|
|
struct ei *ei= ei_device_get_context(device);
|
|
struct ei_event *e = ei_event_new_for_device(device);
|
|
|
|
e->type = EI_EVENT_DEVICE_RESUMED;
|
|
|
|
queue_event(ei, e);
|
|
}
|
|
|
|
void
|
|
ei_queue_keyboard_modifiers_event(struct ei_device *device,
|
|
const struct ei_xkb_modifiers *mods)
|
|
{
|
|
struct ei *ei= ei_device_get_context(device);
|
|
struct ei_event *e = ei_event_new_for_device(device);
|
|
|
|
e->type = EI_EVENT_KEYBOARD_MODIFIERS;
|
|
e->modifiers = *mods;
|
|
|
|
queue_event(ei, e);
|
|
}
|
|
|
|
void
|
|
ei_queue_frame_event(struct ei_device *device, uint64_t time)
|
|
{
|
|
struct ei_event *e = ei_event_new_for_device(device);
|
|
|
|
e->type = EI_EVENT_FRAME;
|
|
e->timestamp = time;
|
|
|
|
queue_event(ei_device_get_context(device), e);
|
|
}
|
|
|
|
void
|
|
ei_queue_device_start_emulating_event(struct ei_device *device, uint32_t sequence)
|
|
{
|
|
struct ei_event *e = ei_event_new_for_device(device);
|
|
|
|
e->type = EI_EVENT_DEVICE_START_EMULATING;
|
|
e->start_emulating.sequence = sequence;
|
|
|
|
queue_event(ei_device_get_context(device), e);
|
|
}
|
|
|
|
void
|
|
ei_queue_device_stop_emulating_event(struct ei_device *device)
|
|
{
|
|
struct ei_event *e = ei_event_new_for_device(device);
|
|
|
|
e->type = EI_EVENT_DEVICE_STOP_EMULATING;
|
|
|
|
queue_event(ei_device_get_context(device), e);
|
|
}
|
|
|
|
void
|
|
ei_queue_pointer_rel_event(struct ei_device *device,
|
|
double dx, double dy)
|
|
{
|
|
struct ei_event *e = ei_event_new_for_device(device);
|
|
|
|
e->type = EI_EVENT_POINTER_MOTION;
|
|
e->pointer.dx = dx;
|
|
e->pointer.dy = dy;
|
|
|
|
queue_event(ei_device_get_context(device), e);
|
|
}
|
|
|
|
void
|
|
ei_queue_pointer_abs_event(struct ei_device *device,
|
|
double x, double y)
|
|
{
|
|
struct ei_event *e = ei_event_new_for_device(device);
|
|
|
|
e->type = EI_EVENT_POINTER_MOTION_ABSOLUTE;
|
|
e->pointer.absx = x;
|
|
e->pointer.absy = y;
|
|
|
|
queue_event(ei_device_get_context(device), e);
|
|
}
|
|
|
|
void
|
|
ei_queue_pointer_button_event(struct ei_device *device, uint32_t button,
|
|
bool is_press)
|
|
{
|
|
struct ei_event *e = ei_event_new_for_device(device);
|
|
|
|
e->type = EI_EVENT_BUTTON_BUTTON;
|
|
e->pointer.button = button;
|
|
e->pointer.button_is_press = is_press;
|
|
|
|
queue_event(ei_device_get_context(device), e);
|
|
}
|
|
|
|
void
|
|
ei_queue_pointer_scroll_event(struct ei_device *device,
|
|
double x, double y)
|
|
{
|
|
struct ei_event *e = ei_event_new_for_device(device);
|
|
|
|
e->type = EI_EVENT_SCROLL_DELTA;
|
|
e->pointer.sx = x;
|
|
e->pointer.sy = y;
|
|
|
|
queue_event(ei_device_get_context(device), e);
|
|
}
|
|
|
|
void
|
|
ei_queue_pointer_scroll_discrete_event(struct ei_device *device,
|
|
int32_t x, int32_t y)
|
|
{
|
|
struct ei_event *e = ei_event_new_for_device(device);
|
|
|
|
e->type = EI_EVENT_SCROLL_DISCRETE;
|
|
e->pointer.sdx = x;
|
|
e->pointer.sdy = y;
|
|
|
|
queue_event(ei_device_get_context(device), e);
|
|
}
|
|
|
|
void
|
|
ei_queue_pointer_scroll_stop_event(struct ei_device *device, bool x, bool y)
|
|
{
|
|
struct ei_event *e = ei_event_new_for_device(device);
|
|
|
|
e->type = EI_EVENT_SCROLL_STOP;
|
|
e->pointer.stop_x = x;
|
|
e->pointer.stop_y = y;
|
|
|
|
queue_event(ei_device_get_context(device), e);
|
|
}
|
|
|
|
void
|
|
ei_queue_pointer_scroll_cancel_event(struct ei_device *device, bool x, bool y)
|
|
{
|
|
struct ei_event *e = ei_event_new_for_device(device);
|
|
|
|
e->type = EI_EVENT_SCROLL_CANCEL;
|
|
e->pointer.stop_x = x;
|
|
e->pointer.stop_y = y;
|
|
|
|
queue_event(ei_device_get_context(device), e);
|
|
}
|
|
|
|
void
|
|
ei_queue_keyboard_key_event(struct ei_device *device, uint32_t key,
|
|
bool is_press)
|
|
{
|
|
struct ei_event *e = ei_event_new_for_device(device);
|
|
|
|
e->type = EI_EVENT_KEYBOARD_KEY;
|
|
e->keyboard.key = key;
|
|
e->keyboard.key_is_press = is_press;
|
|
|
|
queue_event(ei_device_get_context(device), e);
|
|
}
|
|
|
|
void
|
|
ei_queue_touch_down_event(struct ei_device *device, uint32_t touchid,
|
|
double x, double y)
|
|
{
|
|
struct ei_event *e = ei_event_new_for_device(device);
|
|
|
|
e->type = EI_EVENT_TOUCH_DOWN;
|
|
e->touch.touchid = touchid;
|
|
e->touch.x = x;
|
|
e->touch.y = y;
|
|
|
|
queue_event(ei_device_get_context(device), e);
|
|
}
|
|
|
|
void
|
|
ei_queue_touch_motion_event(struct ei_device *device, uint32_t touchid,
|
|
double x, double y)
|
|
{
|
|
struct ei_event *e = ei_event_new_for_device(device);
|
|
|
|
e->type = EI_EVENT_TOUCH_MOTION;
|
|
e->touch.touchid = touchid;
|
|
e->touch.x = x;
|
|
e->touch.y = y;
|
|
|
|
queue_event(ei_device_get_context(device), e);
|
|
}
|
|
|
|
void
|
|
ei_queue_touch_up_event(struct ei_device *device, uint32_t touchid)
|
|
{
|
|
struct ei_event *e = ei_event_new_for_device(device);
|
|
|
|
e->type = EI_EVENT_TOUCH_UP;
|
|
e->touch.touchid = touchid;
|
|
e->touch.is_cancel = false;
|
|
|
|
queue_event(ei_device_get_context(device), e);
|
|
}
|
|
|
|
void
|
|
ei_queue_touch_cancel_event(struct ei_device *device, uint32_t touchid)
|
|
{
|
|
struct ei_event *e = ei_event_new_for_device(device);
|
|
|
|
e->type = EI_EVENT_TOUCH_UP;
|
|
e->touch.touchid = touchid;
|
|
e->touch.is_cancel = true;
|
|
|
|
queue_event(ei_device_get_context(device), e);
|
|
}
|
|
|
|
void
|
|
ei_queue_text_keysym_event(struct ei_device *device, uint32_t keysym,
|
|
bool is_press)
|
|
{
|
|
struct ei_event *e = ei_event_new_for_device(device);
|
|
|
|
e->type = EI_EVENT_TEXT_KEYSYM;
|
|
e->text.keysym = keysym;
|
|
e->text.is_press = is_press;
|
|
|
|
queue_event(ei_device_get_context(device), e);
|
|
}
|
|
|
|
void
|
|
ei_queue_text_utf8_event(struct ei_device *device, const char *utf8)
|
|
{
|
|
struct ei_event *e = ei_event_new_for_device(device);
|
|
|
|
e->type = EI_EVENT_TEXT_UTF8;
|
|
e->text.utf8 = xstrdup(utf8);
|
|
|
|
queue_event(ei_device_get_context(device), e);
|
|
}
|
|
|
|
_public_ void
|
|
ei_disconnect(struct ei *ei)
|
|
{
|
|
if (ei->state == EI_STATE_DISCONNECTED ||
|
|
ei->state == EI_STATE_DISCONNECTING)
|
|
return;
|
|
|
|
enum ei_state state = ei->state;
|
|
|
|
/* We need the disconnecting state to be re-entrant
|
|
ei_device_remove() may call ei_disconnect() on a socket error */
|
|
ei->state = EI_STATE_DISCONNECTING;
|
|
|
|
struct ei_seat *seat;
|
|
list_for_each_safe(seat, &ei->seats, link) {
|
|
ei_seat_remove(seat);
|
|
}
|
|
|
|
if (state != EI_STATE_NEW && state != EI_STATE_BACKEND) {
|
|
ei_connection_request_disconnect(ei->connection);
|
|
ei_connection_remove_pending_callbacks(ei->connection);
|
|
}
|
|
ei_queue_disconnect_event(ei);
|
|
ei->state = EI_STATE_DISCONNECTED;
|
|
if (ei->source)
|
|
source_remove(ei->source);
|
|
ei->source = source_unref(ei->source);
|
|
}
|
|
|
|
#define DISCONNECT_IF_INVALID_ID(connection_, id_) do { \
|
|
if (!brei_is_server_id(id_)) { \
|
|
struct ei *ei_ = ei_connection_get_context(connection_); \
|
|
log_bug(ei_, "Received invalid object id %#" PRIx64 ". Disconnecting", id_); \
|
|
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Received invalid object id %#" PRIx64 ".", id_); \
|
|
} \
|
|
} while(0)
|
|
|
|
static struct brei_result *
|
|
handle_msg_seat(struct ei_connection *connection, object_id_t seat_id, uint32_t version)
|
|
{
|
|
DISCONNECT_IF_INVALID_ID(connection, seat_id);
|
|
|
|
struct ei *ei = ei_connection_get_context(connection);
|
|
|
|
DISCONNECT_IF_INVALID_VERSION(ei, ei_seat, seat_id, version);
|
|
|
|
struct ei_seat *seat = ei_seat_new(ei, seat_id, version);
|
|
|
|
/* We might get the seat event before our callback finished, so make sure
|
|
* we know we're connected
|
|
*/
|
|
ei_connected(ei);
|
|
|
|
/* seats list owns the ref */
|
|
list_append(&ei->seats, &seat->link);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
ei_queue_device_removed_event(struct ei_device *device)
|
|
{
|
|
queue_device_removed_event(device);
|
|
}
|
|
|
|
void
|
|
ei_queue_device_added_event(struct ei_device *device)
|
|
{
|
|
queue_device_added_event(device);
|
|
}
|
|
|
|
_public_ struct ei_event*
|
|
ei_get_event(struct ei *ei)
|
|
{
|
|
if (list_empty(&ei->event_queue))
|
|
return NULL;
|
|
|
|
struct ei_event *e = list_first_entry(&ei->event_queue, e, link);
|
|
list_remove(&e->link);
|
|
|
|
return e;
|
|
}
|
|
|
|
_public_ struct ei_event*
|
|
ei_peek_event(struct ei *ei)
|
|
{
|
|
if (list_empty(&ei->event_queue))
|
|
return NULL;
|
|
|
|
struct ei_event *e = list_first_entry(&ei->event_queue, e, link);
|
|
return ei_event_ref(e);
|
|
}
|
|
|
|
void
|
|
ei_connected(struct ei *ei)
|
|
{
|
|
if (ei->state == EI_STATE_CONNECTING) {
|
|
ei->state = EI_STATE_CONNECTED;
|
|
ei_queue_connect_event(ei);
|
|
}
|
|
}
|
|
|
|
static struct brei_result *
|
|
handle_msg_disconnected(struct ei_connection *connection, uint32_t last_serial,
|
|
uint32_t reason, const char *explanation)
|
|
{
|
|
struct ei *ei = ei_connection_get_context(connection);
|
|
|
|
if (reason == EI_CONNECTION_DISCONNECT_REASON_DISCONNECTED) {
|
|
log_info(ei, "Disconnected by EIS");
|
|
/* We got disconnected, disconnect our source because whatever
|
|
* we'd receive after this is garbage and the server won't
|
|
* want to hear anything from us anyway. */
|
|
source_remove(ei->source);
|
|
ei_disconnect(ei);
|
|
return NULL;
|
|
} else {
|
|
log_info(ei, "Disconnected after error: %s", explanation);
|
|
return brei_result_new(reason, "%s", explanation);
|
|
}
|
|
|
|
}
|
|
|
|
static struct brei_result *
|
|
handle_msg_invalid_object(struct ei_connection *connection, uint32_t last_serial, object_id_t object_id)
|
|
{
|
|
struct ei *ei = ei_connection_get_context(connection);
|
|
|
|
/* The protocol is async, so what can happen is:
|
|
*
|
|
* server sends A->destroyed()
|
|
* client sends A->foo()
|
|
* client receives A->destroyed()
|
|
* server receives A->foo()
|
|
* server sends invalid_object_id(A)
|
|
* client receives invalid_object_id(A)
|
|
*
|
|
* This is expected and we shouldn't have a problem with that.
|
|
*/
|
|
|
|
struct ei_defunct_object *defunct;
|
|
list_for_each_safe(defunct, &ei->defunct_objects, node) {
|
|
if (defunct->object_id == object_id)
|
|
return NULL;
|
|
}
|
|
|
|
log_bug(ei, "Invalid object %#" PRIx64 " after serial %u, I don't yet know how to handle that", object_id, last_serial);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct brei_result *
|
|
handle_msg_ping(struct ei_connection *connection, object_id_t id, uint32_t version)
|
|
{
|
|
DISCONNECT_IF_INVALID_ID(connection, id);
|
|
|
|
struct ei *ei = ei_connection_get_context(connection);
|
|
|
|
DISCONNECT_IF_INVALID_VERSION(ei, ei_pingpong, id, version);
|
|
|
|
_unref_(ei_pingpong) *pingpong = ei_pingpong_new_for_id(ei, id, version);
|
|
ei_queue_sync_event(ei_connection_get_context(connection), pingpong);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static const struct ei_connection_interface interface = {
|
|
.disconnected = handle_msg_disconnected,
|
|
.seat = handle_msg_seat,
|
|
.invalid_object = handle_msg_invalid_object,
|
|
.ping = handle_msg_ping,
|
|
};
|
|
|
|
const struct ei_connection_interface *
|
|
ei_get_interface(struct ei *ei)
|
|
{
|
|
return &interface;
|
|
}
|
|
|
|
static int
|
|
lookup_object(object_id_t object_id, struct brei_object **object, void *userdata)
|
|
{
|
|
struct ei *ei = userdata;
|
|
|
|
struct brei_object *obj;
|
|
list_for_each(obj, &ei->proto_objects, link) {
|
|
if (obj->id == object_id) {
|
|
*object = obj;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
log_debug(ei, "Failed to find object %#" PRIx64 "", object_id);
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
static void
|
|
connection_dispatch(struct source *source, void *userdata)
|
|
{
|
|
static uint8_t cleanup;
|
|
struct ei *ei = userdata;
|
|
enum ei_state old_state = ei->state;
|
|
|
|
/* Flush any pending writes, if we have them */
|
|
int rc = ei_unsent_flush(ei);
|
|
if (rc < 0 && rc != -EAGAIN) {
|
|
log_warn(ei, "Error flushing unsent queue: %s", strerror(-rc));
|
|
ei_disconnect(ei);
|
|
} else {
|
|
_unref_(brei_result) *result = brei_dispatch(ei->brei, source_get_fd(source),
|
|
lookup_object, ei);
|
|
if (result) {
|
|
log_warn(ei, "Connection error: %s", brei_result_get_explanation(result));
|
|
brei_drain_fd(source_get_fd(source));
|
|
ei_disconnect(ei);
|
|
} else if (++cleanup % 20 == 0) {
|
|
uint64_t now = ei_now(ei);
|
|
struct ei_defunct_object *defunct;
|
|
|
|
list_for_each_safe(defunct, &ei->defunct_objects, node) {
|
|
/* Drop defunct objects after 5s */
|
|
if (now - defunct->time < s2us(5))
|
|
break;
|
|
ei_defunct_object_free(defunct);
|
|
}
|
|
}
|
|
}
|
|
|
|
static const char *states[] = {
|
|
"NEW",
|
|
"BACKEND",
|
|
"CONNECTING",
|
|
"CONNECTED",
|
|
"DISCONNECTED",
|
|
"DISCONNECTING",
|
|
};
|
|
|
|
if (old_state != ei->state)
|
|
log_debug(ei, "Connection dispatch: %s -> %s",
|
|
states[old_state],
|
|
states[ei->state]);
|
|
}
|
|
|
|
static void
|
|
ei_queue_unsent(struct ei *ei, struct source *source, struct iobuf *buf)
|
|
{
|
|
if (list_empty(&ei->unsent_queue)) {
|
|
source_enable_write(source, true);
|
|
}
|
|
|
|
struct ei_unsent *unsent = xalloc(sizeof *unsent);
|
|
unsent->buf = buf;
|
|
list_append(&ei->unsent_queue, &unsent->node);
|
|
}
|
|
|
|
static void
|
|
ei_unsent_free(struct ei_unsent *unsent)
|
|
{
|
|
list_remove(&unsent->node);
|
|
iobuf_free(unsent->buf);
|
|
free(unsent);
|
|
}
|
|
|
|
int
|
|
ei_unsent_flush(struct ei* ei)
|
|
{
|
|
if (list_empty(&ei->unsent_queue))
|
|
return 0;
|
|
|
|
struct source *source = ei->source;
|
|
int fd = source_get_fd(source);
|
|
struct ei_unsent *unsent;
|
|
list_for_each_safe(unsent, &ei->unsent_queue, node) {
|
|
int rc = iobuf_send(unsent->buf, fd);
|
|
if (rc < 0)
|
|
return rc;
|
|
ei_unsent_free(unsent);
|
|
}
|
|
source_enable_write(source, false);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
ei_send_message(struct ei *ei, const struct brei_object *object,
|
|
uint32_t opcode, const char *signature, size_t nargs, ...)
|
|
{
|
|
log_debug(ei, "sending: object %#" PRIx64 " (%s@v%u:%s(%u)) signature '%s'",
|
|
object->id,
|
|
object->interface->name,
|
|
object->interface->version,
|
|
object->interface->requests[opcode].name,
|
|
opcode,
|
|
signature);
|
|
|
|
va_list args;
|
|
va_start(args, nargs);
|
|
_unref_(brei_result) *result = brei_marshal_message(ei->brei,
|
|
object->id,
|
|
opcode, signature,
|
|
nargs, args);
|
|
va_end(args);
|
|
|
|
if (brei_result_get_reason(result) != 0) {
|
|
log_warn(ei, "failed to marshal message: %s",
|
|
brei_result_get_explanation(result));
|
|
return -EBADMSG;
|
|
}
|
|
|
|
_cleanup_iobuf_ struct iobuf *buf = brei_result_get_data(result);
|
|
assert(buf);
|
|
int fd = source_get_fd(ei->source);
|
|
int rc = -EPIPE;
|
|
if (fd != -1) {
|
|
rc = ei_unsent_flush(ei);
|
|
if (rc >= 0)
|
|
rc = iobuf_send(buf, fd);
|
|
if (rc == -EAGAIN) {
|
|
ei_queue_unsent(ei, ei->source, steal(&buf));
|
|
rc = 0;
|
|
} else if (rc < 0){
|
|
log_warn(ei, "failed to send message: %s", strerror(-rc));
|
|
source_remove(ei->source);
|
|
}
|
|
}
|
|
return rc < 0 ? rc : 0;
|
|
}
|
|
|
|
int
|
|
ei_set_socket(struct ei *ei, int fd)
|
|
{
|
|
struct source *source = source_new(fd, connection_dispatch, ei);
|
|
int rc = sink_add_source(ei->sink, source);
|
|
if (rc == 0) {
|
|
ei->source = source_ref(source);
|
|
ei->state = EI_STATE_BACKEND;
|
|
|
|
/* The server SHOULD have already sent the handshake
|
|
* version, let's process that. If not ready, it'll happen
|
|
* in the next dispatch.
|
|
*
|
|
* FIXME: this will block if O_NONBLOCK is missing
|
|
*/
|
|
ei_dispatch(ei);
|
|
}
|
|
|
|
source_unref(source);
|
|
|
|
return rc < 0 ? rc : 0;
|
|
}
|
|
|
|
_public_ void
|
|
ei_configure_name(struct ei *ei, const char *name)
|
|
{
|
|
if (ei->state != EI_STATE_NEW) {
|
|
log_bug_client(ei,"Client is already connected");
|
|
return;
|
|
}
|
|
|
|
if (strlen(name) > 1024) {
|
|
log_bug_client(ei, "Client name too long");
|
|
return;
|
|
}
|
|
|
|
free(ei->name);
|
|
ei->name = xstrdup(name);
|
|
}
|
|
|
|
_public_ void
|
|
ei_clock_set_now_func(struct ei *ei, ei_clock_now_func func)
|
|
{
|
|
ei->clock_now = func;
|
|
}
|
|
|
|
_public_ uint64_t
|
|
ei_now(struct ei *ei)
|
|
{
|
|
uint64_t ts = 0;
|
|
|
|
if (ei->clock_now)
|
|
ts = ei->clock_now(ei);
|
|
else {
|
|
int rc = now(&ts);
|
|
if (rc < 0) {
|
|
/* We should probably disconnect here but the chances of this
|
|
* happening are so slim it's not worth worrying about. Plus,
|
|
* if this fails we're likely to be inside eis_device_frame()
|
|
* so we should flush a frame event before disconnecting and... */
|
|
log_error(ei, "clock_gettime failed: %s", strerror(-rc));
|
|
}
|
|
}
|
|
|
|
return ts;
|
|
}
|
|
|
|
#ifdef _enable_tests_
|
|
#include "util-munit.h"
|
|
|
|
MUNIT_TEST(test_init_unref)
|
|
{
|
|
struct ei *ei = ei_new(NULL);
|
|
|
|
munit_assert_int(ei->state, ==, EI_STATE_NEW);
|
|
munit_assert(list_empty(&ei->event_queue));
|
|
munit_assert(list_empty(&ei->seats));
|
|
|
|
munit_assert_not_null(ei->sink);
|
|
|
|
struct ei *refd = ei_ref(ei);
|
|
munit_assert_ptr_equal(ei, refd);
|
|
munit_assert_int(ei->object.refcount, ==, 2);
|
|
|
|
struct ei *unrefd = ei_unref(ei);
|
|
munit_assert_null(unrefd);
|
|
|
|
unrefd = ei_unref(ei);
|
|
munit_assert_null(unrefd);
|
|
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
MUNIT_TEST(test_configure_name)
|
|
{
|
|
struct ei *ei = ei_new(NULL);
|
|
|
|
ei_configure_name(ei, "foo");
|
|
munit_assert_string_equal(ei->name, "foo");
|
|
ei_configure_name(ei, "bar");
|
|
munit_assert_string_equal(ei->name, "bar");
|
|
|
|
/* ignore names that are too long */
|
|
char buf[1200] = {0};
|
|
memset(buf, 'a', sizeof(buf) - 1);
|
|
ei_configure_name(ei, buf);
|
|
munit_assert_string_equal(ei->name, "bar");
|
|
|
|
/* ignore names in all other states */
|
|
for (enum ei_state state = EI_STATE_NEW + 1;
|
|
state <= EI_STATE_DISCONNECTED;
|
|
state++) {
|
|
ei->state = state;
|
|
ei_configure_name(ei, "expect ignored");
|
|
munit_assert_string_equal(ei->name, "bar");
|
|
}
|
|
|
|
ei_unref(ei);
|
|
|
|
return MUNIT_OK;
|
|
}
|
|
#endif
|