libei/src/libei.c
Peter Hutterer faa9500afe Add concept of active/passive libei contexts
A libei context can be initialized as active or passive context -
an "active" context sends events, a "passive" context receives events.
The EIS context supports both simultaneously, it is up to the
implementation to disconnect libei clients that it does not want to
suppport.

For example, the xdotool use-case creates an active libei context. The
EIS implementation controls and sets up the devices, but libei
sends the events.

In an input-capturing use-case, the EIS implementation controls
and sets up the devices **and** sends the events. libei is merely the
receiver for any event, it cannot send events. Thus this use-case
requires a passive libei context.

Most of this code is copy/paste with minor modifications - libei already
had the code to send events, libeis had the code to receive events, so
the vast majority of this patch is copying the code into the respective
other library, swap "ei" and "eis" and then apply the various minor
modifications needed to hook into the existing library.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
2022-03-04 14:04:33 +10:00

1424 lines
31 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 <stdlib.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 "libei.h"
#include "libei-private.h"
#include "libei-proto.h"
#include "brei-shared.h"
_Static_assert(sizeof(enum ei_device_capability) == sizeof(int), "Invalid enum size");
_Static_assert(sizeof(enum ei_property_permission) == 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 struct ei_seat *
ei_find_seat(struct ei *ei, uint32_t seatid)
{
struct ei_seat *seat;
list_for_each(seat, &ei->seats, link) {
if (seat->id == seatid)
return seat;
}
return NULL;
}
static struct ei_device *
ei_find_device(struct ei *ei, uint32_t deviceid)
{
struct ei_seat *seat;
list_for_each(seat, &ei->seats, link) {
struct ei_device *device = ei_seat_find_device(seat, deviceid);
if (device)
return device;
}
return NULL;
}
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);
if (ei->backend_interface.destroy)
ei->backend_interface.destroy(ei, ei->backend);
ei->backend = NULL;
sink_unref(ei->sink);
free(ei->name);
struct ei_property *prop;
list_for_each_safe(prop, &ei->properties, link) {
ei_property_unref(prop);
}
}
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 *);
DEFINE_UNREF_CLEANUP_FUNC(ei_device);
DEFINE_UNREF_CLEANUP_FUNC(ei_region);
static void
set_prop_cmdline(struct ei *ei)
{
_cleanup_free_ char *cmdline = cmdline_as_str();
ei_property_update(ei, "ei.application.cmdline", cmdline, EI_PROPERTY_PERM_NONE);
}
static void
set_prop_pid(struct ei *ei)
{
char pid[64];
xsnprintf(pid, sizeof(pid), "%d", getpid());
ei_property_update(ei, "ei.application.pid", pid, EI_PROPERTY_PERM_NONE);
}
static void
set_prop_type(struct ei *ei)
{
ei_property_update(ei, "ei.connection.type", "socket", EI_PROPERTY_PERM_NONE);
}
static struct ei *
ei_create_context(bool is_active, void *user_data)
{
_unref_(ei) *ei = ei_create(NULL);
ei->state = EI_STATE_NEW;
ei->requests = ei_proto_get_requests();
list_init(&ei->event_queue);
list_init(&ei->seats);
list_init(&ei->properties);
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_active = is_active;
set_prop_pid(ei);
set_prop_cmdline(ei);
set_prop_type(ei);
return steal(&ei);
}
_public_ struct ei *
ei_new(void *user_data)
{
return ei_new_active(user_data);
}
_public_ struct ei *
ei_new_active(void *user_data)
{
return ei_create_context(true, user_data);
}
_public_ struct ei *
ei_new_passive(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
queue_event(struct ei *ei, struct ei_event *event)
{
log_debug(ei, "queuing event type %s (%u)\n",
ei_event_type_to_string(event->type), event->type);
list_append(&ei->event_queue, &event->link);
}
static void
insert_event(struct ei *ei, struct ei_event *event)
{
log_debug(ei, "inserting event type %s (%u)\n",
ei_event_type_to_string(event->type), event->type);
list_insert(&ei->event_queue, &event->link);
}
static void
queue_connect_event(struct ei *ei)
{
struct ei_event *e = ei_event_new(ei);
e->type = EI_EVENT_CONNECT;
queue_event(ei, e);
}
static void
queue_disconnect_event(struct ei *ei)
{
struct ei_event *e = ei_event_new(ei);
e->type = EI_EVENT_DISCONNECT;
queue_event(ei, e);
}
static void
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);
}
static void
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);
}
void
ei_queue_seat_removed_event(struct ei_seat *seat)
{
queue_seat_removed_event(seat);
}
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);
}
static void
insert_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;
insert_event(ei, e);
}
static void
queue_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);
}
static void
queue_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);
}
static void
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);
}
static void
queue_property_event(struct ei *ei, const char *name,
const char *value, uint32_t permissions)
{
struct ei_event *e = ei_event_new(ei);
e->type = EI_EVENT_PROPERTY;
e->prop.name = xstrdup(name);
e->prop.value = xstrdup(value);
e->prop.permissions = permissions;
queue_event(ei, e);
}
void
ei_queue_frame_event(struct ei_device *device)
{
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_FRAME;
queue_event(ei_device_get_context(device), e);
}
void
ei_queue_device_start_emulating_event(struct ei_device *device)
{
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_DEVICE_START_EMULATING;
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_POINTER_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_POINTER_SCROLL;
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_POINTER_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_POINTER_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_POINTER_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,
queue_event(ei_device_get_context(device), e);
}
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) {
ei->requests->disconnect(ei);
}
queue_disconnect_event(ei);
ei->state = EI_STATE_DISCONNECTED;
if (ei->source)
source_remove(ei->source);
ei->source = source_unref(ei->source);
}
static int
handle_msg_seat_added(struct ei *ei, uint32_t seatid,
const char *name, uint32_t capabilities)
{
log_debug(ei, "Added seat %#x '%s' with caps %#x\n",
seatid, name, capabilities);
struct ei_seat *seat = ei_seat_new(ei, seatid, name, capabilities);
/* seats list owns the ref */
list_append(&ei->seats, &seat->link);
queue_seat_added_event(seat);
return 0;
}
static int
handle_msg_seat_removed(struct ei *ei, uint32_t seatid)
{
log_debug(ei, "server removed seat %#x\n", seatid);
struct ei_seat *seat = ei_find_seat(ei, seatid);
if (seat) {
ei_seat_remove(seat);
}
return 0;
}
static int
handle_msg_device_added(struct ei *ei, uint32_t deviceid, uint32_t seatid,
const char *name, uint32_t capabilities)
{
struct ei_seat *seat = ei_find_seat(ei, seatid);
if (!seat) {
log_bug(ei, "Invalid seat id %#x for device %s (%#x)\n",
seatid, name, deviceid);
return 0;
}
/* Wrong device id or a device already removed by the client but we
* won't know which unless we keep some device ID table. Not worth
* it, so just silently ignore */
if (ei_seat_find_device(seat, deviceid)) {
log_error(ei, "Server sent duplicate device id %#x\n", deviceid);
return -EINVAL;
}
_unref_(ei_device) *device = ei_device_new(seat, deviceid);
ei_device_set_name(device, name);
ei_device_set_capabilities(device, capabilities);
ei_device_added(device);
log_debug(ei,
"Added device %#x '%s' caps: %s%s%s%s seat: %s\n",
deviceid, name,
ei_device_has_capability(device, EI_DEVICE_CAP_POINTER) ? "p" : "",
ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE) ? "a" : "",
ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD) ? "k" : "",
ei_device_has_capability(device, EI_DEVICE_CAP_TOUCH) ? "t" : "",
ei_seat_get_name(seat));
return 0;
}
static int
handle_msg_device_keymap(struct ei *ei, uint32_t deviceid,
enum ei_keymap_type keymap_type,
int keymap_fd, size_t keymap_sz)
{
log_debug(ei, "Adding keymap for %#x\n", deviceid);
struct ei_device *device = ei_find_device(ei, deviceid);
if (!device)
return 0;
ei_device_set_keymap(device, keymap_type, keymap_fd, keymap_sz);
return 0;
}
void
ei_queue_device_removed_event(struct ei_device *device)
{
queue_device_removed_event(device);
}
void
ei_insert_device_removed_event(struct ei_device *device)
{
insert_device_removed_event(device);
}
static int
handle_msg_device_added_done(struct ei *ei, uint32_t deviceid)
{
log_debug(ei, "Done with device %#x\n", deviceid);
struct ei_device *device = ei_find_device(ei, deviceid);
if (!device)
return 0;
queue_device_added_event(device);
ei_device_done(device);
return 0;
}
static int
handle_msg_device_region(struct ei *ei, uint32_t deviceid,
uint32_t x, uint32_t y,
uint32_t w, uint32_t h,
double scale)
{
log_debug(ei, "Adding device region for %#x\n", deviceid);
struct ei_device *device = ei_find_device(ei, deviceid);
if (!device)
return 0;
_unref_(ei_region) *r = ei_region_new();
ei_region_set_offset(r, x, y);
ei_region_set_size(r, w, h);
ei_region_set_physical_scale(r, scale);
ei_device_add_region(device, r);
return 0;
}
static int
handle_msg_keyboard_modifiers(struct ei *ei, uint32_t deviceid,
uint32_t depressed, uint32_t latched,
uint32_t locked, uint32_t group)
{
log_debug(ei, "Setting modifiers for %#x\n", deviceid);
struct ei_device *device = ei_find_device(ei, deviceid);
if (!device)
return 0;
if (!ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD)) {
log_bug(ei,"Modifier event for non-keyboard\n");
return -EPROTO;
}
struct ei_xkb_modifiers mods = {
.depressed = depressed,
.latched = latched,
.locked = locked,
.group = group,
};
queue_keyboard_modifiers_event(device, &mods);
return 0;
}
static int
handle_msg_property(struct ei *ei, const char *name, const char *value,
uint32_t permissions)
{
int rc = ei_property_update(ei, name, value, permissions);
if (rc == 0)
queue_property_event(ei, name, value, permissions);
return 0;
}
static int
handle_msg_device_removed(struct ei *ei, uint32_t deviceid)
{
log_debug(ei, "Removed device %#x\n", deviceid);
struct ei_device *device = ei_find_device(ei, deviceid);
if (!device)
return 0;
ei_device_removed_by_server(device);
return 0;
}
static int
handle_msg_device_resumed(struct ei *ei, uint32_t deviceid)
{
log_debug(ei, "Resumed device %#x\n", deviceid);
struct ei_device *device = ei_find_device(ei, deviceid);
if (device) {
ei_device_resumed(device);
queue_resumed_event(device);
}
return 0;
}
static int
handle_msg_device_paused(struct ei *ei, uint32_t deviceid)
{
log_debug(ei, "Paused device %#x\n", deviceid);
struct ei_device *device = ei_find_device(ei, deviceid);
if (device) {
ei_device_paused(device);
queue_paused_event(device);
}
return 0;
}
int
ei_send_property(struct ei *ei, const char *name, const char *value, uint32_t permissions)
{
/* properties before CONNECTED_DONE are handled in a custom way */
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
int rc = ei->requests->property(ei, name, value, permissions);
if (rc)
ei_disconnect(ei);
return rc;
}
int
ei_send_close_device(struct ei_device *device)
{
struct ei *ei = ei_device_get_context(device);
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
int rc = ei->requests->close_device(device);
if (rc)
ei_disconnect(ei);
return rc;
}
int
ei_send_start_emulating(struct ei_device *device)
{
struct ei *ei = ei_device_get_context(device);
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
int rc = ei->requests->start_emulating(device);
if (rc)
ei_disconnect(ei);
return rc;
}
int
ei_send_stop_emulating(struct ei_device *device)
{
struct ei *ei = ei_device_get_context(device);
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
int rc = ei->requests->stop_emulating(device);
if (rc)
ei_disconnect(ei);
return rc;
}
int
ei_send_seat_bind(struct ei_seat *seat, uint32_t capabilities)
{
struct ei *ei = ei_seat_get_context(seat);
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
int rc = ei->requests->bind_seat(seat, capabilities);
if (rc)
ei_disconnect(ei);
return rc;
}
int
ei_send_seat_unbind(struct ei_seat *seat)
{
struct ei *ei = ei_seat_get_context(seat);
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
int rc = ei->requests->unbind_seat(seat);
if (rc)
ei_disconnect(ei);
return rc;
}
int
ei_send_frame(struct ei_device *device)
{
struct ei *ei = ei_device_get_context(device);
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
int rc = ei->requests->frame(device);
if (rc)
ei_disconnect(ei);
return rc;
}
int
ei_send_pointer_rel(struct ei_device *device, double x, double y)
{
struct ei *ei = ei_device_get_context(device);
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
int rc = ei->requests->rel(device, x, y);
if (rc)
ei_disconnect(ei);
return rc;
}
int
ei_send_pointer_abs(struct ei_device *device, double x, double y)
{
struct ei *ei = ei_device_get_context(device);
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
int rc = ei->requests->abs(device, x, y);
if (rc)
ei_disconnect(ei);
return rc;
}
int
ei_send_pointer_button(struct ei_device *device, uint32_t button, bool is_press)
{
struct ei *ei = ei_device_get_context(device);
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
int rc = ei->requests->button(device, button, is_press);
if (rc)
ei_disconnect(ei);
return rc;
}
int ei_send_pointer_scroll(struct ei_device *device, double x, double y)
{
struct ei *ei = ei_device_get_context(device);
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
int rc = ei->requests->scroll(device, x, y);
if (rc)
ei_disconnect(ei);
return rc;
}
int ei_send_pointer_scroll_stop(struct ei_device *device, double x, double y)
{
struct ei *ei = ei_device_get_context(device);
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
int rc = ei->requests->scroll_stop(device, x, y);
if (rc)
ei_disconnect(ei);
return rc;
}
int ei_send_pointer_scroll_cancel(struct ei_device *device, double x, double y)
{
struct ei *ei = ei_device_get_context(device);
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
int rc = ei->requests->scroll_cancel(device, x, y);
if (rc)
ei_disconnect(ei);
return rc;
}
int ei_send_pointer_scroll_discrete(struct ei_device *device, int32_t x, int32_t y)
{
struct ei *ei = ei_device_get_context(device);
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
int rc = ei->requests->scroll_discrete(device, x, y);
if (rc)
ei_disconnect(ei);
return rc;
}
int
ei_send_keyboard_key(struct ei_device *device, uint32_t key, bool is_press)
{
struct ei *ei = ei_device_get_context(device);
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
int rc = ei->requests->key(device, key, is_press);
if (rc)
ei_disconnect(ei);
return rc;
}
int
ei_send_touch_down(struct ei_device *device, uint32_t tid,
double x, double y)
{
struct ei *ei = ei_device_get_context(device);
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
int rc = ei->requests->touch_down(device, tid, x, y);
if (rc)
ei_disconnect(ei);
return rc;
}
int
ei_send_touch_motion(struct ei_device *device, uint32_t tid,
double x, double y)
{
struct ei *ei = ei_device_get_context(device);
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
int rc = ei->requests->touch_motion(device, tid, x, y);
if (rc)
ei_disconnect(ei);
return rc;
}
int
ei_send_touch_up(struct ei_device *device, uint32_t tid)
{
struct ei *ei = ei_device_get_context(device);
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
int rc = ei->requests->touch_up(device, tid);
if (rc)
ei_disconnect(ei);
return rc;
}
_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);
}
static int handle_msg_connected(struct ei *ei) {
ei->state = EI_STATE_CONNECTED;
queue_connect_event(ei);
return 0;
}
static int handle_msg_disconnected(struct ei *ei, bool is_active) {
if (ei->is_active == is_active) {
log_bug_client(ei, "Unable to connect %s ei context to %s EIS implementation\n",
is_active ? "active" : "passive",
is_active ? "active" : "passive");
}
return -ECANCELED;
}
#define DISCONNECT_IF_ACTIVE_CONTEXT(ei_) do {\
if (ei_->is_active) { \
log_bug_client(ei_, "Invalid event from passive EIS context. Disconnecting\n"); \
return -ECANCELED; \
} \
} while(0)
static int
handle_msg_start_emulating(struct ei *ei, uint32_t deviceid)
{
DISCONNECT_IF_ACTIVE_CONTEXT(ei);
struct ei_device *device = ei_find_device(ei, deviceid);
if (device)
ei_device_event_start_emulating(device);
return 0;
}
static int
handle_msg_stop_emulating(struct ei *ei, uint32_t deviceid)
{
DISCONNECT_IF_ACTIVE_CONTEXT(ei);
struct ei_device *device = ei_find_device(ei, deviceid);
if (device)
ei_device_event_stop_emulating(device);
return 0;
}
static int
handle_msg_frame(struct ei *ei, uint32_t deviceid)
{
DISCONNECT_IF_ACTIVE_CONTEXT(ei);
struct ei_device *device = ei_find_device(ei, deviceid);
if (device)
return ei_device_event_frame(device);
return 0;
}
static int
handle_msg_pointer_rel(struct ei *ei, uint32_t deviceid,
double x, double y)
{
DISCONNECT_IF_ACTIVE_CONTEXT(ei);
struct ei_device *device = ei_find_device(ei, deviceid);
if (device)
return ei_device_event_pointer_rel(device, x, y);
return -EINVAL;
}
static int
handle_msg_pointer_abs(struct ei *ei, uint32_t deviceid,
double x, double y)
{
DISCONNECT_IF_ACTIVE_CONTEXT(ei);
struct ei_device *device = ei_find_device(ei, deviceid);
if (device)
return ei_device_event_pointer_abs(device, x, y);
return -EINVAL;
}
static int
handle_msg_pointer_button(struct ei *ei, uint32_t deviceid,
uint32_t button, bool state)
{
DISCONNECT_IF_ACTIVE_CONTEXT(ei);
struct ei_device *device = ei_find_device(ei, deviceid);
if (device)
return ei_device_event_pointer_button(device, button, state);
return -EINVAL;
}
static int
handle_msg_pointer_scroll(struct ei *ei, uint32_t deviceid,
double x, double y)
{
DISCONNECT_IF_ACTIVE_CONTEXT(ei);
struct ei_device *device = ei_find_device(ei, deviceid);
if (device)
return ei_device_event_pointer_scroll(device, x, y);
return -EINVAL;
}
static int
handle_msg_pointer_scroll_discrete(struct ei *ei, uint32_t deviceid,
int32_t x, int32_t y)
{
DISCONNECT_IF_ACTIVE_CONTEXT(ei);
struct ei_device *device = ei_find_device(ei, deviceid);
if (device)
return ei_device_event_pointer_scroll_discrete(device, x, y);
return -EINVAL;
}
static int
handle_msg_pointer_scroll_stop(struct ei *ei, uint32_t deviceid,
bool x, bool y, bool is_cancel)
{
DISCONNECT_IF_ACTIVE_CONTEXT(ei);
struct ei_device *device = ei_find_device(ei, deviceid);
if (device) {
if (is_cancel)
return ei_device_event_pointer_scroll_cancel(device, x, y);
else
return ei_device_event_pointer_scroll_stop(device, x, y);
}
return -EINVAL;
}
static int
handle_msg_keyboard_key(struct ei *ei, uint32_t deviceid,
uint32_t key, bool state)
{
DISCONNECT_IF_ACTIVE_CONTEXT(ei);
struct ei_device *device = ei_find_device(ei, deviceid);
if (device)
return ei_device_event_keyboard_key(device, key, state);
return -EINVAL;
}
static int
handle_msg_touch_down(struct ei *ei, uint32_t deviceid,
uint32_t touchid, double x, double y)
{
DISCONNECT_IF_ACTIVE_CONTEXT(ei);
struct ei_device *device = ei_find_device(ei, deviceid);
if (device)
return ei_device_event_touch_down(device, touchid, x, y);
return -EINVAL;
}
static int
handle_msg_touch_motion(struct ei *ei, uint32_t deviceid,
uint32_t touchid, double x, double y)
{
DISCONNECT_IF_ACTIVE_CONTEXT(ei);
struct ei_device *device = ei_find_device(ei, deviceid);
if (device)
return ei_device_event_touch_motion(device, touchid, x, y);
return -EINVAL;
}
static int
handle_msg_touch_up(struct ei *ei, uint32_t deviceid, uint32_t touchid)
{
DISCONNECT_IF_ACTIVE_CONTEXT(ei);
struct ei_device *device = ei_find_device(ei, deviceid);
if (device)
return ei_device_event_touch_up(device, touchid);
return -EINVAL;
}
static const struct ei_proto_interface intf_state_backend = {
/* Everything triggers -EPROTO */
.connected = NULL,
};
static const struct ei_proto_interface intf_state_connecting = {
.connected = handle_msg_connected,
.disconnected = handle_msg_disconnected,
};
static const struct ei_proto_interface intf_state_connected = {
.disconnected = handle_msg_disconnected,
.seat_added = handle_msg_seat_added,
.seat_removed = handle_msg_seat_removed,
.device_added = handle_msg_device_added,
.device_removed = handle_msg_device_removed,
.device_resumed = handle_msg_device_resumed,
.device_paused = handle_msg_device_paused,
.device_region = handle_msg_device_region,
.device_keymap = handle_msg_device_keymap,
.device_done = handle_msg_device_added_done,
.keyboard_modifiers = handle_msg_keyboard_modifiers,
.property = handle_msg_property,
/* events */
.start_emulating = handle_msg_start_emulating,
.stop_emulating = handle_msg_stop_emulating,
.rel = handle_msg_pointer_rel,
.abs = handle_msg_pointer_abs,
.button = handle_msg_pointer_button,
.scroll = handle_msg_pointer_scroll,
.scroll_stop = handle_msg_pointer_scroll_stop,
.scroll_discrete = handle_msg_pointer_scroll_discrete,
.key = handle_msg_keyboard_key,
.touch_down = handle_msg_touch_down,
.touch_motion = handle_msg_touch_motion,
.touch_up = handle_msg_touch_up,
.frame = handle_msg_frame,
};
static const struct ei_proto_interface *interfaces[] = {
[EI_STATE_NEW] = NULL,
[EI_STATE_BACKEND] = &intf_state_backend,
[EI_STATE_CONNECTING] = &intf_state_connecting,
[EI_STATE_CONNECTED] = &intf_state_connected,
[EI_STATE_DISCONNECTING] = NULL,
[EI_STATE_DISCONNECTED] = NULL,
};
static int
connection_message_callback(struct brei_message *bmsg, void *userdata)
{
struct ei *ei = userdata;
assert(ei->state < ARRAY_LENGTH(interfaces));
const struct ei_proto_interface *intf = interfaces[ei->state];
return ei_proto_handle_message(ei, intf, bmsg);
}
static void
connection_dispatch(struct source *source, void *userdata)
{
struct ei *ei = userdata;
enum ei_state old_state = ei->state;
int rc = brei_dispatch(source_get_fd(source), connection_message_callback, ei);
if (rc < 0) {
brei_drain_fd(source_get_fd(source));
ei_disconnect(ei);
}
static const char *states[] = {
"NEW",
"BACKEND",
"CONNECTING",
"CONNECTED",
"DISCONNECTED",
"DISCONNECTING",
};
if (rc == -ECANCELED)
log_info(ei, "Disconnected\n");
else if (rc)
log_warn(ei, "Connnection error: %s\n", strerror(-rc));
if (old_state != ei->state)
log_debug(ei, "Connnection dispatch: %s -> %s\n",
states[old_state],
states[ei->state]);
}
int
ei_set_connection(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;
rc = ei->requests->connect(ei);
struct ei_property *prop;
list_for_each_safe(prop, &ei->properties, link) {
if (rc == 0)
rc = ei->requests->property(ei, prop->name, prop->value, prop->permissions);
}
if (rc == 0) {
rc = ei->requests->connect_done(ei);
}
if (rc == 0) {
ei->state = EI_STATE_CONNECTING;
}
if (rc != 0) {
log_error(ei, "message failed to send: %s\n", strerror(-rc));
ei_disconnect(ei);
}
}
source_unref(source);
return rc;
}
_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\n");
return;
}
if (strlen(name) > 1024) {
log_bug_client(ei, "Client name too long\n");
return;
}
free(ei->name);
ei->name = xstrdup(name);
}
#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