mirror of
https://gitlab.freedesktop.org/libinput/libei.git
synced 2026-01-02 09:50:09 +01:00
There's nothing in the protocol to modify the client device state from the server, so a pause/resume cycle must leave the client with the same(-ish) state. Pause is really just that, a short "no event now please". Anything that would require e.g. modifying the device state by releasing keys or buttons should result in the device being removed and re-added. Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
645 lines
15 KiB
C
645 lines
15 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);
|
|
|
|
_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 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);
|
|
}
|
|
|
|
_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_unbind(s);
|
|
eis_seat_remove(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_seat *seat;
|
|
|
|
list_for_each(seat, &client->seats, link) {
|
|
struct eis_device *device;
|
|
|
|
list_for_each(device, &seat->devices, link) {
|
|
if (device->id == deviceid) {
|
|
eis_device_closed_by_client(device);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
client_msg_bind_seat(struct eis_client *client, uint32_t seatid, uint32_t caps)
|
|
{
|
|
struct eis_seat *seat;
|
|
|
|
list_for_each(seat, &client->seats, link) {
|
|
if (seat->id == seatid) {
|
|
eis_seat_bind(seat, caps);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int
|
|
client_msg_unbind_seat(struct eis_client *client, uint32_t seatid)
|
|
{
|
|
struct eis_seat *seat;
|
|
|
|
list_for_each(seat, &client->seats, link) {
|
|
if (seat->id == seatid) {
|
|
eis_seat_unbind(seat);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int
|
|
client_msg_pointer_rel(struct eis_client *client, uint32_t deviceid,
|
|
double x, double y)
|
|
{
|
|
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 eis_device_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_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 eis_device_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_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 eis_device_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_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 eis_device_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_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 eis_device_pointer_scroll_discrete(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_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 eis_device_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_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 eis_device_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_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 eis_device_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_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 eis_device_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, 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_msg_connect(struct eis_client *client, const char *name)
|
|
{
|
|
eis_queue_connect_event(client);
|
|
if (client->name == NULL)
|
|
client->name = xstrdup(name);
|
|
client->state = EIS_CLIENT_STATE_CONNECTING;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
client_msg_disconnect(struct eis_client *client)
|
|
{
|
|
return -ECANCELED;
|
|
}
|
|
|
|
static const struct eis_proto_interface intf_state_new = {
|
|
.connect = client_msg_connect,
|
|
.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,
|
|
.bind_seat = client_msg_bind_seat,
|
|
.unbind_seat = client_msg_unbind_seat,
|
|
.close_device = client_msg_close_device,
|
|
.rel = client_msg_pointer_rel,
|
|
.abs = client_msg_pointer_abs,
|
|
.button = client_msg_pointer_button,
|
|
.scroll = client_msg_pointer_scroll,
|
|
.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,
|
|
.configure_name = client_msg_configure_name,
|
|
.configure_capabilities = client_msg_configure_capabilities,
|
|
};
|
|
|
|
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);
|
|
}
|
|
#if 0
|
|
struct eis_client *client = userdata;
|
|
size_t consumed;
|
|
|
|
_cleanup_message_ struct message *msg = eis_proto_parse_message(bmsg, &consumed);
|
|
if (!msg)
|
|
return -EBADMSG;
|
|
|
|
log_debug(eis_client_parent(client),
|
|
"handling message type %s\n", message_type_to_string(msg->type));
|
|
|
|
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;
|
|
}
|
|
#endif
|
|
|
|
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)
|
|
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);
|
|
|
|
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_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);
|
|
}
|