libei/src/libei.c
Peter Hutterer 90d2b1b980 Add support for absolute pointer motion events
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
2020-09-24 11:24:58 +10:00

754 lines
16 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-io.h"
#include "util-macros.h"
#include "util-object.h"
#include "util-sources.h"
#include "util-strings.h"
#include "libei.h"
#include "libei-private.h"
#include "libei-proto.h"
#include "brei-shared.h"
static const char *
ei_event_type_to_string(enum ei_event_type type)
{
switch(type) {
CASE_RETURN_STRING(EI_EVENT_CONNECT);
CASE_RETURN_STRING(EI_EVENT_DISCONNECT);
CASE_RETURN_STRING(EI_EVENT_DEVICE_ADDED);
CASE_RETURN_STRING(EI_EVENT_DEVICE_REMOVED);
CASE_RETURN_STRING(EI_EVENT_DEVICE_SUSPENDED);
CASE_RETURN_STRING(EI_EVENT_DEVICE_RESUMED);
}
assert(!"Unhandled event type");
}
static void
ei_event_destroy(struct ei_event *event)
{
switch (event->type) {
case EI_EVENT_CONNECT:
case EI_EVENT_DISCONNECT:
case EI_EVENT_DEVICE_ADDED:
case EI_EVENT_DEVICE_REMOVED:
case EI_EVENT_DEVICE_SUSPENDED:
case EI_EVENT_DEVICE_RESUMED:
break;
default:
assert(!"destroy not implemented for this type");
}
ei_device_unref(event->device);
}
static
OBJECT_IMPLEMENT_CREATE(ei_event);
static
OBJECT_IMPLEMENT_REF(ei_event);
_public_
OBJECT_IMPLEMENT_UNREF(ei_event);
_public_
OBJECT_IMPLEMENT_GETTER(ei_event, type, enum ei_event_type);
_public_
OBJECT_IMPLEMENT_GETTER(ei_event, device, struct ei_device*);
static void
ei_destroy(struct ei *ei)
{
ei_disconnect(ei);
struct ei_event *e;
while ((e = ei_get_event(ei)) != NULL)
ei_event_unref(e);
if (ei->backend_interface.destroy)
ei->backend_interface.destroy(ei, ei->backend);
ei->backend = NULL;
sink_unref(ei->sink);
free(ei->name);
}
static
OBJECT_IMPLEMENT_CREATE(ei);
_public_
OBJECT_IMPLEMENT_REF(ei);
_public_
OBJECT_IMPLEMENT_UNREF(ei);
#define _cleanup_ei_ _cleanup_(ei_cleanup)
_public_
OBJECT_IMPLEMENT_SETTER(ei, user_data, void *);
_public_
OBJECT_IMPLEMENT_GETTER(ei, user_data, void *);
_public_ struct ei *
ei_new(void *user_data)
{
_cleanup_ei_ struct ei *ei = ei_create(NULL);
ei->state = EI_STATE_NEW;
list_init(&ei->event_queue);
list_init(&ei->devices);
ei_log_set_handler(ei, NULL);
ei_log_set_priority(ei, EI_LOG_PRIORITY_INFO);
ei->sink = sink_new();
if (!ei->sink)
return NULL;
ei->user_data = user_data;
ei->backend = NULL;
return steal(&ei);
}
_public_ int
ei_get_fd(struct ei *ei)
{
return sink_get_fd(ei->sink);
}
_public_ void
ei_dispatch(struct ei *ei)
{
sink_dispatch(ei->sink);
}
static void
queue_event(struct ei *ei, struct ei_event *event)
{
log_debug(ei, "queuing event type %s (%d)\n",
ei_event_type_to_string(event->type), event->type);
list_append(&ei->event_queue, &event->link);
}
static void
queue_connect_event(struct ei *ei)
{
struct ei_event *e = ei_event_create(&ei->object);
e->type = EI_EVENT_CONNECT;
queue_event(ei, e);
}
static void
queue_disconnect_event(struct ei *ei)
{
struct ei_event *e = ei_event_create(&ei->object);
e->type = EI_EVENT_DISCONNECT;
queue_event(ei, e);
}
static void
queue_added_event(struct ei_device *device)
{
struct ei *ei= ei_device_get_context(device);
struct ei_event *e = ei_event_create(&ei->object);
e->type = EI_EVENT_DEVICE_ADDED;
e->device = ei_device_ref(device);
queue_event(ei, e);
}
static void
queue_removed_event(struct ei_device *device)
{
struct ei *ei= ei_device_get_context(device);
struct ei_event *e = ei_event_create(&ei->object);
e->type = EI_EVENT_DEVICE_REMOVED;
e->device = ei_device_ref(device);
queue_event(ei, e);
}
static void
queue_suspended_event(struct ei_device *device)
{
struct ei *ei= ei_device_get_context(device);
struct ei_event *e = ei_event_create(&ei->object);
e->type = EI_EVENT_DEVICE_SUSPENDED;
e->device = ei_device_ref(device);
queue_event(ei, e);
}
static void
queue_resumed_event(struct ei_device *device)
{
struct ei *ei= ei_device_get_context(device);
struct ei_event *e = ei_event_create(&ei->object);
e->type = EI_EVENT_DEVICE_RESUMED;
e->device = ei_device_ref(device);
queue_event(ei, e);
}
static int
connection_send_connect(struct ei *ei)
{
if (ei->state == EI_STATE_NEW ||
ei->state == EI_STATE_DISCONNECTED)
return 0;
return ei_proto_send_connect(ei);
}
static int
connection_send_disconnect(struct ei *ei)
{
if (ei->state == EI_STATE_NEW ||
ei->state == EI_STATE_DISCONNECTED)
return 0;
return ei_proto_send_disconnect(ei);
}
static int
connection_send_add(struct ei *ei, struct ei_device *device)
{
if (ei->state == EI_STATE_NEW ||
ei->state == EI_STATE_DISCONNECTED)
return 0;
return ei_proto_send_add(ei, device);
}
static int
connection_send_remove(struct ei *ei, struct ei_device *device)
{
if (ei->state == EI_STATE_NEW ||
ei->state == EI_STATE_DISCONNECTED)
return 0;
return ei_proto_send_remove(ei, device);
}
static int
connection_send_rel(struct ei *ei, struct ei_device *device,
double x, double y)
{
if (ei->state == EI_STATE_NEW ||
ei->state == EI_STATE_DISCONNECTED)
return 0;
return ei_proto_send_rel(ei, device, x, y);
}
static int
connection_send_abs(struct ei *ei, struct ei_device *device,
double x, double y)
{
if (ei->state == EI_STATE_NEW ||
ei->state == EI_STATE_DISCONNECTED)
return 0;
return ei_proto_send_abs(ei, device, x, y);
}
static int
connection_send_button(struct ei *ei, struct ei_device *device,
uint32_t b, bool is_press)
{
if (ei->state == EI_STATE_NEW ||
ei->state == EI_STATE_DISCONNECTED)
return 0;
return ei_proto_send_button(ei, device, b, is_press);
}
static int
connection_send_key(struct ei *ei, struct ei_device *device,
uint32_t k, bool is_press)
{
if (ei->state == EI_STATE_NEW ||
ei->state == EI_STATE_DISCONNECTED)
return 0;
return ei_proto_send_key(ei, device, k, is_press);
}
static void
ei_drop_device(struct ei_device *device)
{
switch (device->state) {
case EI_DEVICE_STATE_NEW:
break;
/* In any of these states the client has added the device, so we
* must send the REMOVED */
case EI_DEVICE_STATE_CONNECTING:
case EI_DEVICE_STATE_SUSPENDED:
case EI_DEVICE_STATE_RESUMED:
case EI_DEVICE_STATE_REMOVED:
queue_removed_event(device);
break;
}
list_remove(&device->link);
ei_device_unref(device);
}
void
ei_disconnect(struct ei *ei)
{
if (ei->state == EI_STATE_DISCONNECTED ||
ei->state == EI_STATE_DISCONNECTING)
return;
enum ei_state state = ei->state;
/* We need the disconnecting state to be re-entrant
ei_device_remove() may call ei_disconnect() on a socket error */
ei->state = EI_STATE_DISCONNECTING;
struct ei_device *d, *tmp;
list_for_each_safe(d, tmp, &ei->devices, link) {
/* remove the device */
ei_device_remove(d);
/* And pretend to process the removed message from the
* server */
ei_drop_device(d);
}
if (state != EI_STATE_NEW) {
connection_send_disconnect(ei);
queue_disconnect_event(ei);
}
ei->state = EI_STATE_DISCONNECTED;
if (ei->source)
source_remove(ei->source);
ei->source = source_unref(ei->source);
}
int
ei_add_device(struct ei_device *device)
{
struct ei *ei = ei_device_get_context(device);
int rc = connection_send_add(ei, device);
if (rc) {
ei_disconnect(ei);
return rc;
}
ei_device_ref(device);
list_append(&ei->devices, &device->link);
return 0;
}
int
ei_remove_device(struct ei_device *device)
{
struct ei *ei = ei_device_get_context(device);
int rc = connection_send_remove(ei, device);
if (rc)
ei_disconnect(ei);
return rc;
}
static int
handle_msg_added(struct ei *ei, uint32_t deviceid,
const char *name, uint32_t capabilities,
bool keymap_from_server, enum ei_keymap_type keymap_type,
int keymap_fd, size_t keymap_sz)
{
struct ei_device *d;
log_debug(ei, "Added device %d '%s' with caps %#x\n", deviceid, name, capabilities);
list_for_each(d, &ei->devices, link) {
if (d->id == deviceid) {
ei_device_set_name(d, name);
ei_device_set_capabilities(d, capabilities);
if (keymap_from_server)
ei_device_set_keymap(d, keymap_type, keymap_fd, keymap_sz);
ei_device_added(d);
queue_added_event(d);
return 0;
}
}
/* Wrong device id or a device already removed by the client but we
* won't know which unless we keep some device ID table. Not worth
* it, so just silently ignore */
return 0;
}
static int
handle_msg_removed(struct ei *ei, uint32_t deviceid)
{
struct ei_device *d;
log_debug(ei, "Removed device %d\n", deviceid);
list_for_each(d, &ei->devices, link) {
if (d->id == deviceid) {
ei_drop_device(d);
break;
}
}
return 0;
}
static int
handle_msg_resumed(struct ei *ei, uint32_t deviceid)
{
struct ei_device *d;
log_debug(ei, "Resumed device %d\n", deviceid);
list_for_each(d, &ei->devices, link) {
if (d->id == deviceid) {
ei_device_resumed(d);
queue_resumed_event(d);
break;
}
}
return 0;
}
static int
handle_msg_suspended(struct ei *ei, uint32_t deviceid)
{
struct ei_device *d;
log_debug(ei, "Suspended device %d\n", deviceid);
list_for_each(d, &ei->devices, link) {
if (d->id == deviceid) {
ei_device_suspended(d);
queue_suspended_event(d);
break;
}
}
return 0;
}
int
ei_pointer_rel(struct ei_device *device, double x, double y)
{
struct ei *ei = ei_device_get_context(device);
int rc = connection_send_rel(ei, device, x, y);
if (rc)
ei_disconnect(ei);
return rc;
}
int
ei_pointer_abs(struct ei_device *device, double x, double y)
{
struct ei *ei = ei_device_get_context(device);
int rc = connection_send_abs(ei, device, x, y);
if (rc)
ei_disconnect(ei);
return rc;
}
int
ei_pointer_button(struct ei_device *device, uint32_t button, bool is_press)
{
struct ei *ei = ei_device_get_context(device);
int rc = connection_send_button(ei, device, button, is_press);
if (rc)
ei_disconnect(ei);
return rc;
}
int
ei_keyboard_key(struct ei_device *device, uint32_t key, bool is_press)
{
struct ei *ei = ei_device_get_context(device);
int rc = connection_send_key(ei, device, key, is_press);
if (rc)
ei_disconnect(ei);
return rc;
}
_public_ struct ei_event*
ei_get_event(struct ei *ei)
{
if (list_empty(&ei->event_queue))
return NULL;
struct ei_event *e = list_first_entry(&ei->event_queue, e, link);
list_remove(&e->link);
return e;
}
_public_ struct ei_event*
ei_peek_event(struct ei *ei)
{
if (list_empty(&ei->event_queue))
return NULL;
struct ei_event *e = list_first_entry(&ei->event_queue, e, link);
return ei_event_ref(e);
}
static int
connection_new_handle_msg(struct ei *ei, struct message *msg)
{
int rc = 0;
switch (msg->type) {
case MESSAGE_CONNECTED:
case MESSAGE_DISCONNECTED:
case MESSAGE_DEVICE_ADDED:
case MESSAGE_DEVICE_REMOVED:
case MESSAGE_DEVICE_RESUMED:
case MESSAGE_DEVICE_SUSPENDED:
rc = -EPROTO;
break;
}
return rc;
}
static int
connection_connecting_handle_msg(struct ei *ei, struct message *msg)
{
int rc = 0;
switch (msg->type) {
case MESSAGE_CONNECTED:
ei->state = EI_STATE_CONNECTED;
queue_connect_event(ei);
break;
case MESSAGE_DISCONNECTED:
rc = -ECANCELED;
break;
case MESSAGE_DEVICE_ADDED:
case MESSAGE_DEVICE_REMOVED:
case MESSAGE_DEVICE_RESUMED:
case MESSAGE_DEVICE_SUSPENDED:
rc = -EPROTO;
break;
}
return rc;
}
static int
connection_connected_handle_msg(struct ei *ei, struct message *msg)
{
int rc = 0;
switch (msg->type) {
case MESSAGE_CONNECTED:
rc = -EPROTO;
break;
case MESSAGE_DISCONNECTED:
rc = -ECANCELED;
break;
case MESSAGE_DEVICE_ADDED:
rc = handle_msg_added(ei, msg->added.deviceid,
msg->added.name, msg->added.capabilities,
msg->added.keymap_from_server, msg->added.keymap_type,
msg->added.keymap_fd, msg->added.keymap_size);
break;
case MESSAGE_DEVICE_REMOVED:
rc = handle_msg_removed(ei, msg->removed.deviceid);
break;
case MESSAGE_DEVICE_RESUMED:
rc = handle_msg_resumed(ei, msg->resumed.deviceid);
break;
case MESSAGE_DEVICE_SUSPENDED:
rc = handle_msg_suspended(ei, msg->resumed.deviceid);
break;
}
return rc;
}
static int
connection_message_callback(struct brei_message *bmsg, void *userdata)
{
struct ei *ei = userdata;
size_t consumed;
_cleanup_message_ struct message *msg = ei_proto_parse_message(bmsg, &consumed);
if (!msg)
return -EBADMSG;
log_debug(ei, "handling message type %s\n", message_type_to_string(msg->type));
int rc = 0;
switch (ei->state) {
case EI_STATE_NEW:
abort();
case EI_STATE_BACKEND:
rc = connection_new_handle_msg(ei, msg);
break;
case EI_STATE_CONNECTING:
rc = connection_connecting_handle_msg(ei, msg);
break;
case EI_STATE_CONNECTED:
rc = connection_connected_handle_msg(ei, msg);
break;
case EI_STATE_DISCONNECTING:
case EI_STATE_DISCONNECTED:
abort();
}
if (rc < 0)
return rc;
else
return consumed;
}
static void
connection_dispatch(struct source *source, void *userdata)
{
struct ei *ei = userdata;
enum ei_state old_state = ei->state;
int rc = brei_dispatch(source_get_fd(source), connection_message_callback, ei);
if (rc < 0)
ei_disconnect(ei);
static const char *states[] = {
"NEW",
"BACKEND",
"CONNECTING",
"CONNECTED",
"DISCONNECTED",
"DISCONNECTING",
};
if (rc == -ECANCELED)
log_info(ei, "Disconnected\n");
else if (rc)
log_warn(ei, "Connnection error: %s\n", strerror(-rc));
log_debug(ei, "Connnection dispatch: %s -> %s\n",
states[old_state],
states[ei->state]);
}
int
ei_set_connection(struct ei *ei, int fd)
{
struct source *source = source_new(fd, connection_dispatch, ei);
int rc = sink_add_source(ei->sink, source);
if (rc == 0) {
ei->source = source_ref(source);
ei->state = EI_STATE_BACKEND;
rc = connection_send_connect(ei);
if (rc != 0) {
log_error(ei, "message failed to send: %s\n", strerror(-rc));
ei_disconnect(ei);
} else {
ei->state = EI_STATE_CONNECTING;
}
}
source_unref(source);
return rc;
}
_public_ void
ei_configure_name(struct ei *ei, const char *name)
{
if (ei->state != EI_STATE_NEW)
return;
if (strlen(name) > 1024)
return;
free(ei->name);
ei->name = xstrdup(name);
}
#ifdef _enable_tests_
#include "util-munit.h"
MUNIT_TEST(test_init_unref)
{
struct ei *ei = ei_new(NULL);
munit_assert_int(ei->state, ==, EI_STATE_NEW);
munit_assert(list_empty(&ei->event_queue));
munit_assert(list_empty(&ei->devices));
munit_assert_not_null(ei->sink);
struct ei *refd = ei_ref(ei);
munit_assert_ptr_equal(ei, refd);
munit_assert_int(ei->object.refcount, ==, 2);
struct ei *unrefd = ei_unref(ei);
munit_assert_null(unrefd);
unrefd = ei_unref(ei);
munit_assert_null(unrefd);
return MUNIT_OK;
}
MUNIT_TEST(test_configure_name)
{
struct ei *ei = ei_new(NULL);
ei_configure_name(ei, "foo");
munit_assert_string_equal(ei->name, "foo");
ei_configure_name(ei, "bar");
munit_assert_string_equal(ei->name, "bar");
/* ignore names that are too long */
char buf[1200] = {0};
memset(buf, 'a', sizeof(buf) - 1);
ei_configure_name(ei, buf);
munit_assert_string_equal(ei->name, "bar");
/* ignore names in all other states */
for (enum ei_state state = EI_STATE_NEW + 1;
state <= EI_STATE_DISCONNECTED;
state++) {
ei->state = state;
ei_configure_name(ei, "expect ignored");
munit_assert_string_equal(ei->name, "bar");
}
ei_unref(ei);
return MUNIT_OK;
}
#endif