libei/src/libeis-client.c
Peter Hutterer d99d1e8707 libeis: better logging of client bugs
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
2020-10-01 15:28:46 +10:00

578 lines
14 KiB
C

/*
* 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 "libeis-private.h"
#include "libeis-proto.h"
#include "brei-shared.h"
static void
eis_client_destroy(struct eis_client *client)
{
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(eis_client);
#define _cleanup_eis_client_ _cleanup_(eis_client_cleanup)
_public_
OBJECT_IMPLEMENT_GETTER(eis_client, name, const char*);
_public_ struct eis*
eis_client_get_context(struct eis_client *client)
{
return eis_client_parent(client);
}
static int
client_send_disconnect(struct eis_client *client)
{
return eis_proto_send_disconnect(client);
}
static int
client_send_connect(struct eis_client *client)
{
return eis_proto_send_connect(client);
}
static int
client_send_device_added(struct eis_client *client, struct eis_device *device)
{
return eis_proto_send_device_added(client, device);
}
static int
client_send_device_removed(struct eis_client *client, struct eis_device *device)
{
return eis_proto_send_device_removed(client, device);
}
static int
client_send_device_suspended(struct eis_client *client, struct eis_device *device)
{
return eis_proto_send_device_suspended(client, device);
}
static int
client_send_device_resumed(struct eis_client *client, struct eis_device *device)
{
return eis_proto_send_device_resumed(client, device);
}
_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\n", __func__);
return;
}
int rc = client_send_connect(client);
if (rc) {
log_debug(eis_client_get_context(client), "Message failed to send: %s\n", strerror(-rc));
eis_client_disconnect(client);
} else {
client->state = EIS_CLIENT_STATE_CONNECTED;
}
}
_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_device *d, *tmp;
list_for_each_safe(d, tmp, &client->devices, link)
eis_device_disconnect(d);
}
eis_queue_disconnect_event(client);
_fallthrough_;
case EIS_CLIENT_STATE_NEW:
client_send_disconnect(client);
client->state = EIS_CLIENT_STATE_DISCONNECTED;
source_remove(client->source);
break;
}
eis_client_unref(client);
}
static int
client_new_device(struct eis_client *client,
uint32_t id, const char *name, uint32_t capabilities,
const struct dimensions *dim_pointer,
const struct dimensions *dim_touch,
enum eis_keymap_type keymap_type,
int keymap_fd, size_t keymap_sz)
{
/* Check for duplicate IDs */
struct eis_device *d;
list_for_each(d, &client->devices, link) {
if (d->id == id)
return -EINVAL;
}
if (capabilities == 0 ||
capabilities & ~(bit(EIS_DEVICE_CAP_POINTER) |
bit(EIS_DEVICE_CAP_POINTER_ABSOLUTE) |
bit(EIS_DEVICE_CAP_KEYBOARD) |
bit(EIS_DEVICE_CAP_TOUCH)))
return -EINVAL;
struct eis_device *device = eis_device_new(client, id, name, capabilities);
list_append(&client->devices, &device->link);
if (flag_is_set(capabilities, EIS_DEVICE_CAP_POINTER_ABSOLUTE))
eis_device_set_pointer_range(device, dim_pointer->width,
dim_pointer->height);
if (flag_is_set(capabilities, EIS_DEVICE_CAP_TOUCH))
eis_device_set_touch_range(device, dim_touch->width,
dim_touch->height);
eis_device_set_client_keymap(device, keymap_type, keymap_fd, keymap_sz);
log_debug(eis_client_parent(client),
"New device %d '%s' caps: %s%s%s%s\n",
id, name,
eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER) ? "p" : "",
eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE) ? "a" : "",
eis_device_has_capability(device, EIS_DEVICE_CAP_KEYBOARD) ? "k" : "",
eis_device_has_capability(device, EIS_DEVICE_CAP_TOUCH) ? "t" : "");
eis_queue_added_event(device);
return 0;
}
static int
client_remove_device(struct eis_client *client, uint32_t deviceid)
{
struct eis_device *device;
list_for_each(device, &client->devices, link) {
if (device->id == deviceid) {
eis_queue_removed_event(device);
break;
}
}
return 0;
}
static int
client_pointer_rel(struct eis_client *client, uint32_t deviceid,
double x, double y)
{
struct eis_device *device;
list_for_each(device, &client->devices, link) {
if (device->id == deviceid) {
return eis_device_pointer_rel(device, x, y);
}
}
return -EINVAL;
}
static int
client_pointer_abs(struct eis_client *client, uint32_t deviceid,
double x, double y)
{
struct eis_device *device;
list_for_each(device, &client->devices, link) {
if (device->id == deviceid) {
return eis_device_pointer_abs(device, x, y);
}
}
return -EINVAL;
}
static int
client_pointer_button(struct eis_client *client, uint32_t deviceid,
uint32_t button, bool state)
{
struct eis_device *device;
list_for_each(device, &client->devices, link) {
if (device->id == deviceid) {
return eis_device_pointer_button(device, button, state);
}
}
return -EINVAL;
}
static int
client_keyboard_key(struct eis_client *client, uint32_t deviceid,
uint32_t key, bool state)
{
struct eis_device *device;
list_for_each(device, &client->devices, link) {
if (device->id == deviceid) {
return eis_device_keyboard_key(device, key, state);
}
}
return -EINVAL;
}
static int
client_touch(struct eis_client *client, uint32_t deviceid,
uint32_t touchid, bool is_down, bool is_up,
double x, double y)
{
struct eis_device *device;
list_for_each(device, &client->devices, link) {
if (device->id == deviceid) {
return eis_device_touch(device, touchid,
is_down, is_up, x, y);
}
}
return -EINVAL;
}
static int
client_configure_name(struct eis_client *client, const char *name)
{
/* We silently ignore wrong configure messages */
if (client->state != EIS_CLIENT_STATE_NEW || client->name)
return 0;
client->name = xstrdup(name);
return 0;
}
static int
client_configure_caps(struct eis_client *client, bool policy_is_allow,
uint32_t allowed_caps, uint32_t denied_caps)
{
if (policy_is_allow) {
/* We can only manage allow masks while we're in
* default-allow policy.
* first call to set allow masks is taken as-is, from then onwards
* we can only restrict */
if (client->restrictions.cap_allow_mask == ~0U)
client->restrictions.cap_allow_mask = allowed_caps;
else
client->restrictions.cap_allow_mask &= allowed_caps;
} else {
client->restrictions.cap_policy = CLIENT_CAP_POLICY_DENY;
}
client->restrictions.cap_deny_mask |= denied_caps;
/* FIXME: if something is disallowed now, we should disconnect
* accordingly.
*/
return 0;
}
static int
client_new_handle_msg(struct eis_client *client, struct message *msg)
{
int rc = 0;
switch (msg->type) {
case MESSAGE_CONNECT:
eis_queue_connect_event(client);
if (client->name == NULL)
client->name = steal(&msg->connect.name);
client->state = EIS_CLIENT_STATE_CONNECTING;
break;
case MESSAGE_DISCONNECT:
rc = -ECANCELED;
break;
case MESSAGE_ADD_DEVICE:
case MESSAGE_REMOVE_DEVICE:
case MESSAGE_POINTER_REL:
case MESSAGE_POINTER_ABS:
case MESSAGE_POINTER_BUTTON:
case MESSAGE_KEYBOARD_KEY:
case MESSAGE_TOUCH:
rc = -EPROTO;
break;
case MESSAGE_CONFIGURE_NAME:
rc = client_configure_name(client, msg->configure_name.name);
break;
case MESSAGE_CONFIGURE_CAPABILITIES:
rc = client_configure_caps(client, msg->configure_caps.policy_is_allow,
msg->configure_caps.caps_allow,
msg->configure_caps.caps_deny);
break;
}
return rc;
}
static int
client_connecting_handle_msg(struct eis_client *client, const struct message *msg)
{
int rc = 0;
switch (msg->type) {
case MESSAGE_CONNECT:
rc = -EPROTO;
break;
case MESSAGE_DISCONNECT:
rc = -ECANCELED;
break;
case MESSAGE_ADD_DEVICE:
case MESSAGE_REMOVE_DEVICE:
case MESSAGE_POINTER_REL:
case MESSAGE_POINTER_ABS:
case MESSAGE_POINTER_BUTTON:
case MESSAGE_KEYBOARD_KEY:
case MESSAGE_TOUCH:
rc = -EPROTO;
break;
case MESSAGE_CONFIGURE_NAME:
rc = client_configure_name(client, msg->configure_name.name);
break;
case MESSAGE_CONFIGURE_CAPABILITIES:
rc = client_configure_caps(client, msg->configure_caps.policy_is_allow,
msg->configure_caps.caps_allow,
msg->configure_caps.caps_deny);
break;
}
return rc;
}
static int
client_connected_handle_msg(struct eis_client *client,
const struct message *msg)
{
int rc = 0;
switch (msg->type) {
case MESSAGE_CONNECT:
rc = -EPROTO;
break;
case MESSAGE_DISCONNECT:
rc = -ECANCELED;
break;
case MESSAGE_ADD_DEVICE:
rc = client_new_device(client, msg->add_device.deviceid,
msg->add_device.name,
msg->add_device.capabilities,
&msg->add_device.dim_pointer,
&msg->add_device.dim_touch,
msg->add_device.keymap_type,
msg->add_device.keymap_fd,
msg->add_device.keymap_size);
break;
case MESSAGE_REMOVE_DEVICE:
rc = client_remove_device(client, msg->remove_device.deviceid);
break;
case MESSAGE_POINTER_REL:
rc = client_pointer_rel(client, msg->pointer_rel.deviceid,
msg->pointer_rel.x, msg->pointer_rel.y);
break;
case MESSAGE_POINTER_ABS:
rc = client_pointer_abs(client, msg->pointer_abs.deviceid,
msg->pointer_abs.x, msg->pointer_abs.y);
break;
case MESSAGE_POINTER_BUTTON:
rc = client_pointer_button(client, msg->pointer_button.deviceid,
msg->pointer_button.button,
msg->pointer_button.state);
break;
case MESSAGE_KEYBOARD_KEY:
rc = client_keyboard_key(client, msg->keyboard_key.deviceid,
msg->keyboard_key.key,
msg->keyboard_key.state);
break;
case MESSAGE_TOUCH:
rc = client_touch(client, msg->touch.deviceid, msg->touch.touchid,
msg->touch.is_down, msg->touch.is_up,
msg->touch.x, msg->touch.y);
break;
case MESSAGE_CONFIGURE_NAME:
rc = client_configure_name(client, msg->configure_name.name);
break;
case MESSAGE_CONFIGURE_CAPABILITIES:
rc = client_configure_caps(client, msg->configure_caps.policy_is_allow,
msg->configure_caps.caps_allow,
msg->configure_caps.caps_deny);
break;
}
return rc;
}
static int
client_message_callback(struct brei_message *bmsg, void *userdata)
{
struct eis_client *client = userdata;
size_t consumed;
_cleanup_message_ struct message *msg = eis_proto_parse_message(bmsg, &consumed);
if (!msg)
return -EBADMSG;
int rc = 0;
switch (client->state) {
case EIS_CLIENT_STATE_NEW:
rc = client_new_handle_msg(client, msg);
break;
case EIS_CLIENT_STATE_CONNECTING:
/* Client is waiting for us, shouldn't send anything
* but disconnect */
rc = client_connecting_handle_msg(client, msg);
break;
case EIS_CLIENT_STATE_CONNECTED:
rc = client_connected_handle_msg(client, msg);
break;
case EIS_CLIENT_STATE_DISCONNECTED:
abort();
}
if (rc < 0)
return rc;
else
return consumed;
}
static void
client_dispatch(struct source *source, void *userdata)
{
_cleanup_eis_client_ struct 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)
eis_client_disconnect(client);
static const char *client_states[] = {
"NEW",
"CONNECTING",
"CONNECTED",
"DISCONNECTED",
};
if (rc == -ECANCELED)
log_info(eis_client_parent(client), "Disconnected\n");
else if (rc)
log_warn(eis_client_parent(client), "Client error: %s\n",
strerror(-rc));
if (old_state != client->state) {
log_debug(eis_client_parent(client), "Client dispatch: %s -> %s\n",
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->id = ++client_id;
list_init(&client->devices);
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_policy = CLIENT_CAP_POLICY_ALLOW;
client->restrictions.cap_allow_mask = ~0U;
client->restrictions.cap_deny_mask = 0;
eis_add_client(eis, eis_client_ref(client));
source_unref(s);
return client;
}
void
eis_client_connect_device(struct eis_client *client, struct eis_device *device)
{
client_send_device_added(client, device);
}
void
eis_client_disconnect_device(struct eis_client *client, struct eis_device *device)
{
client_send_device_removed(client, device);
eis_queue_removed_event(device);
list_remove(&device->link);
eis_device_unref(device);
}
void
eis_client_suspend_device(struct eis_client *client, struct eis_device *device)
{
client_send_device_suspended(client, device);
}
void
eis_client_resume_device(struct eis_client *client, struct eis_device *device)
{
client_send_device_resumed(client, device);
}