diff --git a/meson.build b/meson.build index d94fe73..d2c28c9 100644 --- a/meson.build +++ b/meson.build @@ -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', diff --git a/proto/ei.proto b/proto/ei.proto index 480b728..64b4c66 100644 --- a/proto/ei.proto +++ b/proto/ei.proto @@ -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; diff --git a/src/libei-device.c b/src/libei-device.c index a3cda7d..c7ce171 100644 --- a/src/libei-device.c +++ b/src/libei-device.c @@ -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, ®ion->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); diff --git a/src/libei-private.h b/src/libei-private.h index a6e9897..1def074 100644 --- a/src/libei-private.h +++ b/src/libei-private.h @@ -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, diff --git a/src/libei-proto.c b/src/libei-proto.c index 87d4072..4cf6941 100644 --- a/src/libei-proto.c +++ b/src/libei-proto.c @@ -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; diff --git a/src/libei-proto.h b/src/libei-proto.h index 6205997..fb70334 100644 --- a/src/libei-proto.h +++ b/src/libei-proto.h @@ -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; diff --git a/src/libei-region.c b/src/libei-region.c new file mode 100644 index 0000000..e23304e --- /dev/null +++ b/src/libei-region.c @@ -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(®ion->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(®ion->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); +} diff --git a/src/libei.c b/src/libei.c index c653995..4f51fc8 100644 --- a/src/libei.c +++ b/src/libei.c @@ -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; diff --git a/src/libei.h b/src/libei.h index 0795613..89761fb 100644 --- a/src/libei.h +++ b/src/libei.h @@ -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 diff --git a/src/libeis-client.c b/src/libeis-client.c index 72eca62..2916836 100644 --- a/src/libeis-client.c +++ b/src/libeis-client.c @@ -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; diff --git a/src/libeis-device.c b/src/libeis-device.c index c2377ef..026f50f 100644 --- a/src/libeis-device.c +++ b/src/libeis-device.c @@ -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, ®ion->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); diff --git a/src/libeis-private.h b/src/libeis-private.h index 6b19d9a..528e473 100644 --- a/src/libeis-private.h +++ b/src/libeis-private.h @@ -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); diff --git a/src/libeis-proto.c b/src/libeis-proto.c index 93a1641..82d1de3 100644 --- a/src/libeis-proto.c +++ b/src/libeis-proto.c @@ -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 = ®ion; + 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) { diff --git a/src/libeis-proto.h b/src/libeis-proto.h index e5e835c..9349d4f 100644 --- a/src/libeis-proto.h +++ b/src/libeis-proto.h @@ -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, diff --git a/src/libeis-region.c b/src/libeis-region.c new file mode 100644 index 0000000..83b8717 --- /dev/null +++ b/src/libeis-region.c @@ -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(®ion->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(®ion->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); +} diff --git a/src/libeis.h b/src/libeis.h index 990ba0a..1fcb407 100644 --- a/src/libeis.h +++ b/src/libeis.h @@ -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 diff --git a/test/eierpecken.c b/test/eierpecken.c index 2311bd4..bae4573 100644 --- a/test/eierpecken.c +++ b/test/eierpecken.c @@ -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) diff --git a/test/eierpecken.h b/test/eierpecken.h index 32ca980..fd12125 100644 --- a/test/eierpecken.h +++ b/test/eierpecken.h @@ -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 */ diff --git a/test/test-ei-device.c b/test/test-ei-device.c index 7b55961..0c0bb82 100644 --- a/test/test-ei-device.c +++ b/test/test-ei-device.c @@ -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); }