libei/src/libeis-client.c
Peter Hutterer 29da572dca proto: separate pre-connection properties into ConfigureProperty
This is primarily for namespacing: where a portal sets some properties
it needs to do so *before* the Connect event. By moving this out to a
different namespace we can separate this easier, avoiding a portal
accidentally sending a property event after it has already passed the fd
to the client.

Fixes #23
2022-09-07 10:07:56 +10:00

890 lines
22 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 <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include "util-bits.h"
#include "util-io.h"
#include "util-macros.h"
#include "util-mem.h"
#include "util-sources.h"
#include "util-strings.h"
#include "util-structs.h"
#include "util-tristate.h"
#include "libeis-private.h"
#include "libeis-proto.h"
#include "brei-shared.h"
DEFINE_TRISTATE(started, finished, connected);
static void
eis_client_destroy(struct eis_client *client)
{
free(client->name);
source_remove(client->source);
source_unref(client->source);
list_remove(&client->link);
struct eis_property *prop;
list_for_each_safe(prop, &client->properties, link) {
eis_property_unref(prop);
}
}
static
OBJECT_IMPLEMENT_CREATE(eis_client);
static
OBJECT_IMPLEMENT_PARENT(eis_client, eis);
_public_
OBJECT_IMPLEMENT_REF(eis_client);
_public_
OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_client);
_public_
OBJECT_IMPLEMENT_GETTER(eis_client, name, const char*);
_public_
OBJECT_IMPLEMENT_GETTER(eis_client, version, uint32_t);
_public_
OBJECT_IMPLEMENT_SETTER(eis_client, user_data, void*);
_public_
OBJECT_IMPLEMENT_GETTER(eis_client, user_data, void*);
_public_ struct eis*
eis_client_get_context(struct eis_client *client)
{
return eis_client_parent(client);
}
_public_ bool
eis_client_is_sender(struct eis_client *client)
{
return client->is_sender;
}
_public_ bool
eis_client_has_capability(struct eis_client *client,
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:
return flag_is_set(client->restrictions.cap_allow_mask, cap);
}
/* Quietly ignore other capabilities, we don't want to future-proof this */
if (cap == 0)
log_bug_client(eis_client_get_context(client), "Check for invalid capability 0");
return false;
}
static struct eis_device *
eis_client_find_device(struct eis_client *client, uint32_t deviceid)
{
struct eis_seat *seat;
list_for_each(seat, &client->seats, link) {
struct eis_device *device;
list_for_each(device, &seat->devices, link) {
if (device->id == deviceid)
return device;
}
}
return NULL;
}
static struct eis_seat *
eis_client_find_seat(struct eis_client *client, uint32_t seatid)
{
struct eis_seat *seat;
list_for_each(seat, &client->seats, link) {
if (seat->id == seatid)
return seat;
}
return NULL;
}
static int
client_send_version(struct eis_client *client, uint32_t version)
{
struct eis *eis = eis_client_get_context(client);
return eis->requests->version(client, version);
}
static int
client_send_disconnect(struct eis_client *client)
{
struct eis *eis = eis_client_get_context(client);
return eis->requests->disconnected(client);
}
static int
client_send_connect(struct eis_client *client)
{
struct eis *eis = eis_client_get_context(client);
return eis->requests->connected(client);
}
static int
client_send_seat_added(struct eis_client *client, struct eis_seat *seat)
{
struct eis *eis = eis_client_get_context(client);
return eis->requests->seat_added(seat);
}
static int
client_send_seat_removed(struct eis_client *client, struct eis_seat *seat)
{
struct eis *eis = eis_client_get_context(client);
return eis->requests->seat_removed(seat);
}
static int
client_send_device_added(struct eis_client *client, struct eis_device *device)
{
struct eis *eis = eis_client_get_context(client);
int rc = eis->requests->device_added(device);
if (rc >= 0 && device->keymap)
rc = eis->requests->device_keymap(device);
if (rc >= 0 && device->type == EIS_DEVICE_TYPE_VIRTUAL) {
struct eis_region *r;
list_for_each(r, &device->regions, link) {
rc = eis->requests->device_region(device, r);
}
}
if (rc >= 0)
rc = eis->requests->device_done(device);
return rc;
}
static int
client_send_device_removed(struct eis_client *client, struct eis_device *device)
{
struct eis *eis = eis_client_get_context(client);
return eis->requests->device_removed(device);
}
static int
client_send_device_paused(struct eis_client *client, struct eis_device *device)
{
struct eis *eis = eis_client_get_context(client);
return eis->requests->device_paused(device);
}
static int
client_send_device_resumed(struct eis_client *client, struct eis_device *device)
{
struct eis *eis = eis_client_get_context(client);
return eis->requests->device_resumed(device);
}
static int
client_send_keyboard_modifiers(struct eis_client *client, struct eis_device *device,
const struct eis_xkb_modifiers *mods)
{
struct eis *eis = eis_client_get_context(client);
return eis->requests->keyboard_modifiers(device, mods);
}
static int
client_send_property(struct eis_client *client, struct eis_property *property)
{
struct eis *eis = eis_client_get_context(client);
if ((property->permissions & EIS_PROPERTY_PERM_READ) == 0)
return 0;
return eis->requests->property(client, property->name, property->value, property->permissions);
}
_public_ void
eis_client_connect(struct eis_client *client)
{
switch(client->state) {
case EIS_CLIENT_STATE_CONNECTING:
break;
default:
log_bug_client(eis_client_get_context(client),
"%s: client already connected", __func__);
return;
}
int rc = client_send_connect(client);
if (rc) {
log_debug(eis_client_get_context(client), "Message failed to send: %s", strerror(-rc));
eis_client_disconnect(client);
} else {
client->state = EIS_CLIENT_STATE_CONNECTED;
struct eis_property *prop;
list_for_each(prop, &client->properties, link)
client_send_property(client, prop);
}
}
_public_ void
eis_client_disconnect(struct eis_client *client)
{
switch(client->state) {
case EIS_CLIENT_STATE_DISCONNECTED:
/* Client already disconnected? don't bother sending an
* event */
return;
case EIS_CLIENT_STATE_CONNECTING:
case EIS_CLIENT_STATE_CONNECTED:
{
struct eis_seat *s;
list_for_each_safe(s, &client->seats, link) {
eis_seat_drop(s);
}
}
eis_queue_disconnect_event(client);
_fallthrough_;
case EIS_CLIENT_STATE_NEW:
case EIS_CLIENT_STATE_CONFIGURING:
case EIS_CLIENT_STATE_CONFIGURED:
client_send_disconnect(client);
client->state = EIS_CLIENT_STATE_DISCONNECTED;
source_remove(client->source);
break;
}
eis_client_unref(client);
}
static int
client_msg_close_device(struct eis_client *client, uint32_t deviceid)
{
struct eis_device *device = eis_client_find_device(client, deviceid);
if (device)
eis_device_closed_by_client(device);
return 0;
}
static int
client_msg_bind_seat(struct eis_client *client, uint32_t seatid, uint32_t caps)
{
struct eis_seat *seat = eis_client_find_seat(client, seatid);
if (seat)
eis_seat_bind(seat, caps);
return seat ? 0 : -EINVAL;
}
#define DISCONNECT_IF_RECEIVER_CONTEXT(client_) do { \
if (!(client_)->is_sender) { \
struct eis *_ctx = eis_client_get_context(client_); \
log_bug_client(_ctx, "Invalid event from receiver ei context. Disconnecting client"); \
return -EINVAL; \
} \
} while(0)
static int
client_msg_start_emulating(struct eis_client *client, uint32_t deviceid)
{
DISCONNECT_IF_RECEIVER_CONTEXT(client);
struct eis_device *device = eis_client_find_device(client, deviceid);
if (device)
eis_device_event_start_emulating(device);
return 0;
}
static int
client_msg_stop_emulating(struct eis_client *client, uint32_t deviceid)
{
DISCONNECT_IF_RECEIVER_CONTEXT(client);
struct eis_device *device = eis_client_find_device(client, deviceid);
if (device)
eis_device_event_stop_emulating(device);
return 0;
}
static int
client_msg_frame(struct eis_client *client, uint32_t deviceid, uint64_t time)
{
DISCONNECT_IF_RECEIVER_CONTEXT(client);
struct eis_device *device = eis_client_find_device(client, deviceid);
if (device)
return eis_device_event_frame(device, time);
return 0;
}
static int
client_msg_pointer_rel(struct eis_client *client, uint32_t deviceid,
double x, double y)
{
DISCONNECT_IF_RECEIVER_CONTEXT(client);
struct eis_device *device = eis_client_find_device(client, deviceid);
if (device)
return eis_device_event_pointer_rel(device, x, y);
return -EINVAL;
}
static int
client_msg_pointer_abs(struct eis_client *client, uint32_t deviceid,
double x, double y)
{
DISCONNECT_IF_RECEIVER_CONTEXT(client);
struct eis_device *device = eis_client_find_device(client, deviceid);
if (device)
return eis_device_event_pointer_abs(device, x, y);
return -EINVAL;
}
static int
client_msg_pointer_button(struct eis_client *client, uint32_t deviceid,
uint32_t button, bool state)
{
DISCONNECT_IF_RECEIVER_CONTEXT(client);
struct eis_device *device = eis_client_find_device(client, deviceid);
if (device)
return eis_device_event_pointer_button(device, button, state);
return -EINVAL;
}
static int
client_msg_pointer_scroll(struct eis_client *client, uint32_t deviceid,
double x, double y)
{
DISCONNECT_IF_RECEIVER_CONTEXT(client);
struct eis_device *device = eis_client_find_device(client, deviceid);
if (device)
return eis_device_event_pointer_scroll(device, x, y);
return -EINVAL;
}
static int
client_msg_pointer_scroll_discrete(struct eis_client *client, uint32_t deviceid,
int32_t x, int32_t y)
{
DISCONNECT_IF_RECEIVER_CONTEXT(client);
struct eis_device *device = eis_client_find_device(client, deviceid);
if (device)
return eis_device_event_pointer_scroll_discrete(device, x, y);
return -EINVAL;
}
static int
client_msg_pointer_scroll_stop(struct eis_client *client, uint32_t deviceid,
bool x, bool y, bool is_cancel)
{
DISCONNECT_IF_RECEIVER_CONTEXT(client);
struct eis_device *device = eis_client_find_device(client, deviceid);
if (device) {
if (is_cancel)
return eis_device_event_pointer_scroll_cancel(device, x, y);
else
return eis_device_event_pointer_scroll_stop(device, x, y);
}
return -EINVAL;
}
static int
client_msg_keyboard_key(struct eis_client *client, uint32_t deviceid,
uint32_t key, bool state)
{
DISCONNECT_IF_RECEIVER_CONTEXT(client);
struct eis_device *device = eis_client_find_device(client, deviceid);
if (device)
return eis_device_event_keyboard_key(device, key, state);
return -EINVAL;
}
static int
client_msg_touch_down(struct eis_client *client, uint32_t deviceid,
uint32_t touchid, double x, double y)
{
DISCONNECT_IF_RECEIVER_CONTEXT(client);
struct eis_device *device = eis_client_find_device(client, deviceid);
if (device)
return eis_device_event_touch_down(device, touchid, x, y);
return -EINVAL;
}
static int
client_msg_touch_motion(struct eis_client *client, uint32_t deviceid,
uint32_t touchid, double x, double y)
{
DISCONNECT_IF_RECEIVER_CONTEXT(client);
struct eis_device *device = eis_client_find_device(client, deviceid);
if (device)
return eis_device_event_touch_motion(device, touchid, x, y);
return -EINVAL;
}
static int
client_msg_touch_up(struct eis_client *client, uint32_t deviceid, uint32_t touchid)
{
DISCONNECT_IF_RECEIVER_CONTEXT(client);
struct eis_device *device = eis_client_find_device(client, deviceid);
if (device)
return eis_device_event_touch_up(device, touchid);
return -EINVAL;
}
static tristate
client_is_in_configure_transaction(struct eis_client *client)
{
tristate t = tristate_connected;
switch (client->state) {
case EIS_CLIENT_STATE_NEW:
case EIS_CLIENT_STATE_CONFIGURED:
t = tristate_finished;
break;
case EIS_CLIENT_STATE_CONFIGURING:
t = tristate_started;
break;
case EIS_CLIENT_STATE_CONNECTING:
case EIS_CLIENT_STATE_CONNECTED:
case EIS_CLIENT_STATE_DISCONNECTED:
t = tristate_connected;
break;
}
return t;
}
static int
client_msg_configure_start(struct eis_client *client, uint32_t version)
{
tristate t = client_is_in_configure_transaction(client);
if (tristate_is_started(t))
return -EPROTO;
/* Once the client is connected, we silently ignore all Configure
requests so a broken portal can't accidentally disconnect a client */
if (tristate_is_connected(t))
return 0;
if (version == 0)
return -EINVAL;
client->configure_version = version;
client->state = EIS_CLIENT_STATE_CONFIGURING;
return 0;
}
static int
client_msg_configure_finish(struct eis_client *client)
{
tristate t = client_is_in_configure_transaction(client);
if (tristate_is_finished(t))
return -EPROTO;
/* Once the client is connected, we silently ignore all Configure
requests so a broken portal can't accidentally disconnect a client */
if (tristate_is_connected(t))
return 0;
client->configure_version = 0;
client->state = EIS_CLIENT_STATE_CONFIGURED;
return 0;
}
static int
client_msg_configure_name(struct eis_client *client, const char *name)
{
tristate t = client_is_in_configure_transaction(client);
if (tristate_is_finished(t))
return -EPROTO;
/* Once the client is connected, we silently ignore all Configure
requests so a broken portal can't accidentally disconnect a client */
if (tristate_is_connected(t))
return 0;
if (client->name)
return 0;
client->name = xstrdup(name);
return 0;
}
static int
client_msg_configure_capabilities(struct eis_client *client, uint32_t allowed_caps)
{
tristate t = client_is_in_configure_transaction(client);
if (tristate_is_finished(t))
return -EPROTO;
/* Once the client is connected, we silently ignore all Configure
requests so a broken portal can't accidentally disconnect a client */
if (tristate_is_connected(t))
return 0;
/* restrictions can only be reduced */
client->restrictions.cap_allow_mask &= allowed_caps;
return 0;
}
static int
client_msg_configure_property(struct eis_client *client,
const char *name, const char *value,
uint32_t permissions)
{
tristate t = client_is_in_configure_transaction(client);
if (tristate_is_finished(t))
return -EPROTO;
/* Once the client is connected, we silently ignore all Configure
requests so a broken portal can't accidentally disconnect a client */
if (tristate_is_connected(t))
return 0;
eis_property_update_from_client(client, name, value, permissions);
return 0;
}
static int
client_msg_connect(struct eis_client *client, uint32_t version,
const char *name, bool is_sender)
{
if (client->version > EIS_PROTOCOL_VERSION)
return -EPROTO;
client->version = version;
if (client->name == NULL)
client->name = xstrdup(name);
client->is_sender = is_sender;
return 0;
}
static int
client_msg_connect_done(struct eis_client *client)
{
eis_queue_connect_event(client);
client->state = EIS_CLIENT_STATE_CONNECTING;
return 0;
}
static int
client_msg_disconnect(struct eis_client *client)
{
return -ECANCELED;
}
static int
client_msg_set_property(struct eis_client *client,
const char *name, const char *value,
uint32_t permissions)
{
eis_property_update_from_client(client, name, value, permissions);
return 0;
}
static int
client_msg_set_property_with_event(struct eis_client *client,
const char *name, const char *value,
uint32_t permissions)
{
int rc = client_msg_set_property(client, name, value, permissions);
if (rc == 0)
eis_queue_property_event(client, name, value, permissions);
return rc;
}
static int
client_msg_get_version(struct eis_client *client)
{
return client_send_version(client, EI_PROTOCOL_VERSION);
}
static const struct eis_proto_interface intf_state_new = {
.connect = client_msg_connect,
.set_property = client_msg_set_property,
.connect_done = client_msg_connect_done,
.disconnect = client_msg_disconnect,
.get_version = client_msg_get_version,
.configure_start = client_msg_configure_start,
.configure_finish = client_msg_configure_finish,
.configure_name = client_msg_configure_name,
.configure_capabilities = client_msg_configure_capabilities,
.configure_property = client_msg_configure_property,
};
/* Client is waiting for us, shouldn't send anything except disconnect */
static const struct eis_proto_interface intf_state_connecting = {
.disconnect = client_msg_disconnect,
.get_version = client_msg_get_version,
.configure_start = client_msg_configure_start,
.configure_finish = client_msg_configure_finish,
.configure_name = client_msg_configure_name,
.configure_capabilities = client_msg_configure_capabilities,
.configure_property = client_msg_configure_property,
};
static const struct eis_proto_interface intf_state_connected = {
.disconnect = client_msg_disconnect,
.set_property = client_msg_set_property_with_event,
.bind_seat = client_msg_bind_seat,
.close_device = client_msg_close_device,
.get_version = client_msg_get_version,
/* events */
.start_emulating = client_msg_start_emulating,
.stop_emulating = client_msg_stop_emulating,
.rel = client_msg_pointer_rel,
.abs = client_msg_pointer_abs,
.button = client_msg_pointer_button,
.scroll = client_msg_pointer_scroll,
.scroll_stop = client_msg_pointer_scroll_stop,
.scroll_discrete = client_msg_pointer_scroll_discrete,
.key = client_msg_keyboard_key,
.touch_down = client_msg_touch_down,
.touch_motion = client_msg_touch_motion,
.touch_up = client_msg_touch_up,
.frame = client_msg_frame,
/* configuration */
.configure_start = client_msg_configure_start,
.configure_finish = client_msg_configure_finish,
.configure_name = client_msg_configure_name,
.configure_capabilities = client_msg_configure_capabilities,
.configure_property = client_msg_configure_property,
};
static const struct eis_proto_interface *interfaces[] = {
[EIS_CLIENT_STATE_NEW] = &intf_state_new,
[EIS_CLIENT_STATE_CONFIGURING] = &intf_state_new,
[EIS_CLIENT_STATE_CONFIGURED] = &intf_state_new,
[EIS_CLIENT_STATE_CONNECTING] = &intf_state_connecting,
[EIS_CLIENT_STATE_CONNECTED] = &intf_state_connected,
[EIS_CLIENT_STATE_DISCONNECTED] = NULL,
};
static int
client_message_callback(struct brei_message *bmsg, void *userdata)
{
struct eis_client *client = userdata;
assert(client->state < ARRAY_LENGTH(interfaces));
const struct eis_proto_interface *intf = interfaces[client->state];
return eis_proto_handle_message(client, intf, bmsg);
}
static void
client_dispatch(struct source *source, void *userdata)
{
_unref_(eis_client) *client = eis_client_ref(userdata);
enum eis_client_state old_state = client->state;
int rc = brei_dispatch(source_get_fd(source), client_message_callback, client);
if (rc < 0) {
brei_drain_fd(source_get_fd(source));
eis_client_disconnect(client);
}
static const char *client_states[] = {
"NEW",
"CONFIGURING",
"CONFIGURED",
"CONNECTING",
"CONNECTED",
"DISCONNECTED",
};
if (rc == -ECANCELED)
log_info(eis_client_parent(client), "Disconnected");
else if (rc)
log_warn(eis_client_parent(client), "Client error: %s",
strerror(-rc));
if (old_state != client->state) {
assert(old_state < ARRAY_LENGTH(client_states));
assert(client->state < ARRAY_LENGTH(client_states));
log_debug(eis_client_parent(client), "Client dispatch: %s -> %s",
client_states[old_state],
client_states[client->state]);
}
}
struct eis_client *
eis_client_new(struct eis *eis, int fd)
{
static uint32_t client_id;
struct eis_client *client = eis_client_create(&eis->object);
client->is_sender = true;
client->id = ++client_id;
list_init(&client->seats);
list_init(&client->seats_pending);
list_init(&client->properties);
struct source *s = source_new(fd, client_dispatch, client);
int rc = sink_add_source(eis->sink, s);
if (rc != 0) {
source_unref(s);
return NULL;
}
client->source = source_ref(s);
client->state = EIS_CLIENT_STATE_NEW;
client->restrictions.cap_allow_mask =
bit(EIS_DEVICE_CAP_KEYBOARD) |
bit(EIS_DEVICE_CAP_POINTER) |
bit(EIS_DEVICE_CAP_POINTER_ABSOLUTE) |
bit(EIS_DEVICE_CAP_TOUCH);
eis_add_client(eis, eis_client_ref(client));
source_unref(s);
/* Immediately send our version so the client doesn't need a roundtrip */
client_send_version(client, EI_PROTOCOL_VERSION);
return client;
}
void
eis_client_add_seat(struct eis_client *client, struct eis_seat *seat)
{
/* remove from the pending list first */
list_remove(&seat->link);
/* We own this seat now */
eis_seat_ref(seat);
list_append(&client->seats, &seat->link);
client_send_seat_added(client, seat);
}
void
eis_client_remove_seat(struct eis_client *client, struct eis_seat *seat)
{
client_send_seat_removed(client, seat);
seat->state = EIS_SEAT_STATE_REMOVED;
}
void
eis_client_add_device(struct eis_client *client, struct eis_device *device)
{
client_send_device_added(client, device);
}
void
eis_client_remove_device(struct eis_client *client, struct eis_device *device)
{
client_send_device_removed(client, device);
}
void
eis_client_pause_device(struct eis_client *client, struct eis_device *device)
{
client_send_device_paused(client, device);
}
void
eis_client_resume_device(struct eis_client *client, struct eis_device *device)
{
client_send_device_resumed(client, device);
}
void
eis_client_keyboard_modifiers(struct eis_client *client, struct eis_device *device,
uint32_t depressed, uint32_t latched, uint32_t locked,
uint32_t group)
{
struct eis_xkb_modifiers mods = {
.depressed = depressed,
.locked = locked,
.latched = latched,
.group = group,
};
client_send_keyboard_modifiers(client, device, &mods);
}