Add a physical scale factor for the regions

This isn't something that libei itself uses but clients like synergy
need to know about this to be able to map relative pointer motion from
one host into the right physical pixel on another host.

This is required for mutter in the x11-compat mode where a 4k screen is
logically twice the size of a 2k screen, despite having the same
physical size.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
Peter Hutterer 2021-07-23 10:30:35 +10:00
parent 56b82c2b8f
commit 6095a0d99f
12 changed files with 113 additions and 2 deletions

View file

@ -183,6 +183,7 @@ message DeviceRegion {
uint32 offset_y = 3;
uint32 width = 4;
uint32 height = 5;
double scale = 6;
}
message DeviceAddedDone {

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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(&region->link);
return region;

View file

@ -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);

View file

@ -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

View file

@ -119,6 +119,7 @@ struct eis_region {
struct list link;
uint32_t x, y;
uint32_t width, height;
double physical_scale;
};
struct eis_device {

View file

@ -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 = &region;
msg.msg_case = SERVER_MESSAGE__MSG_DEVICE_REGION;

View file

@ -48,6 +48,7 @@ eis_region_new(void)
{
struct eis_region *region = eis_region_create(NULL);
region->physical_scale = 1.0;
list_init(&region->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)
{

View file

@ -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);

View file

@ -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));
}