diff --git a/proto/ei.proto b/proto/ei.proto index 64b4c66..65e922e 100644 --- a/proto/ei.proto +++ b/proto/ei.proto @@ -183,6 +183,7 @@ message DeviceRegion { uint32 offset_y = 3; uint32 width = 4; uint32 height = 5; + double scale = 6; } message DeviceAddedDone { diff --git a/src/libei-private.h b/src/libei-private.h index 1def074..16638d7 100644 --- a/src/libei-private.h +++ b/src/libei-private.h @@ -110,6 +110,7 @@ struct ei_region { struct list link; uint32_t x, y; uint32_t width, height; + double physical_scale; }; struct ei_device { @@ -275,6 +276,9 @@ 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); +void +ei_region_set_physical_scale(struct ei_region *region, double scale); + bool ei_region_contains(struct ei_region *region, double x, double y); diff --git a/src/libei-proto.c b/src/libei-proto.c index 4cf6941..5b201ca 100644 --- a/src/libei-proto.c +++ b/src/libei-proto.c @@ -137,6 +137,7 @@ ei_proto_parse_message(struct brei_message *bmsg, size_t *consumed) .device_region.y = r->offset_y, .device_region.w = r->width, .device_region.h = r->height, + .device_region.scale = r->scale, }; } break; diff --git a/src/libei-proto.h b/src/libei-proto.h index fb70334..e02fba4 100644 --- a/src/libei-proto.h +++ b/src/libei-proto.h @@ -103,6 +103,7 @@ struct message { uint32_t y; uint32_t w; uint32_t h; + double scale; } device_region; struct message_device_removed { uint32_t deviceid; diff --git a/src/libei-region.c b/src/libei-region.c index e23304e..46e95e5 100644 --- a/src/libei-region.c +++ b/src/libei-region.c @@ -50,12 +50,16 @@ _public_ OBJECT_IMPLEMENT_GETTER(ei_region, width, uint32_t); _public_ OBJECT_IMPLEMENT_GETTER(ei_region, height, uint32_t); +_public_ +OBJECT_IMPLEMENT_GETTER(ei_region, physical_scale, double); +OBJECT_IMPLEMENT_SETTER(ei_region, physical_scale, double); struct ei_region * ei_region_new(void) { struct ei_region *region = ei_region_create(NULL); + region->physical_scale = 1.0; list_init(®ion->link); return region; diff --git a/src/libei.c b/src/libei.c index 4f51fc8..caf3926 100644 --- a/src/libei.c +++ b/src/libei.c @@ -597,7 +597,8 @@ handle_msg_device_added_done(struct ei *ei, uint32_t deviceid) static int handle_msg_device_region(struct ei *ei, uint32_t deviceid, uint32_t x, uint32_t y, - uint32_t w, uint32_t h) + uint32_t w, uint32_t h, + double scale) { log_debug(ei, "Adding device region for %#x\n", deviceid); @@ -608,6 +609,7 @@ handle_msg_device_region(struct ei *ei, uint32_t deviceid, _unref_(ei_region) *r = ei_region_new(); ei_region_set_offset(r, x, y); ei_region_set_size(r, w, h); + ei_region_set_physical_scale(r, scale); ei_device_add_region(device, r); @@ -894,7 +896,8 @@ connection_connected_handle_msg(struct ei *ei, struct message *msg) 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); + msg->device_region.w, msg->device_region.h, + msg->device_region.scale); break; case MESSAGE_DEVICE_REMOVED: rc = handle_msg_device_removed(ei, msg->device_removed.deviceid); diff --git a/src/libei.h b/src/libei.h index 89761fb..db8eb41 100644 --- a/src/libei.h +++ b/src/libei.h @@ -825,6 +825,76 @@ ei_region_get_width(struct ei_region *region); uint32_t ei_region_get_height(struct ei_region *region); +/** + * Return the physical scale for this region. The default scale is 1.0. + * + * The regions' coordinate space is in logical pixels in the EIS range. The + * logical pixels may or may not match the physical pixels on the output + * range but the mapping from logical pixels to physical pixels is performed + * by the EIS implementation. + * + * In some use-cases though, relative data from a remote input source needs + * to be converted by the libei client into an absolute movement on an EIS + * region. In that case, the physical scale provides the factor to multiply + * the relative logical input to provide the expected physical relative + * movement. + * + * For example consider the following dual-monitor setup comprising a 2k and + * a 4k monitor **of the same physical size**: + * The physical layout of the monitors appears like this: + * @code + * 2k 4k + * +-------------++-------------+ + * | || | + * | a b || c d | + * | || | + * +-------------++-------------+ + * @endcode + * + * The physical distance `ab` is the same as the physical distance `cd`. + * Where the EIS implementation supports high-dpi screens, the logical + * distance (in pixels) are identical too. + * + * Where the EIS implementation does not support high-dpi screens, the + * logical layout of these two monitors appears like this: + * + * @code + * 2k 4k + * +-------------++--------------------------+ + * | || | + * | a b || | + * | || | + * +-------------+| c d | + * | | + * | | + * | | + * +--------------------------+ + * @endcode + * + * While the two physical distances `ab` and `cd` are still identical, the + * logical distance `cd` (in pixels) is twice that of `ab`. + * Where a libei client receives relative deltas from an input source and + * converts that relative input into an absolute position on the screen, it + * needs to take this into account. + * + * For example, if a remote input source moves by relative 100 logical + * pixels, the libei client would convert this as `a + 100 = b` on the + * region for the 2k screen and send the absolute events to logically change + * the position from `a` to `b`. If the same remote input source moves by + * relative 100 logical pixels, the libei client would convert this as + * `c + 100 * scale = d` on the region for the 4k screen to logically + * change the position from `c` to `d`. While the pixel movement differs, + * the physical movement as seen by the user is thus identical. + * + * A second possible use-case for the physical scale is to match pixels from + * one region to their respective counterpart on a different region. + * For example, if the bottom-right corner of the 2k screen in the + * illustration above has a coordinate of x/y, the neighbouring pixel on the + * **physical** 4k screen is (0/y * scale). + */ +double +ei_region_get_physical_scale(struct ei_region *region); + /** * Return the keymap for this device or `NULL`. The keymap is constant for * the lifetime of the device after the @ref EI_EVENT_DEVICE_ADDED was diff --git a/src/libeis-private.h b/src/libeis-private.h index 528e473..e1315c6 100644 --- a/src/libeis-private.h +++ b/src/libeis-private.h @@ -119,6 +119,7 @@ struct eis_region { struct list link; uint32_t x, y; uint32_t width, height; + double physical_scale; }; struct eis_device { diff --git a/src/libeis-proto.c b/src/libeis-proto.c index 82d1de3..05c78e5 100644 --- a/src/libeis-proto.c +++ b/src/libeis-proto.c @@ -227,6 +227,7 @@ eis_proto_send_device_region(struct eis_client *client, struct eis_device *devic region.offset_y = r->y; region.width = r->width; region.height = r->height; + region.scale = r->physical_scale; msg.device_region = ®ion; msg.msg_case = SERVER_MESSAGE__MSG_DEVICE_REGION; diff --git a/src/libeis-region.c b/src/libeis-region.c index 83b8717..7dd6c34 100644 --- a/src/libeis-region.c +++ b/src/libeis-region.c @@ -48,6 +48,7 @@ eis_region_new(void) { struct eis_region *region = eis_region_create(NULL); + region->physical_scale = 1.0; list_init(®ion->link); return region; @@ -67,6 +68,13 @@ eis_region_set_size(struct eis_region *region, uint32_t w, uint32_t h) region->height = h; } +_public_ void +eis_region_set_physical_scale(struct eis_region *region, double scale) +{ + if (scale > 0.0) + region->physical_scale = scale; +} + bool eis_region_contains(struct eis_region *r, double x, double y) { diff --git a/src/libeis.h b/src/libeis.h index 1fcb407..44996e9 100644 --- a/src/libeis.h +++ b/src/libeis.h @@ -465,6 +465,17 @@ 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); +/** + * Set the physical scale for this region. If unset, the scale defaults to + * 1.0. + * + * A @a scale value of less or equal to 0.0 will be silently ignored. + * + * See ei_region_get_physical_scale() for details. + */ +void +eis_region_set_physical_scale(struct eis_region *region, double scale); + struct eis_region * eis_region_ref(struct eis_region *region); diff --git a/test/test-ei-device.c b/test/test-ei-device.c index 0c0bb82..ef2977c 100644 --- a/test/test-ei-device.c +++ b/test/test-ei-device.c @@ -282,16 +282,19 @@ MUNIT_TEST(test_ei_device_regions) _unref_(eis_region) *r1 = eis_region_new(); eis_region_set_size(r1, 100, 200); eis_region_set_offset(r1, 300, 400); + /* no scale, default to 1.0 */ 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_region_set_physical_scale(r2, 3.9); 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_region_set_physical_scale(r3, 0.3); eis_device_configure_region(device, r3); /* Add the same region twice, should be ignored */ @@ -311,18 +314,21 @@ MUNIT_TEST(test_ei_device_regions) 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); + munit_assert_double_equal(ei_region_get_physical_scale(r), 1.0, 2 /* precision */); 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); + munit_assert_double_equal(ei_region_get_physical_scale(r), 3.9, 2 /* precision */); 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_double_equal(ei_region_get_physical_scale(r), 0.3, 2 /* precision */); munit_assert_ptr_null(ei_device_get_region(device, 3)); }