libei/src/libei-device.c
Peter Hutterer 3abf13742a Restructure keymap handling to server-side only
This keeps most of the current structure but gets rid of client-side
keymaps (which have been broken since the server-side devices anyway).
The new approach: clients get a keymap (or NULL) from the server, if
they don't like it they will have to do transformation on their side.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
2021-08-11 09:29:55 +10:00

518 lines
12 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_SUSPENDED);
CASE_RETURN_STRING(EI_DEVICE_STATE_RESUMED);
CASE_RETURN_STRING(EI_DEVICE_STATE_REMOVED_FROM_CLIENT);
CASE_RETURN_STRING(EI_DEVICE_STATE_REMOVED_FROM_SERVER);
CASE_RETURN_STRING(EI_DEVICE_STATE_DEAD);
}
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_region *region;
assert(device->state == EI_DEVICE_STATE_DEAD);
list_for_each_safe(region, &device->regions, link)
ei_region_unref(region);
list_remove(&device->link);
ei_keymap_unref(device->keymap);
ei_seat_unref(seat);
free(device->name);
}
_public_
OBJECT_IMPLEMENT_REF(ei_device);
_public_
OBJECT_IMPLEMENT_UNREF(ei_device);
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));
}
struct ei_device *
ei_device_new(struct ei_seat *seat, uint32_t deviceid)
{
struct ei_device *device = ei_device_create(&seat->object);
device->capabilities = 0;
device->id = deviceid;
device->state = EI_DEVICE_STATE_NEW;
device->name = xaprintf("unnamed device %d", device->id);
list_init(&device->regions);
/* We have a ref to the seat to make sure our seat doesn't get
* destroyed while a ref to the device is still alive.
* And the seat has a ref to the device in the seat->devices list.
* dropped when the device is removed.
*/
ei_seat_ref(seat);
/* this list "owns" the ref for this device */
ei_device_ref(device);
list_append(&seat->devices, &device->link);
return device;
}
void
ei_device_done(struct ei_device *device)
{
ei_device_suspended(device);
}
void
ei_device_add_region(struct ei_device *device, struct ei_region *region)
{
if (device->state != EI_DEVICE_STATE_NEW)
return;
ei_region_ref(region);
list_append(&device->regions, &region->link);
}
_public_
OBJECT_IMPLEMENT_REF(ei_keymap);
_public_
OBJECT_IMPLEMENT_UNREF(ei_keymap);
_public_
OBJECT_IMPLEMENT_GETTER(ei_keymap, type, enum ei_keymap_type);
_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;
}
static struct ei_keymap *
ei_keymap_new(enum ei_keymap_type type, int fd, size_t size)
{
_unref_(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;
_unref_(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;
device->keymap = ei_keymap_ref(keymap);
}
_public_ void
ei_device_close(struct ei_device *device)
{
switch (device->state) {
case EI_DEVICE_STATE_NEW:
case EI_DEVICE_STATE_DEAD:
case EI_DEVICE_STATE_REMOVED_FROM_CLIENT:
case EI_DEVICE_STATE_REMOVED_FROM_SERVER:
break;
case EI_DEVICE_STATE_SUSPENDED:
case EI_DEVICE_STATE_RESUMED:
ei_device_set_state(device, EI_DEVICE_STATE_REMOVED_FROM_CLIENT);
ei_send_close_device(device);
break;
}
}
void
ei_device_removed_by_server(struct ei_device *device)
{
struct ei_seat *seat = ei_device_get_seat(device);
switch (device->state) {
case EI_DEVICE_STATE_NEW:
case EI_DEVICE_STATE_DEAD:
case EI_DEVICE_STATE_REMOVED_FROM_SERVER:
break;
case EI_DEVICE_STATE_REMOVED_FROM_CLIENT:
case EI_DEVICE_STATE_SUSPENDED:
case EI_DEVICE_STATE_RESUMED:
ei_queue_device_removed_event(device);
ei_device_set_state(device, EI_DEVICE_STATE_DEAD);
/* Device is dead now. Move it from the devices list to
* removed and drop the seat's ref to the device.
* This should be the last ref to the device that libei has
* (not counting any queued events). Device is kept alive by
* any client refs but once those drop, the device can be
* destroyed.
*/
list_remove(&device->link);
list_append(&seat->devices_removed, &device->link);
ei_device_unref(device);
break;
}
}
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)
{
}
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_ struct ei_region *
ei_device_get_region(struct ei_device *device, size_t index)
{
return list_nth_entry(struct ei_region, &device->regions, link, index);
}
_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_send_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;
struct ei_region *r;
list_for_each(r, &device->regions, link) {
if (!ei_region_contains(r, x, y)) {
return;
}
}
ei_send_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_send_pointer_button(device, button, is_press);
}
_public_ void
ei_device_pointer_scroll(struct ei_device *device,
double x, double y)
{
if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER) &&
!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE)) {
log_bug_client(ei_device_get_context(device),
"%s: device is not a (absolute) pointer\n", __func__);
}
if (device->state != EI_DEVICE_STATE_RESUMED)
return;
ei_send_pointer_scroll(device, x, y);
}
_public_ void
ei_device_pointer_scroll_discrete(struct ei_device *device,
int32_t x, int32_t y)
{
if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER) &&
!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE)) {
log_bug_client(ei_device_get_context(device),
"%s: device is not a (absolute) pointer\n", __func__);
}
if (device->state != EI_DEVICE_STATE_RESUMED)
return;
ei_send_pointer_scroll_discrete(device, x, y);
}
_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_send_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)
{
struct ei_device *device = ei_touch_get_device(touch);
if (touch->state != TOUCH_IS_NEW) {
log_bug_client(ei_device_get_context(device),
"%s: device is not a keyboard\n", __func__);
return;
}
struct ei_region *r;
list_for_each(r, &device->regions, link) {
if (!ei_region_contains(r, x, y)) {
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_send_touch_down(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;
struct ei_device *device = ei_touch_get_device(touch);
struct ei_region *r;
list_for_each(r, &device->regions, link) {
if (!ei_region_contains(r, x, y)) {
log_bug_client(ei_device_get_context(device),
"%s: invalid x/y coordinates\n", __func__);
ei_touch_up(touch);
return;
}
}
ei_send_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_send_touch_up(touch->device, touch->tracking_id);
}