mirror of
https://gitlab.freedesktop.org/libinput/libei.git
synced 2025-12-29 23:00:08 +01:00
623 lines
14 KiB
C
623 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-logger.h"
|
|
#include "util-macros.h"
|
|
#include "util-mem.h"
|
|
#include "util-sources.h"
|
|
#include "util-strings.h"
|
|
|
|
#include "libeis-private.h"
|
|
|
|
#include "proto/ei.pb-c.h"
|
|
|
|
/* The message type for the wire format */
|
|
enum message_type {
|
|
MESSAGE_CONNECT,
|
|
MESSAGE_DISCONNECT,
|
|
MESSAGE_ADD_DEVICE,
|
|
MESSAGE_REMOVE_DEVICE,
|
|
MESSAGE_POINTER_REL,
|
|
MESSAGE_POINTER_BUTTON,
|
|
MESSAGE_KEYBOARD_KEY,
|
|
};
|
|
|
|
struct message {
|
|
enum message_type type;
|
|
union {
|
|
struct message_connect {
|
|
char *name;
|
|
} connect;
|
|
struct message_disconnect {
|
|
uint8_t pad; /* no data */
|
|
} disconnect;
|
|
struct message_add_device {
|
|
uint32_t deviceid;
|
|
uint32_t capabilities;
|
|
} add_device;
|
|
struct message_remove_device {
|
|
uint32_t deviceid;
|
|
} remove_device;
|
|
struct message_pointer_rel {
|
|
uint32_t deviceid;
|
|
int32_t x;
|
|
int32_t y;
|
|
} pointer_rel;
|
|
struct message_pointer_button {
|
|
uint32_t deviceid;
|
|
uint32_t button;
|
|
bool state;
|
|
} pointer_button;
|
|
struct message_keyboard_key {
|
|
uint32_t deviceid;
|
|
uint32_t key;
|
|
bool state;
|
|
} keyboard_key;;
|
|
};
|
|
};
|
|
|
|
static void
|
|
message_free(struct message *msg)
|
|
{
|
|
switch (msg->type) {
|
|
case MESSAGE_CONNECT:
|
|
free(msg->connect.name);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
free(msg);
|
|
}
|
|
|
|
DEFINE_TRIVIAL_CLEANUP_FUNC(struct message*, message_free);
|
|
|
|
static void
|
|
eis_client_destroy(struct eis_client *client)
|
|
{
|
|
free(client->name);
|
|
source_unref(client->source);
|
|
list_remove(&client->link);
|
|
}
|
|
|
|
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_ struct eis*
|
|
eis_client_get_context(struct eis_client *client)
|
|
{
|
|
return eis_client_parent(client);
|
|
}
|
|
|
|
static int
|
|
client_send_msg(struct eis_client *client, const ServerMessage *msg)
|
|
{
|
|
size_t msglen = server_message__get_packed_size(msg);
|
|
Frame frame = FRAME__INIT;
|
|
frame.length = msglen;
|
|
size_t framelen = frame__get_packed_size(&frame);
|
|
|
|
uint8_t buf[framelen + msglen];
|
|
frame__pack(&frame, buf);
|
|
server_message__pack(msg, buf + framelen);
|
|
return min(0, xsend(source_get_fd(client->source), buf, sizeof(buf)));
|
|
}
|
|
|
|
static int
|
|
client_send_hello(struct eis_client *client)
|
|
{
|
|
ServerMessage msg = SERVER_MESSAGE__INIT;
|
|
Hello hello = HELLO__INIT;
|
|
|
|
msg.hello = &hello;
|
|
msg.msg_case = SERVER_MESSAGE__MSG_HELLO;
|
|
|
|
return client_send_msg(client, &msg);
|
|
}
|
|
|
|
static int
|
|
client_send_disconnect(struct eis_client *client)
|
|
{
|
|
ServerMessage msg = SERVER_MESSAGE__INIT;
|
|
Disconnected disconnected = DISCONNECTED__INIT;
|
|
|
|
msg.disconnected = &disconnected;
|
|
msg.msg_case = SERVER_MESSAGE__MSG_DISCONNECTED;
|
|
|
|
return client_send_msg(client, &msg);
|
|
}
|
|
|
|
static int
|
|
client_send_connect(struct eis_client *client)
|
|
{
|
|
ServerMessage msg = SERVER_MESSAGE__INIT;
|
|
Connected connected = CONNECTED__INIT;
|
|
|
|
msg.connected = &connected;
|
|
msg.msg_case = SERVER_MESSAGE__MSG_CONNECTED;
|
|
|
|
return client_send_msg(client, &msg);
|
|
}
|
|
|
|
static int
|
|
client_send_accepted(struct eis_client *client, struct eis_device *device)
|
|
{
|
|
ServerMessage msg = SERVER_MESSAGE__INIT;
|
|
Accepted accepted = ACCEPTED__INIT;
|
|
|
|
accepted.deviceid = device->id;
|
|
|
|
msg.accepted = &accepted;
|
|
msg.msg_case = SERVER_MESSAGE__MSG_ACCEPTED;
|
|
|
|
return client_send_msg(client, &msg);
|
|
}
|
|
|
|
static int
|
|
client_send_removed(struct eis_client *client, struct eis_device *device)
|
|
{
|
|
ServerMessage msg = SERVER_MESSAGE__INIT;
|
|
Removed removed = REMOVED__INIT;
|
|
|
|
msg.removed = &removed;
|
|
msg.msg_case = SERVER_MESSAGE__MSG_REMOVED;
|
|
|
|
return client_send_msg(client, &msg);
|
|
}
|
|
|
|
_public_ void
|
|
eis_client_connect(struct eis_client *client)
|
|
{
|
|
switch(client->state) {
|
|
case EIS_CLIENT_STATE_CONNECTING:
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
int rc = client_send_connect(client);
|
|
if (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_HELLO:
|
|
client_send_disconnect(client);
|
|
client->state = EIS_CLIENT_STATE_DISCONNECTED;
|
|
source_remove(client->source);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int
|
|
client_new_device(struct eis_client *client,
|
|
uint32_t id, uint32_t capabilities)
|
|
{
|
|
/* 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, capabilities);
|
|
list_append(&client->devices, &device->link);
|
|
|
|
log_debug(eis_client_parent(client), "New device %d caps: %#x\n",
|
|
id, capabilities);
|
|
|
|
eis_queue_added_event(device);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
client_pointer_rel(struct eis_client *client, uint32_t deviceid,
|
|
int32_t x, int32_t 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_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_hello_handle_msg(struct eis_client *client, struct message *msg)
|
|
{
|
|
int rc = 0;
|
|
|
|
switch (msg->type) {
|
|
case MESSAGE_CONNECT:
|
|
eis_queue_connect_event(client);
|
|
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_BUTTON:
|
|
case MESSAGE_KEYBOARD_KEY:
|
|
rc = -EPROTO;
|
|
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_BUTTON:
|
|
case MESSAGE_KEYBOARD_KEY:
|
|
rc = -EPROTO;
|
|
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:
|
|
client->state = EIS_CLIENT_STATE_DISCONNECTED;
|
|
rc = -ECANCELED;
|
|
break;
|
|
case MESSAGE_ADD_DEVICE:
|
|
rc = client_new_device(client, msg->add_device.deviceid,
|
|
msg->add_device.capabilities);
|
|
break;
|
|
case MESSAGE_REMOVE_DEVICE:
|
|
/* FIXME: remove device */
|
|
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_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;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static struct message *
|
|
client_parse_message(const char *data, size_t *len)
|
|
{
|
|
/* Every message is prefixed by a fixed-length Frame message which
|
|
* contains the length of the next message. Parse that one first,
|
|
* then the real message */
|
|
static size_t framelen = 0;
|
|
if (framelen == 0) {
|
|
Frame f = FRAME__INIT;
|
|
f.length = 0xffff;
|
|
framelen = frame__get_packed_size(&f);
|
|
assert(framelen >= 5);
|
|
}
|
|
Frame *frame = frame__unpack(NULL, framelen, (const unsigned char *)data);
|
|
if (!frame)
|
|
return NULL;
|
|
|
|
size_t msglen = frame->length;
|
|
frame__free_unpacked(frame, NULL);
|
|
|
|
|
|
const unsigned char *msgdata = (const unsigned char *)data + framelen;
|
|
_cleanup_(message_freep) struct message *msg = xalloc(sizeof(*msg));
|
|
ClientMessage *proto = client_message__unpack(NULL, msglen, msgdata);
|
|
if (!proto) {
|
|
return NULL;
|
|
}
|
|
|
|
*len = framelen + msglen;
|
|
|
|
bool success = true;
|
|
switch (proto->msg_case) {
|
|
case CLIENT_MESSAGE__MSG_CONNECT:
|
|
{
|
|
Connect *c = proto->connect;
|
|
*msg = (struct message) {
|
|
.type = MESSAGE_CONNECT,
|
|
.connect.name = xstrdup(c->name),
|
|
};
|
|
}
|
|
break;
|
|
case CLIENT_MESSAGE__MSG_DISCONNECT:
|
|
*msg = (struct message) {
|
|
.type = MESSAGE_DISCONNECT,
|
|
};
|
|
break;
|
|
case CLIENT_MESSAGE__MSG_ADD:
|
|
{
|
|
AddDevice *a = proto->add;
|
|
*msg = (struct message) {
|
|
.type = MESSAGE_ADD_DEVICE,
|
|
.add_device.deviceid = a->deviceid,
|
|
.add_device.capabilities = a->capabilities,
|
|
};
|
|
}
|
|
break;
|
|
case CLIENT_MESSAGE__MSG_REMOVE:
|
|
{
|
|
RemoveDevice *r = proto->remove;
|
|
*msg = (struct message) {
|
|
.type = MESSAGE_REMOVE_DEVICE,
|
|
.remove_device.deviceid = r->deviceid,
|
|
};
|
|
}
|
|
break;
|
|
case CLIENT_MESSAGE__MSG_REL:
|
|
{
|
|
PointerRelative *r = proto->rel;
|
|
*msg = (struct message) {
|
|
.type = MESSAGE_POINTER_REL,
|
|
.pointer_rel.deviceid = r->deviceid,
|
|
.pointer_rel.x = r->x,
|
|
.pointer_rel.y = r->y,
|
|
};
|
|
}
|
|
break;
|
|
case CLIENT_MESSAGE__MSG_BUTTON:
|
|
{
|
|
PointerButton *b = proto->button;
|
|
*msg = (struct message) {
|
|
.type = MESSAGE_POINTER_BUTTON,
|
|
.pointer_button.deviceid = b->deviceid,
|
|
.pointer_button.button = b->button,
|
|
.pointer_button.state = b->state,
|
|
};
|
|
}
|
|
break;
|
|
case CLIENT_MESSAGE__MSG_KEY:
|
|
{
|
|
KeyboardKey *k = proto->key;
|
|
*msg = (struct message) {
|
|
.type = MESSAGE_KEYBOARD_KEY,
|
|
.keyboard_key.deviceid = k->deviceid,
|
|
.keyboard_key.key = k->key,
|
|
.keyboard_key.state = k->state,
|
|
};
|
|
}
|
|
break;
|
|
default:
|
|
success = false;
|
|
break;
|
|
}
|
|
|
|
client_message__free_unpacked(proto, NULL);
|
|
|
|
return success ? steal(&msg) : NULL;
|
|
}
|
|
|
|
static void
|
|
client_dispatch(struct source *source, void *userdata)
|
|
{
|
|
struct eis_client *client = userdata;
|
|
enum eis_client_state old_state = client->state;
|
|
_cleanup_(message_freep) struct message *msg = NULL;
|
|
|
|
_cleanup_iobuf_ struct iobuf *buf = iobuf_new(64);
|
|
int rc = iobuf_append_from_fd(buf, source_get_fd(source));
|
|
if (rc == -EAGAIN) {
|
|
return;
|
|
} else if (rc == 0) {
|
|
rc = -ECANCELED;
|
|
goto error;
|
|
} else if (rc < 0) {
|
|
goto error;
|
|
}
|
|
|
|
size_t idx = 0;
|
|
while (true) {
|
|
const char *data = iobuf_data(buf) + idx;
|
|
size_t len = iobuf_len(buf) - idx;
|
|
|
|
if (len == 0)
|
|
break;
|
|
|
|
msg = client_parse_message(data, &len);
|
|
if (!msg) {
|
|
rc = -EBADMSG;
|
|
goto error;
|
|
}
|
|
|
|
idx += len;
|
|
|
|
switch (client->state) {
|
|
case EIS_CLIENT_STATE_HELLO:
|
|
rc = client_hello_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();
|
|
}
|
|
}
|
|
|
|
error:
|
|
if (rc < 0)
|
|
eis_client_disconnect(client);
|
|
|
|
static const char *client_states[] = {
|
|
"HELLO",
|
|
"CONNECTING",
|
|
"CONNECTED",
|
|
"DISCONNECTED",
|
|
};
|
|
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);
|
|
|
|
list_append(&eis->clients, &client->link);
|
|
client->source = source_add_autoclose(eis->sink, fd,
|
|
client_dispatch, client);
|
|
|
|
client->state = EIS_CLIENT_STATE_HELLO;
|
|
int rc = client_send_hello(client);
|
|
if (rc != 0)
|
|
client = eis_client_unref(client);
|
|
|
|
return client;
|
|
}
|
|
|
|
void
|
|
eis_client_connect_device(struct eis_client *client, struct eis_device *device)
|
|
{
|
|
client_send_accepted(client, device);
|
|
}
|
|
|
|
void
|
|
eis_client_disconnect_device(struct eis_client *client, struct eis_device *device)
|
|
{
|
|
client_send_removed(client, device);
|
|
eis_queue_removed_event(device);
|
|
list_remove(&device->link);
|
|
eis_device_unref(device);
|
|
}
|