libei/src/libeis-client.c
Peter Hutterer b6f477fb96 util-object: split macro to generate unref into one two
One macro that also defines the cleanup function, one macro that only
defines the unref. This is required for any place where we want to
use cleanup from multiple source files - like the test suite.
2022-03-03 05:41:15 +00:00

671 lines
16 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 "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);
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_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);
}
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_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) {
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);
}
_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_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);
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;
}
static int
client_msg_unbind_seat(struct eis_client *client, uint32_t seatid)
{
struct eis_seat *seat = eis_client_find_seat(client, seatid);
if (seat)
eis_seat_unbind(seat);
return seat ? 0 : -EINVAL;
}
static int
client_msg_start_emulating(struct eis_client *client, uint32_t deviceid)
{
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)
{
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)
{
struct eis_device *device = eis_client_find_device(client, deviceid);
if (device)
return eis_device_event_frame(device);
return 0;
}
static int
client_msg_pointer_rel(struct eis_client *client, uint32_t deviceid,
double x, double y)
{
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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
struct eis_device *device = eis_client_find_device(client, deviceid);
if (device)
return eis_device_event_touch_up(device, touchid);
return -EINVAL;
}
static int
client_msg_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_msg_configure_capabilities(struct eis_client *client, uint32_t allowed_caps)
{
client->restrictions.cap_allow_mask = allowed_caps;
/* FIXME: if something is disallowed now, we should disconnect
* accordingly.
*/
return 0;
}
static int
client_msg_connect(struct eis_client *client, const char *name)
{
if (client->name == NULL)
client->name = xstrdup(name);
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_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_property_with_event(struct eis_client *client,
const char *name, const char *value,
uint32_t permissions)
{
int rc = client_msg_property(client, name, value, permissions);
if (rc == 0)
eis_queue_property_event(client, name, value, permissions);
return rc;
}
static const struct eis_proto_interface intf_state_new = {
.connect = client_msg_connect,
.property = client_msg_property,
.connect_done = client_msg_connect_done,
.disconnect = client_msg_disconnect,
.configure_name = client_msg_configure_name,
.configure_capabilities = client_msg_configure_capabilities,
};
/* Client is waiting for us, shouldn't send anything except disconnect */
static const struct eis_proto_interface intf_state_connecting = {
.disconnect = client_msg_disconnect,
.configure_name = client_msg_configure_name,
.configure_capabilities = client_msg_configure_capabilities,
};
static const struct eis_proto_interface intf_state_connected = {
.disconnect = client_msg_disconnect,
.property = client_msg_property_with_event,
.bind_seat = client_msg_bind_seat,
.unbind_seat = client_msg_unbind_seat,
.close_device = client_msg_close_device,
.configure_name = client_msg_configure_name,
.configure_capabilities = client_msg_configure_capabilities,
/* 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,
};
static const struct eis_proto_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,
};
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",
"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->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 = ~0U;
eis_add_client(eis, eis_client_ref(client));
source_unref(s);
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);
}