libei/src/libeis-client.c
Peter Hutterer 0a347f433f protocol: make the connection setup the zero object with a proper handover
This changes the initial connection negotiation to have the
ei_connection_setup as the pre-existing object id 0. Once the client has
sent all the data to set up the connection, the EIS implementation
replies with a new object ID that is the ei_connection protocol object,
i.e. the main object.

This allows for version negotiation of our main protocol object.
2023-03-03 11:21:26 +10:00

411 lines
11 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 "util-time.h"
#include "util-version.h"
#include "libeis-private.h"
#include "libeis-proto.h"
#include "brei-shared.h"
#include "eis-proto.h"
DEFINE_TRISTATE(started, finished, connected);
static void
eis_client_destroy(struct eis_client *client)
{
eis_connection_setup_unref(client->setup);
eis_connection_unref(client->connection);
free(client->name);
source_remove(client->source);
source_unref(client->source);
list_remove(&client->link);
}
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_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);
}
const struct brei_object *
eis_client_get_proto_object(struct eis_client *client)
{
return &client->connection->proto_object;
}
uint32_t
eis_client_get_new_id(struct eis_client *client)
{
static const int offset = 0xff000000;
return offset | (client->next_object_id++ & ~offset);
}
void
eis_client_register_object(struct eis_client *client, struct brei_object *object)
{
struct eis *eis = eis_client_get_context(client);
log_debug(eis, "registering %s v%u object %#x", object->interface->name, object->version, object->id);
list_append(&client->proto_objects, &object->link);
}
void
eis_client_unregister_object(struct eis_client *client, struct brei_object *object)
{
struct eis *eis = eis_client_get_context(client);
log_debug(eis, "deregistering %s v%u object %#x", object->interface->name, object->version, object->id);
list_remove(&object->link);
}
struct eis_client *
eis_client_get_client(struct eis_client *client)
{
return 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;
}
int
eis_client_send_message(struct eis_client *client, const struct brei_object *object,
uint32_t opcode, const char *signature, size_t nargs, ...)
{
struct eis *eis = eis_client_get_context(client);
int fd = source_get_fd(client->source);
log_debug(eis, "sending: object %#x (%s@v%u:%s(%u)) signature '%s'",
object->id,
object->interface->name,
object->interface->version,
object->interface->events[opcode].name,
opcode,
signature);
va_list args;
va_start(args, nargs);
int rc = brei_send_message(fd, object->id, opcode, signature, nargs, args);
va_end(args);
return rc < 0 ? rc : 0;
}
static int
client_send_disconnect(struct eis_client *client, const char *reason)
{
return eis_connection_event_disconnected(client->connection,
reason ? EIS_CONNECTION_DISCONNECT_REASON_ERROR : EIS_CONNECTION_DISCONNECT_REASON_DISCONNECTED,
reason);
}
static int
client_send_seat_added(struct eis_client *client, struct eis_seat *seat)
{
return eis_connection_event_seat(client->connection, eis_seat_get_id(seat),
eis_seat_get_version(seat));
}
_public_ void
eis_client_connect(struct eis_client *client)
{
switch(client->state) {
case EIS_CLIENT_STATE_DISCONNECTED:
return;
case EIS_CLIENT_STATE_CONNECTING:
break;
default:
log_bug_client(eis_client_get_context(client),
"%s: client already connected", __func__);
return;
}
client->state = EIS_CLIENT_STATE_CONNECTED;
}
static void
client_disconnect(struct eis_client *client, const char *reason)
{
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:
client_send_disconnect(client, reason);
client->state = EIS_CLIENT_STATE_DISCONNECTED;
source_remove(client->source);
break;
}
eis_client_unref(client);
}
_public_ void
eis_client_disconnect(struct eis_client *client)
{
client_disconnect(client, NULL);
}
void
eis_client_setup_done(struct eis_client *client, const char *name, bool is_sender,
const struct eis_client_interface_versions *versions)
{
client->setup = NULL; /* connection object cleans itself up */
client->name = xstrdup(name);
client->is_sender = is_sender;
client->interface_versions = *versions;
eis_queue_connect_event(client);
client->state = EIS_CLIENT_STATE_CONNECTING;
}
#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_disconnect(struct eis_connection *connection)
{
struct eis_client * client = eis_connection_get_client(connection);
client_disconnect(client, NULL);
return 0;
}
static int
client_msg_sync(struct eis_connection *connection, uint32_t new_id)
{
if (new_id == 0)
return -EINVAL;
struct eis_client *client = eis_connection_get_client(connection);
struct eis_callback *callback = eis_callback_new(client, new_id, client->interface_versions.ei_callback);
log_debug(eis_client_get_context(client) , "object %u: connection sync done", new_id);
int rc = eis_callback_event_done(callback, 0);
eis_callback_unref(callback);
return rc;
}
static const struct eis_connection_interface intf_state_new = {
.sync = client_msg_sync,
.disconnect = client_msg_disconnect,
};
/* Client is waiting for us, shouldn't send anything except disconnect */
static const struct eis_connection_interface intf_state_connecting = {
.sync = client_msg_sync,
.disconnect = client_msg_disconnect,
};
static const struct eis_connection_interface intf_state_connected = {
.sync = client_msg_sync,
.disconnect = client_msg_disconnect,
};
static const struct eis_connection_interface *interfaces[] = {
[EIS_CLIENT_STATE_NEW] = &intf_state_new,
[EIS_CLIENT_STATE_CONNECTING] = &intf_state_connecting,
[EIS_CLIENT_STATE_CONNECTED] = &intf_state_connected,
[EIS_CLIENT_STATE_DISCONNECTED] = NULL,
};
const struct eis_connection_interface *
eis_client_get_interface(struct eis_client *client)
{
assert(client->state < ARRAY_LENGTH(interfaces));
return interfaces[client->state];
}
static int
lookup_object(uint32_t object_id, struct brei_object **object, void *userdata)
{
struct eis_client *client = userdata;
struct brei_object *obj;
list_for_each(obj, &client->proto_objects, link) {
if (obj->id == object_id) {
*object = obj;
return 0;
}
}
log_debug(eis_client_get_context(client), "Failed to find object %#x", object_id);
return -ENOENT;
}
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), lookup_object, client);
if (rc < 0) {
brei_drain_fd(source_get_fd(source));
client_disconnect(client, strerror(-rc));
}
static const char *client_states[] = {
"NEW",
"CONNECTING",
"CONNECTED",
"DISCONNECTED",
};
if (rc == -ECANCELED)
log_info(eis_client_get_context(client), "Disconnected");
else if (rc)
log_warn(eis_client_get_context(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_get_context(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->proto_objects);
client->interface_versions = (struct eis_client_interface_versions){
.ei_connection = VERSION_V(1),
.ei_connection_setup = VERSION_V(1),
.ei_callback = VERSION_V(1),
.ei_seat = VERSION_V(1),
.ei_device = VERSION_V(1),
.ei_pointer = VERSION_V(1),
.ei_keyboard = VERSION_V(1),
.ei_touchscreen = VERSION_V(1),
};
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);
client->setup = eis_connection_setup_new(client, &client->interface_versions);
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);
}