Add DeviceRegions to replace the pointer/touch ranges

This is required for supporting synergy/barrier and similar clients.
Replacing the touch and pointer range we now have server-defined
rectangular regions that specify the active zones for this device.

For example, a dual-monitor EIS server would create two touch devices
with one region each for the respective monitors - libei-generated
touches would thus fall on the right area of the monitor. Or just one
device with one region if the second screen should be inaccessible.

A relative device may have multiple regions since it can reach all
screens in the layout.

This leaks the screen layout to libei but that is necessary for the
functionality to work. A libei client may need to control devices
through absolute coordinates and it needs to know where screen
transitions from one to the next screen happen:

  +-----------++----------------+
  |           ||                |
  |          B||Q               |
  |           |+----------------+
  |           |
  |          A|P
  +-----------+

In the above example, position P is unreachable and a client that
controls input on both screens must know that it cannot transition from
A to P but it can transition from B to Q.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
Peter Hutterer 2021-07-21 14:52:04 +10:00
parent 12b04fdeb0
commit 56b82c2b8f
19 changed files with 574 additions and 226 deletions

View file

@ -51,6 +51,7 @@ src_libei = [
'src/libei-fd.c',
'src/libei-proto.h',
'src/libei-proto.c',
'src/libei-region.c',
'src/libei-stubs.c',
proto_headers,
]
@ -100,6 +101,7 @@ src_libeis = [
'src/libeis-device.c',
'src/libeis-event.c',
'src/libeis-log.c',
'src/libeis-region.c',
'src/libeis-seat.c',
'src/libeis-socket.c',
'src/libeis-fd.c',

View file

@ -175,10 +175,14 @@ message DeviceAdded {
uint32 keymap_size = 5;
string name = 6;
uint32 seatid = 7;
uint32 pointer_width = 8;
uint32 pointer_height = 9;
uint32 touch_width = 10;
uint32 touch_height = 11;
}
message DeviceRegion {
uint32 deviceid = 1;
uint32 offset_x = 2;
uint32 offset_y = 3;
uint32 width = 4;
uint32 height = 5;
}
message DeviceAddedDone {
@ -204,6 +208,7 @@ message ServerMessage {
SeatAdded seat_added = 4;
SeatRemoved seat_removed = 5;
DeviceAdded device_added = 6;
DeviceRegion device_region = 7;
DeviceAddedDone device_added_done = 8;
DeviceRemoved device_removed = 9;
DeviceResumed device_resumed = 10;

View file

@ -62,8 +62,13 @@ 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);
@ -108,6 +113,7 @@ ei_device_new(struct ei_seat *seat, uint32_t deviceid)
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.
@ -126,7 +132,17 @@ ei_device_new(struct ei_seat *seat, uint32_t deviceid)
void
ei_device_done(struct ei_device *device)
{
ei_device_set_state(device, EI_DEVICE_STATE_SUSPENDED);
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_
@ -305,7 +321,6 @@ ei_device_suspended(struct ei_device *device)
void
ei_device_added(struct ei_device *device)
{
ei_device_suspended(device);
}
void
@ -322,20 +337,6 @@ ei_device_set_capabilities(struct ei_device *device,
device->capabilities = capabilities;
}
void
ei_device_set_pointer_range(struct ei_device *device, uint32_t w, uint32_t h)
{
device->abs.dim.width = w;
device->abs.dim.height = h;
}
void
ei_device_set_touch_range(struct ei_device *device, uint32_t w, uint32_t h)
{
device->touch.dim.width = w;
device->touch.dim.height = h;
}
_public_ bool
ei_device_has_capability(struct ei_device *device,
enum ei_device_capability cap)
@ -350,28 +351,10 @@ ei_device_has_capability(struct ei_device *device,
return false;
}
_public_ uint32_t
ei_device_pointer_get_width(struct ei_device *device)
_public_ struct ei_region *
ei_device_get_region(struct ei_device *device, size_t index)
{
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;
return list_nth_entry(struct ei_region, &device->regions, link, index);
}
_public_ void
@ -403,9 +386,12 @@ ei_device_pointer_motion_absolute(struct ei_device *device,
if (device->state != EI_DEVICE_STATE_RESUMED)
return;
if (x < 0 || x >= device->abs.dim.width ||
y < 0 || y >= device->abs.dim.height)
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);
}
@ -513,25 +499,27 @@ ei_device_touch_new(struct ei_device *device)
_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) {
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;
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(touch->device, touch->tracking_id, x, y);
ei_send_touch_down(device, touch->tracking_id, x, y);
}
_public_ void
@ -540,13 +528,15 @@ 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;
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);

View file

@ -104,6 +104,14 @@ enum ei_device_state {
EI_DEVICE_STATE_DEAD,
};
struct ei_region {
struct object object;
void *user_data;
struct list link;
uint32_t x, y;
uint32_t width, height;
};
struct ei_device {
struct object object;
void *user_data;
@ -113,13 +121,7 @@ struct ei_device {
uint32_t capabilities;
char *name;
struct {
struct dimensions dim;
} abs;
struct {
struct dimensions dim;
} touch;
struct list regions;
struct ei_keymap *keymap;
};
@ -197,6 +199,9 @@ ei_queue_seat_removed_event(struct ei_seat *seat);
struct ei_device *
ei_device_new(struct ei_seat *seat, uint32_t deviceid);
void
ei_device_add_region(struct ei_device *device, struct ei_region *r);
void
ei_device_done(struct ei_device *device);
@ -254,10 +259,6 @@ ei_device_set_seat(struct ei_device *device, const char *seat);
void
ei_device_set_capabilities(struct ei_device *device,
uint32_t capabilities);
void
ei_device_set_pointer_range(struct ei_device *device, uint32_t w, uint32_t h);
void
ei_device_set_touch_range(struct ei_device *device, uint32_t w, uint32_t h);
void
ei_device_set_keymap(struct ei_device *device,
@ -265,6 +266,18 @@ ei_device_set_keymap(struct ei_device *device,
int keymap_fd,
size_t size);
struct ei_region *
ei_region_new(void);
void
ei_region_set_size(struct ei_region *region, uint32_t w, uint32_t h);
void
ei_region_set_offset(struct ei_region *region, uint32_t x, uint32_t y);
bool
ei_region_contains(struct ei_region *region, double x, double y);
_printf_(6, 7) void
ei_log_msg(struct ei *ei,
enum ei_log_priority priority,

View file

@ -113,10 +113,6 @@ ei_proto_parse_message(struct brei_message *bmsg, size_t *consumed)
.device_added.keymap_from_server = a->keymap_from_server,
.device_added.keymap_size = a->keymap_size,
.device_added.seatid = a->seatid,
.device_added.pointer_width = a->pointer_width,
.device_added.pointer_height = a->pointer_height,
.device_added.touch_width = a->touch_width,
.device_added.touch_height = a->touch_height,
};
if (a->keymap_type && a->keymap_from_server)
msg->device_added.keymap_fd = brei_message_take_fd(bmsg);
@ -131,6 +127,19 @@ ei_proto_parse_message(struct brei_message *bmsg, size_t *consumed)
};
}
break;
case SERVER_MESSAGE__MSG_DEVICE_REGION:
{
DeviceRegion *r = proto->device_region;
*msg = (struct message) {
.type = MESSAGE_DEVICE_REGION,
.device_region.deviceid = r->deviceid,
.device_region.x = r->offset_x,
.device_region.y = r->offset_y,
.device_region.w = r->width,
.device_region.h = r->height,
};
}
break;
case SERVER_MESSAGE__MSG_DEVICE_REMOVED:
{
DeviceRemoved *r = proto->device_removed;

View file

@ -39,6 +39,7 @@ enum message_type {
MESSAGE_SEAT_REMOVED,
MESSAGE_DEVICE_ADDED,
MESSAGE_DEVICE_ADDED_DONE,
MESSAGE_DEVICE_REGION,
MESSAGE_DEVICE_REMOVED,
MESSAGE_DEVICE_RESUMED,
MESSAGE_DEVICE_SUSPENDED,
@ -54,6 +55,7 @@ message_type_to_string(enum message_type type)
CASE_RETURN_STRING(MESSAGE_SEAT_REMOVED);
CASE_RETURN_STRING(MESSAGE_DEVICE_ADDED);
CASE_RETURN_STRING(MESSAGE_DEVICE_ADDED_DONE);
CASE_RETURN_STRING(MESSAGE_DEVICE_REGION);
CASE_RETURN_STRING(MESSAGE_DEVICE_REMOVED);
CASE_RETURN_STRING(MESSAGE_DEVICE_RESUMED);
CASE_RETURN_STRING(MESSAGE_DEVICE_SUSPENDED);
@ -95,6 +97,13 @@ struct message {
struct message_device_added_done {
uint32_t deviceid;
} device_added_done;
struct message_device_region {
uint32_t deviceid;
uint32_t x;
uint32_t y;
uint32_t w;
uint32_t h;
} device_region;
struct message_device_removed {
uint32_t deviceid;
} device_removed;

83
src/libei-region.c Normal file
View file

@ -0,0 +1,83 @@
/*
* 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 "libei-private.h"
static void
ei_region_destroy(struct ei_region *region)
{
list_remove(&region->link);
}
_public_
OBJECT_IMPLEMENT_REF(ei_region);
_public_
OBJECT_IMPLEMENT_UNREF(ei_region);
static
OBJECT_IMPLEMENT_CREATE(ei_region);
_public_
OBJECT_IMPLEMENT_GETTER(ei_region, user_data, void *);
_public_
OBJECT_IMPLEMENT_SETTER(ei_region, user_data, void *);
_public_
OBJECT_IMPLEMENT_GETTER(ei_region, x, uint32_t);
_public_
OBJECT_IMPLEMENT_GETTER(ei_region, y, uint32_t);
_public_
OBJECT_IMPLEMENT_GETTER(ei_region, width, uint32_t);
_public_
OBJECT_IMPLEMENT_GETTER(ei_region, height, uint32_t);
struct ei_region *
ei_region_new(void)
{
struct ei_region *region = ei_region_create(NULL);
list_init(&region->link);
return region;
}
void
ei_region_set_offset(struct ei_region *region, uint32_t x, uint32_t y)
{
region->x = x;
region->y = y;
}
void
ei_region_set_size(struct ei_region *region, uint32_t w, uint32_t h)
{
region->width = w;
region->height = h;
}
bool
ei_region_contains(struct ei_region *r, double x, double y)
{
return (x >= r->x && x < r->x + r->width &&
y >= r->y && y < r->y + r->height);
}

View file

@ -148,6 +148,7 @@ _public_
OBJECT_IMPLEMENT_GETTER(ei, user_data, void *);
DEFINE_UNREF_CLEANUP_FUNC(ei_device);
DEFINE_UNREF_CLEANUP_FUNC(ei_region);
_public_ struct ei *
ei_new(void *user_data)
@ -549,8 +550,6 @@ handle_msg_device_added(struct ei *ei, uint32_t deviceid,
_unref_(ei_device) *device = ei_device_new(seat, deviceid);
ei_device_set_name(device, name);
ei_device_set_capabilities(device, capabilities);
ei_device_set_pointer_range(device, pointer_width, pointer_height);
ei_device_set_touch_range(device, touch_width, touch_height);
if (keymap_from_server)
ei_device_set_keymap(device, keymap_type,
keymap_fd, keymap_sz);
@ -595,6 +594,27 @@ handle_msg_device_added_done(struct ei *ei, uint32_t deviceid)
return 0;
}
static int
handle_msg_device_region(struct ei *ei, uint32_t deviceid,
uint32_t x, uint32_t y,
uint32_t w, uint32_t h)
{
log_debug(ei, "Adding device region for %#x\n", deviceid);
struct ei_device *device = ei_find_device(ei, deviceid);
if (!device)
return 0;
_unref_(ei_region) *r = ei_region_new();
ei_region_set_offset(r, x, y);
ei_region_set_size(r, w, h);
ei_device_add_region(device, r);
return 0;
}
static int
handle_msg_device_removed(struct ei *ei, uint32_t deviceid)
{
@ -792,6 +812,7 @@ connection_new_handle_msg(struct ei *ei, struct message *msg)
case MESSAGE_SEAT_REMOVED:
case MESSAGE_DEVICE_ADDED:
case MESSAGE_DEVICE_ADDED_DONE:
case MESSAGE_DEVICE_REGION:
case MESSAGE_DEVICE_REMOVED:
case MESSAGE_DEVICE_RESUMED:
case MESSAGE_DEVICE_SUSPENDED:
@ -819,6 +840,7 @@ connection_connecting_handle_msg(struct ei *ei, struct message *msg)
case MESSAGE_SEAT_REMOVED:
case MESSAGE_DEVICE_ADDED:
case MESSAGE_DEVICE_ADDED_DONE:
case MESSAGE_DEVICE_REGION:
case MESSAGE_DEVICE_REMOVED:
case MESSAGE_DEVICE_RESUMED:
case MESSAGE_DEVICE_SUSPENDED:
@ -869,6 +891,11 @@ connection_connected_handle_msg(struct ei *ei, struct message *msg)
case MESSAGE_DEVICE_ADDED_DONE:
rc = handle_msg_device_added_done(ei, msg->device_added_done.deviceid);
break;
case MESSAGE_DEVICE_REGION:
rc = handle_msg_device_region(ei, msg->device_region.deviceid,
msg->device_region.x, msg->device_region.y,
msg->device_region.w, msg->device_region.h);
break;
case MESSAGE_DEVICE_REMOVED:
rc = handle_msg_device_removed(ei, msg->device_removed.deviceid);
break;

View file

@ -98,6 +98,27 @@ struct ei_event;
*/
struct ei_keymap;
/**
* @struct ei_region
*
* A rectangular region, defined by an x/y offset and a width and a height.
* A region defines the area on an EIS desktop layout that is accessible by
* this device - this region may not be the full area of the desktop.
* Input events may only be sent for points within the regions.
*
* The use of regions is private to the EIS compositor and coordinates may not
* match the size of the actual desktop. For example, a compositor may set a
* 1920x1080 region to represent a 4K monitor and transparently map input
* events into the respective true pixels.
*
* Absolute devices may have different regions, it is up to the libei client
* to send events through the correct device to target the right pixel. For
* example, a dual-head setup my have two absolute devices, the first with a
* zero offset region spanning the first screen, the second with a nonzero
* offset spanning the second screen.
*/
struct ei_region;
/**
* @enum ei_device_capability
*
@ -761,37 +782,48 @@ bool
ei_device_has_capability(struct ei_device *device,
enum ei_device_capability cap);
/**
* Return the requested width for an @ref EI_DEVICE_CAP_POINTER_ABSOLUTE
* device. The width and height is constant after the @ref
* EI_EVENT_DEVICE_ADDED event.
*/
uint32_t
ei_device_pointer_get_width(struct ei_device *device);
/**
* Return the requested height for an @ref EI_DEVICE_CAP_POINTER_ABSOLUTE
* device. The width and height is constant after the @ref
* EI_EVENT_DEVICE_ADDED event.
* Obtain a region from the device. The number of regions is constant for a
* device and the indices of any region remains the same for the lifetime of
* the device.
*
* Regions are shared between all capabilities. Where two capabilities need
* different region, the EIS implementation must create multiple devices with
* individual capabilities and regions.
*
* This function returns the given region or NULL if the index is larger than
* the number of regions available.
*
* This does not increase the refcount of the region. Use ei_region_ref() to
* keep a reference beyond the immediate scope.
*/
uint32_t
ei_device_pointer_get_height(struct ei_device *device);
struct ei_region *
ei_device_get_region(struct ei_device *device, size_t index);
/**
* Return the requested width for an @ref EI_DEVICE_CAP_TOUCH
* device. The width and height is constant after the @ref
* EI_EVENT_DEVICE_ADDED event.
*/
uint32_t
ei_device_touch_get_width(struct ei_device *device);
struct ei_region *
ei_region_ref(struct ei_region *region);
struct ei_region *
ei_region_unref(struct ei_region *region);
void
ei_region_set_user_data(struct ei_region *region, void *user_data);
void *
ei_region_get_user_data(struct ei_region *region);
/**
* Return the requested height for an @ref EI_DEVICE_CAP_TOUCH
* device. The width and height is constant after the @ref
* EI_EVENT_DEVICE_ADDED event.
*/
uint32_t
ei_device_touch_get_height(struct ei_device *device);
ei_region_get_x(struct ei_region *region);
uint32_t
ei_region_get_y(struct ei_region *region);
uint32_t
ei_region_get_width(struct ei_region *region);
uint32_t
ei_region_get_height(struct ei_region *region);
/**
* Return the keymap for this device or `NULL`. The keymap is constant for

View file

@ -102,6 +102,11 @@ client_send_device_added(struct eis_client *client, struct eis_device *device)
{
int rc = eis_proto_send_device_added(client, device);
struct eis_region *r;
list_for_each(r, &device->regions, link) {
rc = eis_proto_send_device_region(client, device, r);
}
if (rc >= 0)
rc = eis_proto_send_device_added_done(client, device);
return rc;

View file

@ -121,6 +121,11 @@ eis_device_keyboard_get_keymap(struct eis_device *device)
static void
eis_device_destroy(struct eis_device *device)
{
struct eis_region *r;
list_for_each_safe(r, &device->regions, link)
eis_region_unref(r);
eis_keymap_unref(device->keymap);
free(device->name);
}
@ -175,6 +180,7 @@ eis_device_new(struct eis_seat *seat)
device->name = xstrdup("unnamed device");
device->capabilities = 0;
device->state = EIS_DEVICE_STATE_NEW;
list_init(&device->regions);
list_append(&seat->devices, &device->link);
@ -204,25 +210,18 @@ eis_device_configure_capability(struct eis_device *device, enum eis_device_capab
}
_public_ void
eis_device_configure_pointer_range(struct eis_device *device,
uint32_t w, uint32_t h)
eis_device_configure_region(struct eis_device *device,
struct eis_region *region)
{
if (device->state != EIS_DEVICE_STATE_NEW)
return;
device->abs.dim.width = w;
device->abs.dim.height = h;
}
_public_ void
eis_device_configure_touch_range(struct eis_device *device,
uint32_t w, uint32_t h)
{
if (device->state != EIS_DEVICE_STATE_NEW)
if (region->added_to_device)
return;
device->touch.dim.width = w;
device->touch.dim.height = h;
region->added_to_device = true;
eis_region_ref(region);
list_append(&device->regions, &region->link);
}
_public_ void
@ -289,30 +288,6 @@ eis_device_has_capability(struct eis_device *device,
}
_public_ uint32_t
eis_device_pointer_get_width(struct eis_device *device)
{
return device->abs.dim.width;
}
_public_ uint32_t
eis_device_pointer_get_height(struct eis_device *device)
{
return device->abs.dim.height;
}
_public_ uint32_t
eis_device_touch_get_width(struct eis_device *device)
{
return device->touch.dim.width;
}
_public_ uint32_t
eis_device_touch_get_height(struct eis_device *device)
{
return device->touch.dim.height;
}
int
eis_device_pointer_rel(struct eis_device *device,
double x, double y)
@ -331,6 +306,19 @@ eis_device_pointer_rel(struct eis_device *device,
return 0;
}
static inline bool
eis_device_in_region(struct eis_device *device, double x, double y)
{
struct eis_region *r;
list_for_each(r, &device->regions, link) {
if (eis_region_contains(r, x, y))
return true;
}
return false;
}
int
eis_device_pointer_abs(struct eis_device *device,
double x, double y)
@ -344,8 +332,7 @@ eis_device_pointer_abs(struct eis_device *device,
if (device->state != EIS_DEVICE_STATE_RESUMED)
return -EINVAL;
if (x < 0 || x >= device->abs.dim.width ||
y < 0 || y >= device->abs.dim.height)
if (!eis_device_in_region(device, x, y))
return -EINVAL;
eis_queue_pointer_abs_event(device, x, y);

View file

@ -112,6 +112,14 @@ enum eis_device_state {
EIS_DEVICE_STATE_DEAD,
};
struct eis_region {
struct object object;
void *user_data;
bool added_to_device;
struct list link;
uint32_t x, y;
uint32_t width, height;
};
struct eis_device {
struct object object; /* parent is ei_seat, and we have a ref to it */
@ -122,12 +130,7 @@ struct eis_device {
uint32_t capabilities;
void *user_data;
struct {
struct dimensions dim;
} abs;
struct {
struct dimensions dim;
} touch;
struct list regions;
struct eis_keymap *keymap;
};
@ -211,13 +214,6 @@ eis_seat_bind(struct eis_seat *seat, uint32_t cap);
void
eis_seat_unbind(struct eis_seat *seat);
void
eis_device_set_pointer_range(struct eis_device *device,
uint32_t w, uint32_t h);
void
eis_device_set_touch_range(struct eis_device *device,
uint32_t w, uint32_t h);
void
eis_device_set_client_keymap(struct eis_device *device,
enum eis_keymap_type type,
@ -253,6 +249,9 @@ eis_device_touch(struct eis_device *device, uint32_t touchid,
void
eis_device_closed_by_client(struct eis_device *device);
bool
eis_region_contains(struct eis_region *r, double x, double y);
struct eis_event *
eis_event_new_for_client(struct eis_client *client);

View file

@ -60,13 +60,14 @@ log_wire_message(struct eis *eis, const ServerMessage *msg)
switch (msg->msg_case) {
case SERVER_MESSAGE__MSG__NOT_SET:
abort();
assert(!"SERVER_MESSAGE__MSG__NOT_SET");
MSG_STRING_CASE(CONNECTED);
MSG_STRING_CASE(DISCONNECTED);
MSG_STRING_CASE(SEAT_ADDED);
MSG_STRING_CASE(SEAT_REMOVED);
MSG_STRING_CASE(DEVICE_ADDED);
MSG_STRING_CASE(DEVICE_ADDED_DONE);
MSG_STRING_CASE(DEVICE_REGION);
MSG_STRING_CASE(DEVICE_REMOVED);
MSG_STRING_CASE(DEVICE_RESUMED);
MSG_STRING_CASE(DEVICE_SUSPENDED);
@ -193,12 +194,6 @@ eis_proto_send_device_added(struct eis_client *client, struct eis_device *device
/* it's NULL anyway */
added.keymap_from_server = true;
}
added.seatid = seat->id;
added.pointer_width = device->abs.dim.width;
added.pointer_height = device->abs.dim.height;
added.touch_width = device->touch.dim.width;
added.touch_height = device->touch.dim.height;
msg.device_added = &added;
msg.msg_case = SERVER_MESSAGE__MSG_DEVICE_ADDED;
@ -220,6 +215,25 @@ eis_proto_send_device_added_done(struct eis_client *client, struct eis_device *d
return eis_proto_send_msg(client, &msg);
}
int
eis_proto_send_device_region(struct eis_client *client, struct eis_device *device,
const struct eis_region *r)
{
ServerMessage msg = SERVER_MESSAGE__INIT;
DeviceRegion region = DEVICE_REGION__INIT;
region.deviceid = device->id;
region.offset_x = r->x;
region.offset_y = r->y;
region.width = r->width;
region.height = r->height;
msg.device_region = &region;
msg.msg_case = SERVER_MESSAGE__MSG_DEVICE_REGION;
return eis_proto_send_msg(client, &msg);
}
int
eis_proto_send_device_removed(struct eis_client *client, struct eis_device *device)
{

View file

@ -168,6 +168,10 @@ eis_proto_send_device_added(struct eis_client *client,
int
eis_proto_send_device_added_done(struct eis_client *client,
struct eis_device *device);
int
eis_proto_send_device_region(struct eis_client *client,
struct eis_device *device,
const struct eis_region *region);
int
eis_proto_send_device_removed(struct eis_client *client,

75
src/libeis-region.c Normal file
View file

@ -0,0 +1,75 @@
/*
* 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 "libeis-private.h"
static void
eis_region_destroy(struct eis_region *region)
{
list_remove(&region->link);
}
_public_
OBJECT_IMPLEMENT_REF(eis_region);
_public_
OBJECT_IMPLEMENT_UNREF(eis_region);
_public_
OBJECT_IMPLEMENT_GETTER(eis_region, user_data, void *);
_public_
OBJECT_IMPLEMENT_SETTER(eis_region, user_data, void *);
static
OBJECT_IMPLEMENT_CREATE(eis_region);
_public_ struct eis_region *
eis_region_new(void)
{
struct eis_region *region = eis_region_create(NULL);
list_init(&region->link);
return region;
}
_public_ void
eis_region_set_offset(struct eis_region *region, uint32_t x, uint32_t y)
{
region->x = x;
region->y = y;
}
_public_ void
eis_region_set_size(struct eis_region *region, uint32_t w, uint32_t h)
{
region->width = w;
region->height = h;
}
bool
eis_region_contains(struct eis_region *r, double x, double y)
{
return (x >= r->x && x < r->x + r->width &&
y >= r->y && y < r->y + r->height);
}

View file

@ -48,6 +48,30 @@ struct eis_seat;
struct eis_event;
struct eis_keymap;
/**
* @struct ei_region
*
* A rectangular region, defined by an x/y offset and a width and a height.
* A region defines the area on an EIS desktop layout that is accessible by
* this device - this region may not be the full area of the desktop.
* Input events may only be sent for points within the regions.
*
* The use of regions is private to the EIS compositor and coordinates do not
* need match the size of the actual desktop. For example, a compositor may
* set a 1920x1080 region to represent a 4K monitor and transparently map
* input events into the respective true pixels.
*
* Absolute devices may have different regions, it is up to the libei client
* to send events through the correct device to target the right pixel. For
* example, a dual-head setup my have two absolute devices, the first with a
* zero offset region spanning the first screen, the second with a nonzero
* offset spanning the second screen.
*
* Regions must be assigned when the device is created and are static for the
* lifetime of the device.
*/
struct eis_region;
enum eis_device_capability {
EIS_DEVICE_CAP_POINTER = 1,
EIS_DEVICE_CAP_POINTER_ABSOLUTE,
@ -418,12 +442,40 @@ eis_device_configure_name(struct eis_device *device, const char *name);
void
eis_device_configure_capability(struct eis_device *device, enum eis_device_capability cap);
/**
* Add a new region to this device. The caller should immediately call
* ei_region_unref() after this call to release any references it has to the
* region.
*
* Adding the same region twice will be silently ignored.
*/
void
eis_device_configure_pointer_range(struct eis_device *device,
uint32_t w, uint32_t h);
eis_device_configure_region(struct eis_device *device,
struct eis_region *region);
/**
* Create a new region with an initial refcount of 1.
*/
struct eis_region *
eis_region_new(void);
void
eis_device_configure_touch_range(struct eis_device *device,
uint32_t w, uint32_t h);
eis_region_set_size(struct eis_region *region, uint32_t w, uint32_t h);
void
eis_region_set_offset(struct eis_region *region, uint32_t x, uint32_t y);
struct eis_region *
eis_region_ref(struct eis_region *region);
struct eis_region *
eis_region_unref(struct eis_region *region);
void *
eis_region_get_user_data(struct eis_region *region);
void
eis_region_set_user_data(struct eis_region *region, void *user_data);
/**
* Add this device to its seat and notify the client of the device's
@ -467,56 +519,6 @@ eis_device_suspend(struct eis_device *device);
void
eis_device_resume(struct eis_device *device);
/**
* Get the width of the absolute pointer device in logical
* pixels. The allowable range for absolute pointer motion is
* [0, max) for each axis, i.e. zero inclusive, max exclusive. Coordinates
* outside this range may be discarded or clipped silently by the library.
*
* The pointer range is constant. Where the pointer range is no longer
* applicable, the client needs to remove the device and create and add a
* new device with the updated pointer range.
*
* The server may use this in mapping heuristics. For example, a pointer
* device with a pixel range of 1920x1200 **may** be automatically mapped by
* the server to the monitor with this range, or a pointer device with a
* ratio of R **may** be mapped to the monitor with the same ratio. This is
* not a guarantee, the mapping policy is a private implementation detail
* in the server. It is assumed that the client has other communication
* channels (e.g. Wayland) to obtain the pointer range it needs to emulate
* input on a device and channels to notify the server of desired mappings
* (e.g. gsettings).
*
* It is a client bug to send pointer values outside this range.
*
* It is a server bug to call this function on a device without the @ref
* EIS_DEVICE_CAP_POINTER_ABSOLUTE capability.
*
* @return The new width in logical pixels
*/
uint32_t
eis_device_pointer_get_width(struct eis_device *device);
/**
* @see eis_device_pointer_get_width
*/
uint32_t
eis_device_pointer_get_height(struct eis_device *device);
/**
* @see eis_device_pointer_get_width
*/
uint32_t
eis_device_touch_get_width(struct eis_device *device);
/**
* @param device The EI device
*
* @see eis_device_touch_get_width
*/
uint32_t
eis_device_touch_get_height(struct eis_device *device);
/**
* Create a new keymap of the given @a type. This keymap does not immediately
* apply to the device, use eis_device_keyboard_set_keymap() to apply

View file

@ -463,10 +463,14 @@ static inline struct eis_device *
peck_eis_create_pointer_absolute(struct peck *peck, struct eis_seat *seat, const char *name)
{
struct eis_device *device = eis_device_new(seat);
_unref_(eis_region) *region = eis_region_new();
eis_region_set_offset(region, 0, 0);
eis_region_set_size(region, 1920, 1080);
eis_device_configure_name(device, name);
eis_device_configure_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE);
eis_device_configure_pointer_range(device, 1920, 1080); /* FIXME */
eis_device_configure_region(device, region);
eis_device_add(device);
if (!peck->eis_abs)
@ -494,10 +498,14 @@ static inline struct eis_device *
peck_eis_create_touch(struct peck *peck, struct eis_seat *seat, const char *name)
{
struct eis_device *device = eis_device_new(seat);
_unref_(eis_region) *region = eis_region_new();
eis_region_set_offset(region, 0, 0);
eis_region_set_size(region, 1920, 1080);
eis_device_configure_name(device, name);
eis_device_configure_capability(device, EIS_DEVICE_CAP_TOUCH);
eis_device_configure_touch_range(device, 1920, 1080); /* FIXME */
eis_device_configure_region(device, region);
eis_device_add(device);
if (!peck->eis_touch)

View file

@ -268,6 +268,7 @@ DEFINE_UNREF_CLEANUP_FUNC(ei_device);
DEFINE_UNREF_CLEANUP_FUNC(ei_touch);
DEFINE_UNREF_CLEANUP_FUNC(ei_keymap);
DEFINE_UNREF_CLEANUP_FUNC(ei_seat);
DEFINE_UNREF_CLEANUP_FUNC(ei_region);
DEFINE_UNREF_CLEANUP_FUNC(eis);
DEFINE_UNREF_CLEANUP_FUNC(eis_client);
@ -275,6 +276,7 @@ DEFINE_UNREF_CLEANUP_FUNC(eis_event);
DEFINE_UNREF_CLEANUP_FUNC(eis_device);
DEFINE_UNREF_CLEANUP_FUNC(eis_keymap);
DEFINE_UNREF_CLEANUP_FUNC(eis_seat);
DEFINE_UNREF_CLEANUP_FUNC(eis_region);
/* Macros intended just for readability to make it more obvious which part
of a test handles server vs client */

View file

@ -263,20 +263,92 @@ MUNIT_TEST(test_ei_device_pointer_rel)
return MUNIT_OK;
}
MUNIT_TEST(test_ei_device_regions)
{
_unref_(peck) *peck = peck_new();
peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
peck_dispatch_until_stable(peck);
with_server(peck) {
struct eis_seat *seat = peck_eis_get_default_seat(peck);
_unref_(eis_device) *device = eis_device_new(seat);
eis_device_configure_name(device, __func__);
eis_device_configure_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE);
/* nothing cares about the actual values, so we're just
* checking for correct passthrough here */
_unref_(eis_region) *r1 = eis_region_new();
eis_region_set_size(r1, 100, 200);
eis_region_set_offset(r1, 300, 400);
eis_device_configure_region(device, r1);
_unref_(eis_region) *r2 = eis_region_new();
eis_region_set_size(r2, 500, 600);
eis_region_set_offset(r2, 700, 800);
eis_device_configure_region(device, r2);
_unref_(eis_region) *r3 = eis_region_new();
eis_region_set_size(r3, 900, 1000);
eis_region_set_offset(r3, 1100, 1200);
eis_device_configure_region(device, r3);
/* Add the same region twice, should be ignored */
eis_device_configure_region(device, r3);
eis_device_add(device);
}
peck_dispatch_until_stable(peck);
with_client(peck) {
struct ei_device *device = peck_ei_get_default_pointer_absolute(peck);
struct ei_region *r;
r = ei_device_get_region(device, 0);
munit_assert_int(ei_region_get_width(r), ==, 100);
munit_assert_int(ei_region_get_height(r), ==, 200);
munit_assert_int(ei_region_get_x(r), ==, 300);
munit_assert_int(ei_region_get_y(r), ==, 400);
r = ei_device_get_region(device, 1);
munit_assert_int(ei_region_get_width(r), ==, 500);
munit_assert_int(ei_region_get_height(r), ==, 600);
munit_assert_int(ei_region_get_x(r), ==, 700);
munit_assert_int(ei_region_get_y(r), ==, 800);
r = ei_device_get_region(device, 2);
munit_assert_int(ei_region_get_width(r), ==, 900);
munit_assert_int(ei_region_get_height(r), ==, 1000);
munit_assert_int(ei_region_get_x(r), ==, 1100);
munit_assert_int(ei_region_get_y(r), ==, 1200);
munit_assert_ptr_null(ei_device_get_region(device, 3));
}
return MUNIT_OK;
}
MUNIT_TEST(test_ei_device_pointer_abs)
{
_unref_(peck) *peck = peck_new();
struct ei_device *device = NULL;
uint32_t maxx = 0, maxy = 0;
peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER_ABSOLUTE);
peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
peck_dispatch_until_stable(peck);
/* FIXME: missing the currently unimplemented region checks */
with_client(peck) {
device = peck_ei_get_default_pointer_absolute(peck);
/* We know our default device has one region */
struct ei_region *r = ei_device_get_region(device, 0);
maxx = ei_region_get_x(r) + ei_region_get_width(r);
maxy = ei_region_get_y(r) + ei_region_get_height(r);
for (int i = 0; i < 10; i++)
ei_device_pointer_motion_absolute(device, 1 * i , 2 + i);
}
@ -294,8 +366,8 @@ MUNIT_TEST(test_ei_device_pointer_abs)
with_client(peck) {
/* outside of pointer range, expect to be discarded */
ei_device_pointer_motion_absolute(device, 1920, 1200);
ei_device_pointer_motion_absolute(device, 2000, 1400);
ei_device_pointer_motion_absolute(device, maxx + 1, maxy/2);
ei_device_pointer_motion_absolute(device, maxx/2 , maxy + 1);
}
peck_dispatch_until_stable(peck);
@ -328,16 +400,21 @@ MUNIT_TEST(test_ei_device_touch)
{
_unref_(peck) *peck = peck_new();
struct ei_device *device = NULL;
uint32_t maxx = 0, maxy = 0;
peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_TOUCH);
peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
peck_dispatch_until_stable(peck);
/* FIXME: missing the currently unimplemented region checks */
with_client(peck) {
device = peck_ei_get_default_touch(peck);
/* We know our default device has one region */
struct ei_region *r = ei_device_get_region(device, 0);
maxx = ei_region_get_x(r) + ei_region_get_width(r);
maxy = ei_region_get_y(r) + ei_region_get_height(r);
_unref_(ei_touch) *t = ei_device_touch_new(device);
ei_touch_down(t, 1, 2);
ei_touch_motion(t, 200, 500);
@ -360,8 +437,13 @@ MUNIT_TEST(test_ei_device_touch)
with_client(peck) {
_unref_(ei_touch) *t = ei_device_touch_new(device);
/* outside clip range, expect touch to be dropped */
ei_touch_down(t, 1920, 1200);
ei_touch_motion(t, 1920, 1000);
ei_touch_down(t, maxx + 1, maxy/2);
ei_touch_motion(t, maxx + 1, maxy/3);
ei_touch_up(t);
/* outside clip range, expect touch to be dropped */
ei_touch_down(t, maxx/2, maxy + 1);
ei_touch_motion(t, maxx/3, maxy + 1);
ei_touch_up(t);
}
@ -375,7 +457,7 @@ MUNIT_TEST(test_ei_device_touch)
_unref_(ei_touch) *t = ei_device_touch_new(device);
ei_touch_down(t, 100, 200);
/* outside allowed range, generates a touch up */
ei_touch_motion(t, 1950, 200);
ei_touch_motion(t, maxx + 1, 200);
ei_touch_up(t);
}