Differentiate between physical and virtual devices

With passive libei contexts receiving events sent by the EIS
implementation, the type of device changes significantly. While a
relative input device could still send data in logical pixels,
absolute devices may not have that luxury.

Best example here is an external tablet (think: Wacom Intuos): that
tablet has no built-in mapping to a screen and thus cannot capture input
events in logical pixels.

Address this by adding a device type, either virtual or physical.
In terms of functionality, the device's type decides:
- only virtual devices have regions
- only physical devices have a size

The event API remains as-is but the event data not represents either
logical pixels (virtual devices) or mm (physical device).

An EIS implementation connected to a passive libei context would likely
create:
- a virtual relative device (sending deltas in logical pixels)
- one or more physical absolute devices (sending deltas in mm)
This commit is contained in:
Peter Hutterer 2022-03-11 10:33:54 +10:00
parent 9d85b1289d
commit fa091d7ac4
14 changed files with 305 additions and 28 deletions

View file

@ -208,6 +208,9 @@ message DeviceAdded {
uint32 capabilities = 2;
string name = 6;
uint32 seatid = 7;
uint32 type = 8;
uint32 width = 9;
uint32 height = 10;
}
message DeviceKeymap {

View file

@ -87,6 +87,8 @@ OBJECT_IMPLEMENT_CREATE(ei_device);
static
OBJECT_IMPLEMENT_PARENT(ei_device, ei_seat);
_public_
OBJECT_IMPLEMENT_GETTER(ei_device, type, enum ei_device_type);
_public_
OBJECT_IMPLEMENT_GETTER(ei_device, name, const char *);
_public_
OBJECT_IMPLEMENT_GETTER(ei_device, user_data, void *);
@ -294,6 +296,29 @@ ei_device_added(struct ei_device *device)
{
}
void
ei_device_set_type(struct ei_device *device, enum ei_device_type type)
{
switch(type) {
case EI_DEVICE_TYPE_PHYSICAL:
case EI_DEVICE_TYPE_VIRTUAL:
device->type = type;
break;
default:
log_bug(ei_device_get_context(device), "Invalid device type %u\n", type);
break;
}
}
void
ei_device_set_size(struct ei_device *device, uint32_t width, uint32_t height)
{
if (device->type == EI_DEVICE_TYPE_PHYSICAL) {
device->width = width;
device->height = height;
}
}
void
ei_device_set_name(struct ei_device *device, const char *name)
{

View file

@ -127,6 +127,9 @@ struct ei_device {
enum ei_device_state state;
uint32_t capabilities;
char *name;
enum ei_device_type type;
uint32_t width, height;
struct list regions;
@ -429,6 +432,12 @@ ei_device_paused(struct ei_device *device);
void
ei_device_resumed(struct ei_device *device);
void
ei_device_set_type(struct ei_device *device, enum ei_device_type type);
void
ei_device_set_size(struct ei_device *device, uint32_t width, uint32_t height);
void
ei_device_set_name(struct ei_device *device, const char *name);

View file

@ -80,7 +80,10 @@ ei_proto_handle_message(struct ei *ei,
proto->device_added->deviceid,
proto->device_added->seatid,
proto->device_added->name,
proto->device_added->capabilities);
proto->device_added->capabilities,
proto->device_added->type,
proto->device_added->width,
proto->device_added->height);
break;
case SERVER_MESSAGE__MSG_DEVICE_DONE:
rc = call(device_done, ei,

View file

@ -40,7 +40,8 @@ struct ei_proto_interface {
const char *name, uint32_t capabilities);
int (*seat_removed)(struct ei *ei, uint32_t seatid);
int (*device_added)(struct ei *ei, uint32_t deviceid, uint32_t seatid,
const char *name, uint32_t capabilities);
const char *name, uint32_t capabilities, uint32_t type,
uint32_t width, uint32_t height);
int (*device_removed)(struct ei *ei, uint32_t deviceid);
int (*device_paused)(struct ei *ei, uint32_t deviceid);
int (*device_resumed)(struct ei *ei, uint32_t deviceid);

View file

@ -576,7 +576,8 @@ handle_msg_seat_removed(struct ei *ei, uint32_t seatid)
static int
handle_msg_device_added(struct ei *ei, uint32_t deviceid, uint32_t seatid,
const char *name, uint32_t capabilities)
const char *name, uint32_t capabilities, uint32_t type,
uint32_t width, uint32_t height)
{
struct ei_seat *seat = ei_find_seat(ei, seatid);
@ -595,7 +596,19 @@ handle_msg_device_added(struct ei *ei, uint32_t deviceid, uint32_t seatid,
return -EINVAL;
}
switch (type) {
case EI_DEVICE_TYPE_PHYSICAL:
case EI_DEVICE_TYPE_VIRTUAL:
break;
default:
log_error(ei, "Server sent invalid device type %u\n", type);
return -EINVAL;
}
_unref_(ei_device) *device = ei_device_new(seat, deviceid);
ei_device_set_type(device, type);
if (type == EI_DEVICE_TYPE_PHYSICAL)
ei_device_set_size(device, width, height);
ei_device_set_name(device, name);
ei_device_set_capabilities(device, capabilities);
ei_device_added(device);

View file

@ -126,6 +126,32 @@ struct ei_keymap;
*/
struct ei_region;
/**
* @enum ei_device_type
*
* The device type determines what the device represents.
*
* If the device type is @ref EI_DEVICE_TYPE_VIRTUAL, the device is a
* virtual device representing input as applied on the EIS implementation's
* screen. A relative virtual device generates input events in logical pixels,
* an absolute virtual device generates input events in logical pixels on one
* of the device's regions. Virtual devices do not have a size.
*
* If the device type is @ref EI_DEVICE_TYPE_PHYSICAL, the device is a
* representation of a physical device as if connected to the EIS
* implementation's host computer. A relative physical device generates input
* events in mm, an absolute physical device generates input events in mm
* within the device's specified physical size. Physical devices do not have
* regions.
*
* @see eis_device_get_width
* @see eis_device_get_height
*/
enum ei_device_type {
EI_DEVICE_TYPE_VIRTUAL = 1,
EI_DEVICE_TYPE_PHYSICAL
};
/**
* @enum ei_device_capability
*
@ -908,6 +934,20 @@ ei_device_close(struct ei_device *device);
const char *
ei_device_get_name(struct ei_device *device);
/**
* The device type of the device is determined by the type of the ei
* context. If the client context was created with ei_new_active(), the device
* type defaults to @ref EI_DEVICE_TYPE_VIRTUAL. If the client context was
* created with ei_new_passive(), the device type defaults to @ref
* EI_DEVICE_TYPE_PHYSICAL.
*
* libei does not currently support virtual devices on a passive context or
* physical devices on an active context. This may change in the future,
* applications should not rely on the type being fixed.
*/
enum ei_device_type
ei_device_get_type(struct ei_device *device);
/**
* Return true if the device has the requested capability. Device
* capabilities are constant.
@ -918,9 +958,9 @@ ei_device_has_capability(struct ei_device *device,
/**
* 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.
* Obtain a region from a device of type @ref EI_DEVICE_TYPE_VIRTUAL. 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 regions, the EIS implementation must create multiple devices with
@ -933,6 +973,8 @@ ei_device_has_capability(struct ei_device *device,
*
* This does not increase the refcount of the region. Use ei_region_ref() to
* keep a reference beyond the immediate scope.
*
* Devices of type @ref EI_DEVICE_TYPE_PHYSICAL do not have regions.
*/
struct ei_region *
ei_device_get_region(struct ei_device *device, size_t index);
@ -1397,28 +1439,28 @@ ei_event_property_get_permissions(struct ei_event *event);
/**
* For an event of type @ref EI_EVENT_POINTER_MOTION return the relative x
* movement in logical pixels.
* movement in logical pixels or mm, depending on the device type.
*/
double
ei_event_pointer_get_dx(struct ei_event *event);
/**
* For an event of type @ref EI_EVENT_POINTER_MOTION return the relative y
* movement in logical pixels.
* movement in logical pixels or mm, depending on the device type.
*/
double
ei_event_pointer_get_dy(struct ei_event *event);
/**
* For an event of type @ref EI_EVENT_POINTER_MOTION_ABSOLUTE return the x
* position in logical pixels.
* position in logical pixels or mm, depending on the device type.
*/
double
ei_event_pointer_get_absolute_x(struct ei_event *event);
/**
* For an event of type @ref EI_EVENT_POINTER_MOTION_ABSOLUTE return the y
* position in logical pixels.
* position in logical pixels or mm, depending on the device type.
*/
double
ei_event_pointer_get_absolute_y(struct ei_event *event);
@ -1439,14 +1481,14 @@ ei_event_pointer_get_button_is_press(struct ei_event *event);
/**
* For an event of type @ref EI_EVENT_POINTER_SCROLL return the x scroll
* distance in logical pixels.
* distance in logical pixels or mm, depending on the device type.
*/
double
ei_event_pointer_get_scroll_x(struct ei_event *event);
/**
* For an event of type @ref EI_EVENT_POINTER_SCROLL return the y scroll
* distance in logical pixels.
* distance in logical pixels or mm, depending on the device type.
*/
double
ei_event_pointer_get_scroll_y(struct ei_event *event);
@ -1508,14 +1550,16 @@ ei_event_touch_get_id(struct ei_event *event);
/**
* For an event of type @ref EI_EVENT_TOUCH_DOWN, or @ref
* EI_EVENT_TOUCH_MOTION, return the x coordinate of the touch.
* EI_EVENT_TOUCH_MOTION, return the x coordinate of the touch
* in logical pixels or mm, depending on the device type.
*/
double
ei_event_touch_get_x(struct ei_event *event);
/**
* For an event of type @ref EI_EVENT_TOUCH_DOWN, or @ref
* EI_EVENT_TOUCH_MOTION, return the y coordinate of the touch.
* EI_EVENT_TOUCH_MOTION, return the y coordinate of the touch
* in logical pixels or mm, depending on the device type.
*/
double
ei_event_touch_get_y(struct ei_event *event);

View file

@ -151,7 +151,7 @@ client_send_device_added(struct eis_client *client, struct eis_device *device)
if (rc >= 0 && device->keymap)
rc = eis->requests->device_keymap(device);
if (rc >= 0) {
if (rc >= 0 && device->type == EIS_DEVICE_TYPE_VIRTUAL) {
struct eis_region *r;
list_for_each(r, &device->regions, link) {
rc = eis->requests->device_region(device, r);

View file

@ -152,6 +152,12 @@ _public_
OBJECT_IMPLEMENT_SETTER(eis_device, user_data, void *);
_public_
OBJECT_IMPLEMENT_GETTER(eis_device, name, const char *);
_public_
OBJECT_IMPLEMENT_GETTER(eis_device, type, enum eis_device_type);
_public_
OBJECT_IMPLEMENT_GETTER(eis_device, width, uint32_t);
_public_
OBJECT_IMPLEMENT_GETTER(eis_device, height, uint32_t);
_public_ struct eis_seat *
eis_device_get_seat(struct eis_device *device)
@ -181,6 +187,7 @@ eis_seat_new_device(struct eis_seat *seat)
device->name = xstrdup("unnamed device");
device->capabilities = 0;
device->state = EIS_DEVICE_STATE_NEW;
device->type = EIS_DEVICE_TYPE_VIRTUAL;
list_init(&device->regions);
list_init(&device->regions_new);
@ -189,6 +196,24 @@ eis_seat_new_device(struct eis_seat *seat)
return eis_device_ref(device);
}
_public_ void
eis_device_configure_type(struct eis_device *device, enum eis_device_type type)
{
if (device->state != EIS_DEVICE_STATE_NEW)
return;
switch (type) {
case EIS_DEVICE_TYPE_VIRTUAL:
case EIS_DEVICE_TYPE_PHYSICAL:
break;
default:
log_bug_client(eis_device_get_context(device), "Invalid device type %u\n", type);
return;
}
device->type = type;
}
_public_ void
eis_device_configure_name(struct eis_device *device, const char *name)
{
@ -211,6 +236,21 @@ eis_device_configure_capability(struct eis_device *device, enum eis_device_capab
flag_set(device->capabilities, cap);
}
_public_ void
eis_device_configure_size(struct eis_device *device, uint32_t width, uint32_t height)
{
if (device->type != EIS_DEVICE_TYPE_PHYSICAL) {
log_bug_client(eis_device_get_context(device), "Device type physical requird for size\n");
return;
}
if (width > 2000 || height > 2000)
log_warn(eis_device_get_context(device), "Suspicious device size: %ux%umm\n", width, height);
device->width = width;
device->height = height;
}
_public_ void
eis_device_add(struct eis_device *device)
{

View file

@ -133,6 +133,9 @@ struct eis_device {
enum eis_device_state state;
uint32_t capabilities;
void *user_data;
enum eis_device_type type;
uint32_t width, height;
struct list regions;
struct list regions_new; /* not yet added */

View file

@ -177,6 +177,9 @@ eis_proto_send_device_added(struct eis_device *device)
device_added.seatid = seat->id;
device_added.name = device->name;
device_added.capabilities = device->capabilities;
device_added.type = device->type;
device_added.width = device->width;
device_added.height = device->height;
return eis_proto_send_msg(eis_device_get_client(device), &msg);
}

View file

@ -57,6 +57,14 @@ OBJECT_IMPLEMENT_CREATE(eis_region);
_public_ struct eis_region *
eis_device_new_region(struct eis_device *device)
{
switch (device->type) {
case EIS_DEVICE_TYPE_VIRTUAL:
break;
case EIS_DEVICE_TYPE_PHYSICAL:
log_bug_client(eis_device_get_context(device), "Regions on physical devices are not supported\n");
return NULL;
}
struct eis_region *region = eis_region_create(NULL);
region->device = eis_device_ref(device);

View file

@ -65,6 +65,8 @@ struct eis_touch;
/**
* @struct eis_region
*
* Regions are only available on devices of type @ref EIS_DEVICE_TYPE_VIRTUAL.
*
* 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.
@ -89,6 +91,32 @@ struct eis_touch;
*/
struct eis_region;
/**
* @enum eis_device_type
*
* The device type determines what the device represents.
*
* If the device type is @ref EIS_DEVICE_TYPE_VIRTUAL, the device is a
* virtual device representing input as applied on the EIS implementation's
* screen. A relative virtual device generates input events in logical pixels,
* an absolute virtual device generates input events in logical pixels on one
* of the device's regions. Virtual devices do not have a size.
*
* If the device type is @ref EIS_DEVICE_TYPE_PHYSICAL, the device is a
* representation of a physical device as if connected to the EIS
* implementation's host computer. A relative physical device generates input
* events in mm, an absolute physical device generates input events in mm
* within the device's specified physical size. Physical devices do not have
* regions.
*
* @see eis_device_get_width
* @see eis_device_get_height
*/
enum eis_device_type {
EIS_DEVICE_TYPE_VIRTUAL = 1,
EIS_DEVICE_TYPE_PHYSICAL
};
enum eis_device_capability {
EIS_DEVICE_CAP_POINTER = 1,
EIS_DEVICE_CAP_POINTER_ABSOLUTE,
@ -185,12 +213,13 @@ enum eis_event_type {
/* These events are only generated on a passive EIS context */
/**
* A relative motion event with delta coordinates
* A relative motion event with delta coordinates in logical pixels or
* mm, depending on the device type.
*/
EIS_EVENT_POINTER_MOTION = 300,
/**
* An absolute motion event with absolute position within the device's
* regions.
* regions or size, depending on the device type.
*/
EIS_EVENT_POINTER_MOTION_ABSOLUTE,
/**
@ -199,7 +228,7 @@ enum eis_event_type {
EIS_EVENT_POINTER_BUTTON,
/**
* A vertical and/or horizontal scroll event with logical-pixels
* precision.
* or mm precision, depending on the device type.
*/
EIS_EVENT_POINTER_SCROLL,
/**
@ -611,6 +640,20 @@ bool
eis_device_has_capability(struct eis_device *device,
enum eis_device_capability cap);
/**
* Return the width in mm of a device of type @ref EIS_DEVICE_TYPE_PHYSICAL,
* or zero otherwise.
*/
uint32_t
eis_device_get_width(struct eis_device *device);
/**
* Return the height in mm of a device of type @ref EIS_DEVICE_TYPE_PHYSICAL,
* or zero otherwise.
*/
uint32_t
eis_device_get_height(struct eis_device *device);
/**
* Create a new device on the seat. This device is not immediately active, use
* eis_device_add() to notify the client of it's availability.
@ -620,14 +663,29 @@ eis_device_has_capability(struct eis_device *device,
*
* Before calling eis_device_add(), use the following functions to set up the
* device:
* - eis_device_configure_type()
* - eis_device_configure_name()
* - eis_device_configure_capability()
* - eis_device_new_region()
* - eis_device_new_keymap()
*
* The device type of the device defaults to @ref EIS_DEVICE_TYPE_VIRTUAL.
*/
struct eis_device *
eis_seat_new_device(struct eis_seat *seat);
/**
* Set the device type for this device. It is recommended that that the device
* type is the first call to configure the device as the device type
* influences which other properties on the device can be set and/or will
* trigger warnings if invoked with wrong arguments.
*/
void
eis_device_configure_type(struct eis_device *device, enum eis_device_type type);
enum eis_device_type
eis_device_get_type(struct eis_device *device);
void
eis_device_configure_name(struct eis_device *device, const char *name);
@ -635,10 +693,29 @@ void
eis_device_configure_capability(struct eis_device *device, enum eis_device_capability cap);
/**
* Create a new region on the device with an initial refcount of 1.
* Use eis_region_add() to properly add the region to the device.
* Configure the size in mm of a device of type @ref EIS_DEVICE_TYPE_PHYSICAL.
*
* Device with relative-only capabilities does not require a size. A device
* with capability @ref EIS_DEVICE_CAP_POINTER_ABSOLUTE or @ref
* EIS_DEVICE_CAP_TOUCH must have a size.
*
* This function has no effect if called on a device of type other than @ref
* EIS_DEVICE_TYPE_PHYSICAL.
*
* This function has no effect if called after ei_device_add()
*/
void
eis_device_configure_size(struct eis_device *device, uint32_t width, uint32_t height);
/**
* Create a new region on the device of type @ref EIS_DEVICE_TYPE_VIRTUAL with
* an initial refcount of 1. Use eis_region_add() to properly add the region
* to the device.
*
* A region **must** have a size to be valid, see eis_region_set_size().
*
* For a device of type @ref EIS_DEVICE_TYPE_PHYSICAL this function returns
* NULL.
*/
struct eis_region *
eis_device_new_region(struct eis_device *device);
@ -966,28 +1043,28 @@ eis_event_get_device(struct eis_event *event);
/**
* For an event of type @ref EIS_EVENT_POINTER_MOTION return the relative x
* movement in logical pixels.
* movement in logical pixels or mm, depending on the device type.
*/
double
eis_event_pointer_get_dx(struct eis_event *event);
/**
* For an event of type @ref EIS_EVENT_POINTER_MOTION return the relative y
* movement in logical pixels.
* movement in logical pixels or mm, depending on the device type.
*/
double
eis_event_pointer_get_dy(struct eis_event *event);
/**
* For an event of type @ref EIS_EVENT_POINTER_MOTION_ABSOLUTE return the x
* position in logical pixels.
* position in logical pixels or mm, depending on the device type.
*/
double
eis_event_pointer_get_absolute_x(struct eis_event *event);
/**
* For an event of type @ref EIS_EVENT_POINTER_MOTION_ABSOLUTE return the y
* position in logical pixels.
* position in logical pixels or mm, depending on the device type.
*/
double
eis_event_pointer_get_absolute_y(struct eis_event *event);
@ -1008,14 +1085,14 @@ eis_event_pointer_get_button_is_press(struct eis_event *event);
/**
* For an event of type @ref EIS_EVENT_POINTER_SCROLL return the x scroll
* distance in logical pixels.
* distance in logical pixels or mm, depending on the device type.
*/
double
eis_event_pointer_get_scroll_x(struct eis_event *event);
/**
* For an event of type @ref EIS_EVENT_POINTER_SCROLL return the y scroll
* distance in logical pixels.
* distance in logical pixels or mm, depending on the device type.
*/
double
eis_event_pointer_get_scroll_y(struct eis_event *event);
@ -1083,14 +1160,16 @@ eis_event_touch_get_id(struct eis_event *event);
/**
* For an event of type @ref EIS_EVENT_TOUCH_DOWN, or @ref
* EIS_EVENT_TOUCH_MOTION, return the x coordinate of the touch.
* EIS_EVENT_TOUCH_MOTION, return the x coordinate of the touch
* in logical pixels or mm, depending on the device type.
*/
double
eis_event_touch_get_x(struct eis_event *event);
/**
* For an event of type @ref EIS_EVENT_TOUCH_DOWN, or @ref
* EIS_EVENT_TOUCH_MOTION, return the y coordinate of the touch.
* EIS_EVENT_TOUCH_MOTION, return the y coordinate of the touch
* in logical pixels or mm, depending on the device type.
*/
double
eis_event_touch_get_y(struct eis_event *event);

View file

@ -48,6 +48,9 @@ MUNIT_TEST(test_ei_device_basics)
struct eis_seat *seat = peck_eis_get_default_seat(peck);
_unref_(eis_device) *device = eis_seat_new_device(seat);
/* The default value */
munit_assert_int(eis_device_get_type(device), ==, EIS_DEVICE_TYPE_VIRTUAL);
eis_device_configure_name(device, "string is freed");
munit_assert_string_equal(eis_device_get_name(device), "string is freed");
@ -83,6 +86,7 @@ MUNIT_TEST(test_ei_device_basics)
struct ei_device *device = ei_event_get_device(event);
munit_assert_not_null(device);
munit_assert_int(ei_device_get_type(device), ==, EI_DEVICE_TYPE_VIRTUAL);
munit_assert_string_equal(ei_device_get_name(device), __func__);
munit_assert_true(ei_device_has_capability(device, EI_DEVICE_CAP_POINTER));
@ -94,6 +98,48 @@ MUNIT_TEST(test_ei_device_basics)
return MUNIT_OK;
}
MUNIT_TEST(test_passive_ei_device_type)
{
_unref_(peck) *peck = peck_new();
peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
peck_dispatch_until_stable(peck);
with_server(peck) {
struct eis_seat *seat = peck_eis_get_default_seat(peck);
_unref_(eis_device) *phys = eis_seat_new_device(seat);
eis_device_configure_type(phys, EIS_DEVICE_TYPE_PHYSICAL);
munit_assert_int(eis_device_get_type(phys), ==, EIS_DEVICE_TYPE_PHYSICAL);
eis_device_add(phys);
/* noop after add */
eis_device_configure_type(phys, EIS_DEVICE_TYPE_VIRTUAL);
_unref_(eis_device) *virt = eis_seat_new_device(seat);
eis_device_configure_type(virt, EIS_DEVICE_TYPE_VIRTUAL);
munit_assert_int(eis_device_get_type(virt), ==, EIS_DEVICE_TYPE_VIRTUAL);
eis_device_add(virt);
/* noop after add */
eis_device_configure_type(virt, EIS_DEVICE_TYPE_PHYSICAL);
}
peck_dispatch_until_stable(peck);
with_client(peck) {
_unref_(ei_event) *event_phys = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED);
struct ei_device *phys = ei_event_get_device(event_phys);
munit_assert_int(ei_device_get_type(phys), ==, EI_DEVICE_TYPE_PHYSICAL);
_unref_(ei_event) *event_virt = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED);
struct ei_device *virt = ei_event_get_device(event_virt);
munit_assert_int(ei_device_get_type(virt), ==, EI_DEVICE_TYPE_VIRTUAL);
}
return MUNIT_OK;
}
MUNIT_TEST(test_ei_device_set_name_multiple_devices)
{
_unref_(peck) *peck = peck_new();