mirror of
https://gitlab.freedesktop.org/libinput/libei.git
synced 2026-05-27 08:18:16 +02:00
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.
671 lines
16 KiB
C
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);
|
|
}
|
|
|