libei/src/libei-device.c
Peter Hutterer 93b96e42ad Add an EIS-controlled seat to the hierarchy
After CONNECT, the EIS implementation needs to add one or more seats. The
libei client can only create devices within those seats. This mirrors the
wayland hierarchy as well as the X.Org one.

The seat has a set of allowed capabilities, so the client knows ahead of time
when it may not be possible to create a specific device.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
2020-10-21 10:47:47 +10:00

738 lines
19 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 "util-bits.h"
#include "util-macros.h"
#include "util-mem.h"
#include "util-io.h"
#include "util-strings.h"
#include "libei-private.h"
static const char *
ei_device_state_to_string(enum ei_device_state state)
{
switch (state) {
CASE_RETURN_STRING(EI_DEVICE_STATE_NEW);
CASE_RETURN_STRING(EI_DEVICE_STATE_CONNECTING);
CASE_RETURN_STRING(EI_DEVICE_STATE_SUSPENDED);
CASE_RETURN_STRING(EI_DEVICE_STATE_RESUMED);
CASE_RETURN_STRING(EI_DEVICE_STATE_REMOVED);
}
assert(!"Unhandled device state");
}
static void
ei_device_set_state(struct ei_device *device,
enum ei_device_state state)
{
enum ei_device_state old_state = device->state;
device->state = state;
log_debug(ei_device_get_context(device), "device %#x: %s → %s\n",
device->id, ei_device_state_to_string(old_state),
ei_device_state_to_string(state));
}
static void
ei_device_destroy(struct ei_device *device)
{
struct ei_seat *seat = ei_device_get_seat(device);
struct ei_device *d;
/* If the device was still pending, the client held the only ref to
* it. Any other case, the device should've been removed from the
* seat by the time we get here. */
list_for_each(d, &seat->devices_pending, link) {
if (device == d) {
list_remove(&d->link);
break;
}
}
ei_keymap_unref(device->keymap);
free(device->name);
}
_public_
OBJECT_IMPLEMENT_REF(ei_device);
_public_
OBJECT_IMPLEMENT_UNREF(ei_device);
#define _cleanup_ei_device_ _cleanup_(ei_device_cleanup)
static
OBJECT_IMPLEMENT_CREATE(ei_device);
static
OBJECT_IMPLEMENT_PARENT(ei_device, ei_seat);
_public_
OBJECT_IMPLEMENT_GETTER(ei_device, name, const char *);
_public_
OBJECT_IMPLEMENT_GETTER(ei_device, user_data, void *);
_public_
OBJECT_IMPLEMENT_SETTER(ei_device, user_data, void *);
_public_ struct ei_seat *
ei_device_get_seat(struct ei_device *device)
{
return ei_device_parent(device);
}
_public_ struct ei*
ei_device_get_context(struct ei_device *device)
{
assert(device);
return ei_seat_get_context(ei_device_get_seat(device));
}
_public_ struct ei_device *
ei_device_new(struct ei_seat *seat)
{
/* device IDs are managed by the client, the server merely accepts
* them and fails where they're being reused. */
static uint32_t deviceid = 0;
struct ei_device *device = ei_device_create(&seat->object);
device->capabilities = 0;
device->id = seat->id | ++deviceid;
device->state = EI_DEVICE_STATE_NEW;
device->name = xaprintf("unnamed device %d", device->id);
list_append(&seat->devices_pending, &device->link); /* not a ref! */
return device;
}
_public_ void
ei_device_configure_name(struct ei_device *device, const char *name)
{
if (device->state != EI_DEVICE_STATE_NEW)
return;
free(device->name);
device->name = xstrdup(name);
}
_public_ bool
ei_device_configure_capability(struct ei_device *device,
enum ei_device_capability cap)
{
if (device->state != EI_DEVICE_STATE_NEW)
return false;
switch (cap) {
case EI_DEVICE_CAP_POINTER:
case EI_DEVICE_CAP_POINTER_ABSOLUTE:
case EI_DEVICE_CAP_KEYBOARD:
case EI_DEVICE_CAP_TOUCH:
device->capabilities |= bit(cap);
return true;
}
return false;
}
_public_ void
ei_device_pointer_configure_range(struct ei_device *device,
uint32_t width,
uint32_t height)
{
if (device->state != EI_DEVICE_STATE_NEW ||
!flag_is_set(device->capabilities, EI_DEVICE_CAP_POINTER_ABSOLUTE))
return;
if (width == 0 || height == 0)
return;
device->abs.dim.width = width;
device->abs.dim.height = height;
}
_public_ void
ei_device_touch_configure_range(struct ei_device *device,
uint32_t width,
uint32_t height)
{
if (device->state != EI_DEVICE_STATE_NEW ||
!flag_is_set(device->capabilities, EI_DEVICE_CAP_TOUCH))
return;
if (width == 0 || height == 0)
return;
device->touch.dim.width = width;
device->touch.dim.height = height;
}
_public_
OBJECT_IMPLEMENT_REF(ei_keymap);
_public_
OBJECT_IMPLEMENT_UNREF(ei_keymap);
#define _cleanup_ei_keymap_ _cleanup_(ei_keymap_cleanup)
_public_
OBJECT_IMPLEMENT_GETTER(ei_keymap, type, enum ei_keymap_type);
_public_
OBJECT_IMPLEMENT_GETTER(ei_keymap, source, enum ei_keymap_source);
_public_
OBJECT_IMPLEMENT_GETTER(ei_keymap, fd, int);
_public_
OBJECT_IMPLEMENT_GETTER(ei_keymap, size, size_t);
_public_
OBJECT_IMPLEMENT_GETTER(ei_keymap, device, struct ei_device *);
static void
ei_keymap_destroy(struct ei_keymap *keymap)
{
xclose(keymap->fd);
}
static
OBJECT_IMPLEMENT_CREATE(ei_keymap);
_public_ struct ei_keymap *
ei_device_keyboard_get_keymap(struct ei_device *device)
{
return device->keymap;
}
_public_ void
ei_device_keyboard_configure_keymap(struct ei_device *device,
struct ei_keymap *keymap)
{
if (!ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD)) {
log_bug_client(ei_device_get_context(device),
"%s: Device is not a keyboard\n", __func__);
return;
}
if (device->state != EI_DEVICE_STATE_NEW) {
log_bug_client(ei_device_get_context(device),
"%s: Device has already been added\n", __func__);
return;
}
/* Can only call this once */
if (device->keymap) {
log_bug_client(ei_device_get_context(device),
"%s: Device already has a keymap\n", __func__);
return;
}
if (!keymap) {
log_bug_client(ei_device_get_context(device),
"%s: Keymap has already been used\n", __func__);
return;
}
/* Reject any previously used keymaps */
if (keymap->assigned) {
log_bug_client(ei_device_get_context(device),
"%s: Keymap has already been used\n", __func__);
return;
}
keymap->assigned = true;
keymap->source = EI_KEYMAP_SOURCE_CLIENT;
keymap->device = device;
device->keymap = ei_keymap_ref(keymap);
}
_public_ struct ei_keymap *
ei_keymap_new(enum ei_keymap_type type, int fd, size_t size)
{
_cleanup_ei_keymap_ struct ei_keymap *keymap = ei_keymap_create(NULL);
switch (type) {
case EI_KEYMAP_TYPE_XKB:
break;
default:
return NULL;
}
if (fd < 0 || size == 0)
return NULL;
int newfd = dup(fd);
if (newfd < 0)
return NULL;
keymap->fd = newfd;
keymap->type = type;
keymap->size = size;
return ei_keymap_ref(keymap);
}
void
ei_device_set_keymap(struct ei_device *device,
enum ei_keymap_type type,
int keymap_fd, size_t size)
{
device->keymap = ei_keymap_unref(device->keymap);
if (!type)
return;
_cleanup_ei_keymap_ struct ei_keymap *keymap = ei_keymap_new(type, keymap_fd, size);
if (!keymap) {
log_bug(ei_device_get_context(device),
"Failed to apply server-requested keymap");
return; /* FIXME: ei_device_remove() here */
}
keymap->device = device;
keymap->source = EI_KEYMAP_SOURCE_SERVER;
device->keymap = ei_keymap_ref(keymap);
}
_public_ void
ei_device_add(struct ei_device *device)
{
if (ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE) &&
(device->abs.dim.width == 0 || device->abs.dim.height == 0))
log_bug_client(ei_device_get_context(device),
"%s: device %s is missing an abs pointer range\n",
__func__, device->name);
ei_add_device(device);
ei_device_set_state(device, EI_DEVICE_STATE_CONNECTING);
}
_public_ void
ei_device_remove(struct ei_device *device)
{
switch (device->state) {
case EI_DEVICE_STATE_NEW:
case EI_DEVICE_STATE_REMOVED:
return;
default:
break;
}
ei_device_set_state(device, EI_DEVICE_STATE_REMOVED);
ei_remove_device(device);
}
void
ei_device_resumed(struct ei_device *device)
{
ei_device_set_state(device, EI_DEVICE_STATE_RESUMED);
}
void
ei_device_suspended(struct ei_device *device)
{
ei_device_set_state(device, EI_DEVICE_STATE_SUSPENDED);
}
void
ei_device_added(struct ei_device *device)
{
ei_device_suspended(device);
}
void
ei_device_set_name(struct ei_device *device, const char *name)
{
free(device->name);
device->name = xstrdup(name);
}
void
ei_device_set_capabilities(struct ei_device *device,
uint32_t capabilities)
{
device->capabilities = capabilities;
}
_public_ bool
ei_device_has_capability(struct ei_device *device,
enum ei_device_capability cap)
{
switch (cap) {
case EI_DEVICE_CAP_POINTER:
case EI_DEVICE_CAP_POINTER_ABSOLUTE:
case EI_DEVICE_CAP_KEYBOARD:
case EI_DEVICE_CAP_TOUCH:
return flag_is_set(device->capabilities, cap);
}
return false;
}
_public_ uint32_t
ei_device_pointer_get_width(struct ei_device *device)
{
return device->abs.dim.width;
}
_public_ uint32_t
ei_device_pointer_get_height(struct ei_device *device)
{
return device->abs.dim.height;
}
_public_ uint32_t
ei_device_touch_get_width(struct ei_device *device)
{
return device->touch.dim.width;
}
_public_ uint32_t
ei_device_touch_get_height(struct ei_device *device)
{
return device->touch.dim.height;
}
_public_ void
ei_device_pointer_motion(struct ei_device *device,
double x, double y)
{
if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER)) {
log_bug_client(ei_device_get_context(device),
"%s: device is not a pointer\n", __func__);
return;
}
if (device->state != EI_DEVICE_STATE_RESUMED)
return;
ei_pointer_rel(device, x, y);
}
_public_ void
ei_device_pointer_motion_absolute(struct ei_device *device,
double x, double y)
{
if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE)) {
log_bug_client(ei_device_get_context(device),
"%s: device is not an absolute pointer\n", __func__);
return;
}
if (device->state != EI_DEVICE_STATE_RESUMED)
return;
if (x < 0 || x >= device->abs.dim.width ||
y < 0 || y >= device->abs.dim.height)
return;
ei_pointer_abs(device, x, y);
}
_public_ void
ei_device_pointer_button(struct ei_device *device,
uint32_t button, bool is_press)
{
if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER)) {
log_bug_client(ei_device_get_context(device),
"%s: device is not a pointer\n", __func__);
return;
}
if (device->state != EI_DEVICE_STATE_RESUMED)
return;
ei_pointer_button(device, button, is_press);
}
_public_ void
ei_device_keyboard_key(struct ei_device *device,
uint32_t key, bool is_press)
{
if (!ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD)) {
log_bug_client(ei_device_get_context(device),
"%s: device is not a keyboard\n", __func__);
return;
}
if (device->state != EI_DEVICE_STATE_RESUMED)
return;
ei_keyboard_key(device, key, is_press);
}
_public_
OBJECT_IMPLEMENT_REF(ei_touch);
_public_
OBJECT_IMPLEMENT_UNREF(ei_touch);
_public_
OBJECT_IMPLEMENT_GETTER(ei_touch, device, struct ei_device*);
_public_
OBJECT_IMPLEMENT_GETTER(ei_touch, user_data, void *);
_public_
OBJECT_IMPLEMENT_SETTER(ei_touch, user_data, void *);
static void
ei_touch_destroy(struct ei_touch *touch)
{
ei_touch_up(touch);
ei_device_unref(touch->device);
}
static
OBJECT_IMPLEMENT_CREATE(ei_touch);
_public_ struct ei_touch *
ei_device_touch_new(struct ei_device *device)
{
static uint32_t tracking_id = 0;
/* Not using the device as parent object because we need a ref
* to it */
struct ei_touch *touch = ei_touch_create(NULL);
touch->device = ei_device_ref(device);
touch->state = TOUCH_IS_NEW;
touch->tracking_id = ++tracking_id;
return touch;
}
_public_ void
ei_touch_down(struct ei_touch *touch, double x, double y)
{
if (touch->state != TOUCH_IS_NEW) {
struct ei_device *device = ei_touch_get_device(touch);
log_bug_client(ei_device_get_context(device),
"%s: device is not a keyboard\n", __func__);
return;
}
if (x < 0 || x >= touch->device->touch.dim.width ||
y < 0 || y >= touch->device->touch.dim.height) {
struct ei_device *device = ei_touch_get_device(touch);
log_bug_client(ei_device_get_context(device),
"%s: invalid x/y coordinates\n", __func__);
touch->state = TOUCH_IS_UP;
return;
}
touch->state = TOUCH_IS_DOWN;
ei_touch_touch_down(touch->device, touch->tracking_id, x, y);
}
_public_ void
ei_touch_motion(struct ei_touch *touch, double x, double y)
{
if (touch->state != TOUCH_IS_DOWN)
return;
if (x < 0 || x >= touch->device->touch.dim.width ||
y < 0 || y >= touch->device->touch.dim.height) {
struct ei_device *device = ei_touch_get_device(touch);
log_bug_client(ei_device_get_context(device),
"%s: invalid x/y coordinates\n", __func__);
ei_touch_up(touch);
return;
}
ei_touch_touch_motion(touch->device, touch->tracking_id, x, y);
}
_public_ void
ei_touch_up(struct ei_touch *touch)
{
if (touch->state != TOUCH_IS_DOWN) {
struct ei_device *device = ei_touch_get_device(touch);
log_bug_client(ei_device_get_context(device),
"%s: touch is not currently down\n", __func__);
return;
}
touch->state = TOUCH_IS_UP;
ei_touch_touch_up(touch->device, touch->tracking_id);
}
#ifdef _enable_tests_
#include "src/util-munit.h"
#include "src/util-memfile.h"
#define FAKE_SEAT(_seat) \
struct ei_seat seat_##__LINE__ = { \
.id = __LINE__ << 16, \
.capabilities = ~0, \
.name = "default", \
}; \
list_init(&seat_##__LINE__.devices); \
list_init(&seat_##__LINE__.devices_pending); \
struct ei_seat *_seat = &seat_ ## __LINE__;
MUNIT_TEST(test_device_new)
{
FAKE_SEAT(seat);
struct ei_device *d = ei_device_new(seat);
munit_assert_int(d->id, >, 0);
munit_assert_int(d->capabilities, ==, 0);
munit_assert_int(d->state, ==, EI_DEVICE_STATE_NEW);
struct ei_device *unrefd = ei_device_unref(d);
munit_assert_ptr_null(unrefd);
return MUNIT_OK;
}
MUNIT_TEST(test_device_ids)
{
FAKE_SEAT(seat);
_cleanup_ei_device_ struct ei_device *d1 = ei_device_new(seat);
_cleanup_ei_device_ struct ei_device *d2 = ei_device_new(seat);
_cleanup_ei_device_ struct ei_device *d3 = ei_device_new(seat);
munit_assert_int(d1->id, <, d2->id);
munit_assert_int(d1->id, <, d3->id);
munit_assert_int(d2->id, <, d3->id);
return MUNIT_OK;
}
MUNIT_TEST(test_device_ref_unref)
{
FAKE_SEAT(seat);
struct ei_device *d = ei_device_new(seat);
munit_assert_int(d->object.refcount, ==, 1);
struct ei_device *refd = ei_device_ref(d);
munit_assert_ptr_equal(d, refd);
munit_assert_int(d->object.refcount, ==, 2);
struct ei_device *unrefd = ei_device_unref(d);
munit_assert_ptr_null(unrefd);
munit_assert_int(d->object.refcount, ==, 1);
unrefd = ei_device_unref(d);
munit_assert_ptr_null(unrefd);
return MUNIT_OK;
}
MUNIT_TEST(test_device_cap)
{
FAKE_SEAT(seat);
_cleanup_ei_device_ struct ei_device *d = ei_device_new(seat);
munit_assert(ei_device_configure_capability(d, EI_DEVICE_CAP_POINTER));
/* twice is fine */
munit_assert(ei_device_configure_capability(d, EI_DEVICE_CAP_POINTER));
munit_assert(ei_device_has_capability(d, EI_DEVICE_CAP_POINTER));
munit_assert(ei_device_configure_capability(d, EI_DEVICE_CAP_POINTER_ABSOLUTE));
munit_assert(ei_device_has_capability(d, EI_DEVICE_CAP_POINTER_ABSOLUTE));
munit_assert(ei_device_configure_capability(d, EI_DEVICE_CAP_KEYBOARD));
munit_assert(ei_device_has_capability(d, EI_DEVICE_CAP_KEYBOARD));
munit_assert(ei_device_configure_capability(d, EI_DEVICE_CAP_TOUCH));
munit_assert(ei_device_has_capability(d, EI_DEVICE_CAP_TOUCH));
/* Invalid caps */
munit_assert(!ei_device_configure_capability(d, 0));
munit_assert(!ei_device_has_capability(d, 0));
munit_assert(!ei_device_configure_capability(d, EI_DEVICE_CAP_TOUCH + 1));
munit_assert(!ei_device_has_capability(d, EI_DEVICE_CAP_TOUCH + 1));
return MUNIT_OK;
}
MUNIT_TEST(test_device_get_seat)
{
FAKE_SEAT(seat);
_cleanup_ei_device_ struct ei_device *d = ei_device_new(seat);
munit_assert_ptr_equal(d->object.parent, seat);
munit_assert_ptr_equal(ei_device_get_seat(d), seat);
return MUNIT_OK;
}
MUNIT_TEST(test_device_pointer_ranges)
{
FAKE_SEAT(seat);
_cleanup_ei_device_ struct ei_device *d = ei_device_new(seat);
/* Missing the cap */
ei_device_pointer_configure_range(d, 1920, 1200);
munit_assert_int(ei_device_pointer_get_width(d), ==, 0);
munit_assert_int(ei_device_pointer_get_height(d), ==, 0);
munit_assert(ei_device_configure_capability(d, EI_DEVICE_CAP_POINTER_ABSOLUTE));
ei_device_pointer_configure_range(d, 1024, 768);
munit_assert_int(ei_device_pointer_get_width(d), ==, 1024);
munit_assert_int(ei_device_pointer_get_height(d), ==, 768);
/* Twice is fine before adding the device */
ei_device_pointer_configure_range(d, 640, 480);
munit_assert_int(ei_device_pointer_get_width(d), ==, 640);
munit_assert_int(ei_device_pointer_get_height(d), ==, 480);
/* But zero is silently rejected */
ei_device_pointer_configure_range(d, 0, 480);
munit_assert_int(ei_device_pointer_get_width(d), ==, 640);
munit_assert_int(ei_device_pointer_get_height(d), ==, 480);
ei_device_pointer_configure_range(d, 640, 0);
munit_assert_int(ei_device_pointer_get_width(d), ==, 640);
munit_assert_int(ei_device_pointer_get_height(d), ==, 480);
return MUNIT_OK;
}
MUNIT_TEST(test_device_touch_ranges)
{
FAKE_SEAT(seat);
_cleanup_ei_device_ struct ei_device *d = ei_device_new(seat);
/* Missing the cap */
ei_device_touch_configure_range(d, 1920, 1200);
munit_assert_int(ei_device_touch_get_width(d), ==, 0);
munit_assert_int(ei_device_touch_get_height(d), ==, 0);
munit_assert(ei_device_configure_capability(d, EI_DEVICE_CAP_TOUCH));
ei_device_touch_configure_range(d, 1024, 768);
munit_assert_int(ei_device_touch_get_width(d), ==, 1024);
munit_assert_int(ei_device_touch_get_height(d), ==, 768);
/* Twice is fine before adding the device */
ei_device_touch_configure_range(d, 640, 480);
munit_assert_int(ei_device_touch_get_width(d), ==, 640);
munit_assert_int(ei_device_touch_get_height(d), ==, 480);
/* But zero is silently rejected */
ei_device_touch_configure_range(d, 0, 480);
munit_assert_int(ei_device_touch_get_width(d), ==, 640);
munit_assert_int(ei_device_touch_get_height(d), ==, 480);
ei_device_touch_configure_range(d, 640, 0);
munit_assert_int(ei_device_touch_get_width(d), ==, 640);
munit_assert_int(ei_device_touch_get_height(d), ==, 480);
return MUNIT_OK;
}
#endif