From 0b28eeea5ab911e30ce9d93df47f8f2776bdf167 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Thu, 13 Jun 2024 10:31:37 +1000 Subject: [PATCH] tablet: implement support for area configuration for external tablets For external tablets like the Intuos series we now expose the area rectangle configuration and the (minimum) implementation required to make this work. Because an area configuration may apply late and tablet events usually get scaled by the compositor we need to store the current axis extents in each event. This is to behave correctly in this events sequence: 1. tool proximity in 2. caller changes config, config is pending 3. tool moves, generates events 4. tool goes out of prox, new config applies 5. caller processes motion events from step 3 If the caller in step five uses any of the get_x_transformed calls these need to be scaled relative to the original area, not the one set in step 2. The current implementation merely clips into the area so moving a stylus outside the area will be equivalent to moving it along the respective edge of the area. It's not a true dead zone yet. Part-of: --- src/evdev-tablet.c | 159 +++++++++++++++++++++++++++++--- src/evdev-tablet.h | 7 ++ src/evdev-totem.c | 45 ++++++--- src/libinput-private.h | 16 +++- src/libinput.c | 56 ++++++++---- test/test-tablet.c | 202 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 438 insertions(+), 47 deletions(-) diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c index fe5db8e0..27177b53 100644 --- a/src/evdev-tablet.c +++ b/src/evdev-tablet.c @@ -260,6 +260,40 @@ tablet_process_absolute(struct tablet_dispatch *tablet, } } +static inline int +axis_range_percentage(const struct input_absinfo *a, double percent) +{ + return (a->maximum - a->minimum) * percent/100.0 + a->minimum; +} + +static void +tablet_change_area(struct evdev_device *device) +{ + struct tablet_dispatch *tablet = tablet_dispatch(device->dispatch); + + if (memcmp(&tablet->area.rect, &tablet->area.want_rect, sizeof(tablet->area.rect)) == 0) + return; + + if (!tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY)) + return; + + tablet->area.rect = tablet->area.want_rect; + + evdev_log_debug(device, + "tablet-area: area is %.2f/%.2f - %.2f/%.2f\n", + tablet->area.rect.x1, + tablet->area.rect.y1, + tablet->area.rect.x2, + tablet->area.rect.y2); + + const struct input_absinfo *absx = device->abs.absinfo_x; + const struct input_absinfo *absy = device->abs.absinfo_y; + tablet->area.x.minimum = axis_range_percentage(absx, tablet->area.rect.x1 * 100); + tablet->area.x.maximum = axis_range_percentage(absx, tablet->area.rect.x2 * 100); + tablet->area.y.minimum = axis_range_percentage(absy, tablet->area.rect.y1 * 100); + tablet->area.y.maximum = axis_range_percentage(absy, tablet->area.rect.y2 * 100); +} + static void tablet_apply_rotation(struct evdev_device *device) { @@ -442,6 +476,31 @@ normalize_wheel(struct tablet_dispatch *tablet, return value * device->scroll.wheel_click_angle.x; } +static void +apply_tablet_area(struct tablet_dispatch *tablet, + struct evdev_device *device, + struct device_coords *point) +{ + if (tablet->area.rect.x1 == 0.0 && tablet->area.rect.x2 == 1.0 && + tablet->area.rect.y1 == 0.0 && tablet->area.rect.y2 == 1.0) + return; + + /* The point is somewhere on the tablet in device coordinates, + * but we need it relative to the x/y offset. + * So clip it first, then offset it to our area min/max. + * + * Right now we're just clipping, we don't completely + * ignore events. What we should do is ignore events outside + * altogether and generate prox in/out events when we actually + * enter the area. + */ + point->x = min(point->x, tablet->area.x.maximum); + point->y = min(point->y, tablet->area.y.maximum); + + point->x = max(point->x, tablet->area.x.minimum); + point->y = max(point->y, tablet->area.y.minimum); +} + static inline void tablet_update_xy(struct tablet_dispatch *tablet, struct evdev_device *device) @@ -473,7 +532,10 @@ tablet_update_xy(struct tablet_dispatch *tablet, tablet->axes.point.y = value; + /* calibration and area are currently mutually exclusive so + * one of those is a noop */ evdev_transform_absolute(device, &tablet->axes.point); + apply_tablet_area(tablet, device, &tablet->axes.point); } } @@ -1070,12 +1132,6 @@ tool_set_bits(const struct tablet_dispatch *tablet, } } -static inline int -axis_range_percentage(const struct input_absinfo *a, double percent) -{ - return (a->maximum - a->minimum) * percent/100.0 + a->minimum; -} - static bool tablet_get_quirked_pressure_thresholds(struct tablet_dispatch *tablet, int *hi, @@ -1338,7 +1394,9 @@ tablet_notify_button_mask(struct tablet_dispatch *tablet, tip_state, &tablet->axes, i, - state); + state, + &tablet->area.x, + &tablet->area.y); } } @@ -1737,7 +1795,9 @@ tablet_send_proximity_in(struct tablet_dispatch *tablet, tool, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN, tablet->changed_axes, - axes); + axes, + &tablet->area.x, + &tablet->area.y); tablet_unset_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY); tablet_unset_status(tablet, TABLET_AXES_UPDATED); @@ -1763,7 +1823,9 @@ tablet_send_proximity_out(struct tablet_dispatch *tablet, tool, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT, tablet->changed_axes, - axes); + axes, + &tablet->area.x, + &tablet->area.y); tablet_set_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY); tablet_unset_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY); @@ -1788,7 +1850,9 @@ tablet_send_tip(struct tablet_dispatch *tablet, tool, LIBINPUT_TABLET_TOOL_TIP_DOWN, tablet->changed_axes, - axes); + axes, + &tablet->area.x, + &tablet->area.y); tablet_unset_status(tablet, TABLET_AXES_UPDATED); tablet_unset_status(tablet, TABLET_TOOL_ENTERING_CONTACT); tablet_set_status(tablet, TABLET_TOOL_IN_CONTACT); @@ -1806,7 +1870,9 @@ tablet_send_tip(struct tablet_dispatch *tablet, tool, LIBINPUT_TABLET_TOOL_TIP_UP, tablet->changed_axes, - axes); + axes, + &tablet->area.x, + &tablet->area.y); tablet_unset_status(tablet, TABLET_AXES_UPDATED); tablet_unset_status(tablet, TABLET_TOOL_LEAVING_CONTACT); tablet_unset_status(tablet, TABLET_TOOL_IN_CONTACT); @@ -1844,7 +1910,9 @@ tablet_send_axes(struct tablet_dispatch *tablet, tool, tip_state, tablet->changed_axes, - axes); + axes, + &tablet->area.x, + &tablet->area.y); tablet_unset_status(tablet, TABLET_AXES_UPDATED); tablet_reset_changed_axes(tablet); axes->delta.x = 0; @@ -1914,6 +1982,7 @@ tablet_send_events(struct tablet_dispatch *tablet, if (tablet_send_proximity_out(tablet, tool, device, &axes, time)) { tablet_change_to_left_handed(device); tablet_apply_rotation(device); + tablet_change_area(device); tablet_history_reset(tablet); } } @@ -2501,6 +2570,71 @@ tablet_init_calibration(struct tablet_dispatch *tablet, evdev_init_calibration(device, &tablet->calibration); } +static int +tablet_area_has_rectangle(struct libinput_device *device) +{ + return 1; +} + +static enum libinput_config_status +tablet_area_set_rectangle(struct libinput_device *device, + const struct libinput_config_area_rectangle *rectangle) +{ + struct evdev_device *evdev = evdev_device(device); + struct tablet_dispatch *tablet = tablet_dispatch(evdev->dispatch); + + if (rectangle->x1 >= rectangle->x2 || rectangle->y1 >= rectangle->y2) + return LIBINPUT_CONFIG_STATUS_INVALID; + + if (rectangle->x1 < 0.0 || rectangle->x2 > 1.0 || + rectangle->y1 < 0.0 || rectangle->y2 > 1.0) + return LIBINPUT_CONFIG_STATUS_INVALID; + + tablet->area.want_rect = *rectangle; + + tablet_change_area(evdev); + + return LIBINPUT_CONFIG_STATUS_SUCCESS; +} + +static struct libinput_config_area_rectangle +tablet_area_get_rectangle(struct libinput_device *device) +{ + struct evdev_device *evdev = evdev_device(device); + struct tablet_dispatch *tablet = tablet_dispatch(evdev->dispatch); + + return tablet->area.rect; +} + +static struct libinput_config_area_rectangle +tablet_area_get_default_rectangle(struct libinput_device *device) +{ + struct libinput_config_area_rectangle rect = { + 0.0, 0.0, 1.0, 1.0, + }; + return rect; +} + +static void +tablet_init_area(struct tablet_dispatch *tablet, + struct evdev_device *device) +{ + tablet->area.rect = (struct libinput_config_area_rectangle) { + 0.0, 0.0, 1.0, 1.0, + }; + tablet->area.want_rect = tablet->area.rect; + tablet->area.x = *device->abs.absinfo_x; + tablet->area.y = *device->abs.absinfo_y; + + if (!libevdev_has_property(device->evdev, INPUT_PROP_DIRECT)) { + device->base.config.area = &tablet->area.config; + tablet->area.config.has_rectangle = tablet_area_has_rectangle; + tablet->area.config.set_rectangle = tablet_area_set_rectangle; + tablet->area.config.get_rectangle = tablet_area_get_rectangle; + tablet->area.config.get_default_rectangle = tablet_area_get_default_rectangle; + } +} + static void tablet_init_proximity_threshold(struct tablet_dispatch *tablet, struct evdev_device *device) @@ -2791,6 +2925,7 @@ tablet_init(struct tablet_dispatch *tablet, tablet_fix_tilt(tablet, device); tablet_init_calibration(tablet, device, is_display_tablet); + tablet_init_area(tablet, device); tablet_init_proximity_threshold(tablet, device); rc = tablet_init_accel(tablet, device); if (rc != 0) diff --git a/src/evdev-tablet.h b/src/evdev-tablet.h index e3490cd8..3d34c446 100644 --- a/src/evdev-tablet.h +++ b/src/evdev-tablet.h @@ -93,6 +93,13 @@ struct tablet_dispatch { uint32_t cursor_proximity_threshold; struct libinput_device_config_calibration calibration; + struct { + struct libinput_device_config_area config; + struct libinput_config_area_rectangle rect; + struct libinput_config_area_rectangle want_rect; + struct input_absinfo x; + struct input_absinfo y; + } area; /* The paired touch device on devices with both pen & touch */ struct evdev_device *touch_device; diff --git a/src/evdev-totem.c b/src/evdev-totem.c index 94a3932b..412192de 100644 --- a/src/evdev-totem.c +++ b/src/evdev-totem.c @@ -401,14 +401,18 @@ totem_handle_slot_state(struct totem_dispatch *totem, slot->tool, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN, slot->changed_axes, - &axes); + &axes, + device->abs.absinfo_x, + device->abs.absinfo_y); totem_slot_reset_changed_axes(totem, slot); tablet_notify_tip(&device->base, time, slot->tool, tip_state, slot->changed_axes, - &axes); + &axes, + device->abs.absinfo_x, + device->abs.absinfo_y); slot->state = SLOT_STATE_UPDATE; break; case SLOT_STATE_UPDATE: @@ -419,7 +423,9 @@ totem_handle_slot_state(struct totem_dispatch *totem, slot->tool, tip_state, slot->changed_axes, - &axes); + &axes, + device->abs.absinfo_x, + device->abs.absinfo_y); } break; case SLOT_STATE_END: @@ -452,7 +458,9 @@ totem_handle_slot_state(struct totem_dispatch *totem, tip_state, &axes, BTN_0, - btn_state); + btn_state, + device->abs.absinfo_x, + device->abs.absinfo_y); totem->button_state_previous = totem->button_state_now; } @@ -468,14 +476,19 @@ totem_handle_slot_state(struct totem_dispatch *totem, slot->tool, tip_state, slot->changed_axes, - &axes); + &axes, + device->abs.absinfo_x, + device->abs.absinfo_y); totem_slot_reset_changed_axes(totem, slot); tablet_notify_proximity(&device->base, time, slot->tool, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT, slot->changed_axes, - &axes); + &axes, + device->abs.absinfo_x, + device->abs.absinfo_y); + slot->state = SLOT_STATE_NONE; break; case SLOT_STATE_NONE: @@ -577,7 +590,9 @@ totem_interface_suspend(struct evdev_dispatch *dispatch, tip_state, &axes, BTN_0, - LIBINPUT_BUTTON_STATE_RELEASED); + LIBINPUT_BUTTON_STATE_RELEASED, + device->abs.absinfo_x, + device->abs.absinfo_y); totem->button_state_now = false; totem->button_state_previous = false; @@ -589,14 +604,18 @@ totem_interface_suspend(struct evdev_dispatch *dispatch, slot->tool, LIBINPUT_TABLET_TOOL_TIP_UP, slot->changed_axes, - &axes); + &axes, + device->abs.absinfo_x, + device->abs.absinfo_y); } tablet_notify_proximity(&device->base, now, slot->tool, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT, slot->changed_axes, - &axes); + &axes, + device->abs.absinfo_x, + device->abs.absinfo_y); } totem_set_touch_device_enabled(totem, true, now); } @@ -683,14 +702,18 @@ totem_interface_initial_proximity(struct evdev_device *device, slot->tool, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN, slot->changed_axes, - &axes); + &axes, + device->abs.absinfo_x, + device->abs.absinfo_y); totem_slot_reset_changed_axes(totem, slot); tablet_notify_tip(&device->base, now, slot->tool, LIBINPUT_TABLET_TOOL_TIP_DOWN, slot->changed_axes, - &axes); + &axes, + device->abs.absinfo_x, + device->abs.absinfo_y); slot->state = SLOT_STATE_UPDATE; enable_touch = false; } diff --git a/src/libinput-private.h b/src/libinput-private.h index 6a0267f9..baca85aa 100644 --- a/src/libinput-private.h +++ b/src/libinput-private.h @@ -777,7 +777,9 @@ tablet_notify_axis(struct libinput_device *device, struct libinput_tablet_tool *tool, enum libinput_tablet_tool_tip_state tip_state, unsigned char *changed_axes, - const struct tablet_axes *axes); + const struct tablet_axes *axes, + const struct input_absinfo *x, + const struct input_absinfo *y); void tablet_notify_proximity(struct libinput_device *device, @@ -785,7 +787,9 @@ tablet_notify_proximity(struct libinput_device *device, struct libinput_tablet_tool *tool, enum libinput_tablet_tool_proximity_state state, unsigned char *changed_axes, - const struct tablet_axes *axes); + const struct tablet_axes *axes, + const struct input_absinfo *x, + const struct input_absinfo *y); void tablet_notify_tip(struct libinput_device *device, @@ -793,7 +797,9 @@ tablet_notify_tip(struct libinput_device *device, struct libinput_tablet_tool *tool, enum libinput_tablet_tool_tip_state tip_state, unsigned char *changed_axes, - const struct tablet_axes *axes); + const struct tablet_axes *axes, + const struct input_absinfo *x, + const struct input_absinfo *y); void tablet_notify_button(struct libinput_device *device, @@ -802,7 +808,9 @@ tablet_notify_button(struct libinput_device *device, enum libinput_tablet_tool_tip_state tip_state, const struct tablet_axes *axes, int32_t button, - enum libinput_button_state state); + enum libinput_button_state state, + const struct input_absinfo *x, + const struct input_absinfo *y); void tablet_pad_notify_button(struct libinput_device *device, diff --git a/src/libinput.c b/src/libinput.c index 7bf26c7d..783926d0 100644 --- a/src/libinput.c +++ b/src/libinput.c @@ -36,6 +36,7 @@ #include "libinput.h" #include "libinput-private.h" +#include "util-input-event.h" #include "evdev.h" #include "timer.h" #include "quirks.h" @@ -219,6 +220,10 @@ struct libinput_event_tablet_tool { struct libinput_tablet_tool *tool; enum libinput_tablet_tool_proximity_state proximity_state; enum libinput_tablet_tool_tip_state tip_state; + struct { + struct input_absinfo x; + struct input_absinfo y; + } abs; }; struct libinput_event_tablet_pad { @@ -1303,8 +1308,6 @@ libinput_event_tablet_tool_wheel_has_changed( LIBINPUT_EXPORT double libinput_event_tablet_tool_get_x(struct libinput_event_tablet_tool *event) { - struct evdev_device *device = evdev_device(event->base.device); - require_event_type(libinput_event_get_context(&event->base), event->base.type, 0, @@ -1313,15 +1316,13 @@ libinput_event_tablet_tool_get_x(struct libinput_event_tablet_tool *event) LIBINPUT_EVENT_TABLET_TOOL_BUTTON, LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); - return absinfo_convert_to_mm(device->abs.absinfo_x, + return absinfo_convert_to_mm(&event->abs.x, event->axes.point.x); } LIBINPUT_EXPORT double libinput_event_tablet_tool_get_y(struct libinput_event_tablet_tool *event) { - struct evdev_device *device = evdev_device(event->base.device); - require_event_type(libinput_event_get_context(&event->base), event->base.type, 0, @@ -1330,7 +1331,7 @@ libinput_event_tablet_tool_get_y(struct libinput_event_tablet_tool *event) LIBINPUT_EVENT_TABLET_TOOL_BUTTON, LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); - return absinfo_convert_to_mm(device->abs.absinfo_y, + return absinfo_convert_to_mm(&event->abs.y, event->axes.point.y); } @@ -1507,8 +1508,6 @@ LIBINPUT_EXPORT double libinput_event_tablet_tool_get_x_transformed(struct libinput_event_tablet_tool *event, uint32_t width) { - struct evdev_device *device = evdev_device(event->base.device); - require_event_type(libinput_event_get_context(&event->base), event->base.type, 0, @@ -1517,17 +1516,13 @@ libinput_event_tablet_tool_get_x_transformed(struct libinput_event_tablet_tool * LIBINPUT_EVENT_TABLET_TOOL_BUTTON, LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); - return evdev_device_transform_x(device, - event->axes.point.x, - width); + return absinfo_scale_axis(&event->abs.x, event->axes.point.x, width); } LIBINPUT_EXPORT double libinput_event_tablet_tool_get_y_transformed(struct libinput_event_tablet_tool *event, uint32_t height) { - struct evdev_device *device = evdev_device(event->base.device); - require_event_type(libinput_event_get_context(&event->base), event->base.type, 0, @@ -1536,9 +1531,7 @@ libinput_event_tablet_tool_get_y_transformed(struct libinput_event_tablet_tool * LIBINPUT_EVENT_TABLET_TOOL_BUTTON, LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); - return evdev_device_transform_y(device, - event->axes.point.y, - height); + return absinfo_scale_axis(&event->abs.y, event->axes.point.y, height); } LIBINPUT_EXPORT struct libinput_tablet_tool * @@ -2772,7 +2765,9 @@ tablet_notify_axis(struct libinput_device *device, struct libinput_tablet_tool *tool, enum libinput_tablet_tool_tip_state tip_state, unsigned char *changed_axes, - const struct tablet_axes *axes) + const struct tablet_axes *axes, + const struct input_absinfo *x, + const struct input_absinfo *y) { struct libinput_event_tablet_tool *axis_event; @@ -2784,6 +2779,8 @@ tablet_notify_axis(struct libinput_device *device, .proximity_state = LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN, .tip_state = tip_state, .axes = *axes, + .abs.x = *x, + .abs.y = *y, }; memcpy(axis_event->changed_axes, @@ -2802,7 +2799,9 @@ tablet_notify_proximity(struct libinput_device *device, struct libinput_tablet_tool *tool, enum libinput_tablet_tool_proximity_state proximity_state, unsigned char *changed_axes, - const struct tablet_axes *axes) + const struct tablet_axes *axes, + const struct input_absinfo *x, + const struct input_absinfo *y) { struct libinput_event_tablet_tool *proximity_event; @@ -2814,6 +2813,8 @@ tablet_notify_proximity(struct libinput_device *device, .tip_state = LIBINPUT_TABLET_TOOL_TIP_UP, .proximity_state = proximity_state, .axes = *axes, + .abs.x = *x, + .abs.y = *y, }; memcpy(proximity_event->changed_axes, changed_axes, @@ -2831,7 +2832,9 @@ tablet_notify_tip(struct libinput_device *device, struct libinput_tablet_tool *tool, enum libinput_tablet_tool_tip_state tip_state, unsigned char *changed_axes, - const struct tablet_axes *axes) + const struct tablet_axes *axes, + const struct input_absinfo *x, + const struct input_absinfo *y) { struct libinput_event_tablet_tool *tip_event; @@ -2843,6 +2846,8 @@ tablet_notify_tip(struct libinput_device *device, .tip_state = tip_state, .proximity_state = LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN, .axes = *axes, + .abs.x = *x, + .abs.y = *y, }; memcpy(tip_event->changed_axes, changed_axes, @@ -2861,7 +2866,9 @@ tablet_notify_button(struct libinput_device *device, enum libinput_tablet_tool_tip_state tip_state, const struct tablet_axes *axes, int32_t button, - enum libinput_button_state state) + enum libinput_button_state state, + const struct input_absinfo *x, + const struct input_absinfo *y) { struct libinput_event_tablet_tool *button_event; int32_t seat_button_count; @@ -2881,6 +2888,8 @@ tablet_notify_button(struct libinput_device *device, .proximity_state = LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN, .tip_state = tip_state, .axes = *axes, + .abs.x = *x, + .abs.y = *y, }; post_device_event(device, @@ -4148,6 +4157,13 @@ libinput_device_config_area_set_rectangle(struct libinput_device *device, if (!libinput_device_config_area_has_rectangle(device)) return LIBINPUT_CONFIG_STATUS_UNSUPPORTED; + if (rectangle->x1 >= rectangle->x2 || rectangle->y1 >= rectangle->y2) + return LIBINPUT_CONFIG_STATUS_INVALID; + + if (rectangle->x1 < 0.0 || rectangle->x2 > 1.0 || + rectangle->y1 < 0.0 || rectangle->y2 > 1.0) + return LIBINPUT_CONFIG_STATUS_INVALID; + return device->config.area->set_rectangle(device, rectangle); } diff --git a/test/test-tablet.c b/test/test-tablet.c index f33dc406..e7daa219 100644 --- a/test/test-tablet.c +++ b/test/test-tablet.c @@ -3891,6 +3891,203 @@ START_TEST(tablet_calibration_set_matrix) } END_TEST +START_TEST(tablet_area_has_rectangle) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device *d = dev->libinput_device; + enum libinput_config_status status; + int rc; + struct libinput_config_area_rectangle rect; + + int has_area = !libevdev_has_property(dev->evdev, INPUT_PROP_DIRECT); + + rc = libinput_device_config_area_has_rectangle(d); + litest_assert_int_eq(rc, has_area); + rect = libinput_device_config_area_get_rectangle(d); + litest_assert_double_eq(rect.x1, 0.0); + litest_assert_double_eq(rect.y1, 0.0); + litest_assert_double_eq(rect.x2, 1.0); + litest_assert_double_eq(rect.y2, 1.0); + + rect = libinput_device_config_area_get_default_rectangle(d); + litest_assert_double_eq(rect.x1, 0.0); + litest_assert_double_eq(rect.y1, 0.0); + litest_assert_double_eq(rect.x2, 1.0); + litest_assert_double_eq(rect.y2, 1.0); + + status = libinput_device_config_area_set_rectangle(d, &rect); + if (has_area) + litest_assert_enum_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + else + litest_assert_enum_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED); +} +END_TEST + +START_TEST(tablet_area_set_rectangle_invalid) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device *d = dev->libinput_device; + int rc; + struct libinput_config_area_rectangle rect; + + int has_area = !libevdev_has_property(dev->evdev, INPUT_PROP_DIRECT); + if (!has_area) + return LITEST_NOT_APPLICABLE; + + rect.x1 = 1.0; + rect.x2 = 0.9; + rect.y1 = 0.0; + rect.y2 = 1.0; + + rc = libinput_device_config_area_set_rectangle(d, &rect); + litest_assert_enum_eq(rc, LIBINPUT_CONFIG_STATUS_INVALID); + + rect.x1 = 0.9; + rect.x2 = 1.0; + rect.y1 = 1.0; + rect.y2 = 0.9; + + rc = libinput_device_config_area_set_rectangle(d, &rect); + litest_assert_enum_eq(rc, LIBINPUT_CONFIG_STATUS_INVALID); + + rect.x1 = 0.9; + rect.x2 = 0.9; + rect.y1 = 0.9; + rect.y2 = 1.0; + + rc = libinput_device_config_area_set_rectangle(d, &rect); + litest_assert_enum_eq(rc, LIBINPUT_CONFIG_STATUS_INVALID); + + rect.x1 = 0.9; + rect.x2 = 1.0; + rect.y1 = 0.9; + rect.y2 = 0.9; + + rc = libinput_device_config_area_set_rectangle(d, &rect); + litest_assert_enum_eq(rc, LIBINPUT_CONFIG_STATUS_INVALID); + + rect.x1 = 0.9; + rect.x2 = 1.5; + rect.y1 = 0.0; + rect.y2 = 1.0; + + rc = libinput_device_config_area_set_rectangle(d, &rect); + litest_assert_enum_eq(rc, LIBINPUT_CONFIG_STATUS_INVALID); + + rect.x1 = 0.0; + rect.x2 = 1.0; + rect.y1 = 0.9; + rect.y2 = 1.4; + + rc = libinput_device_config_area_set_rectangle(d, &rect); + litest_assert_enum_eq(rc, LIBINPUT_CONFIG_STATUS_INVALID); + +} +END_TEST + +static void +get_tool_xy(struct libinput *li, double *x, double *y) +{ + struct libinput_event *event = libinput_get_event(li); + struct libinput_event_tablet_tool *tev; + + litest_assert_ptr_notnull(event); + + switch (libinput_event_get_type(event)) { + case LIBINPUT_EVENT_TABLET_TOOL_AXIS: + tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS); + break; + case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: + tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + break; + default: + abort(); + } + + *x = libinput_event_tablet_tool_get_x_transformed(tev, 100); + *y = libinput_event_tablet_tool_get_y_transformed(tev, 100); + libinput_event_destroy(event); +} + +START_TEST(tablet_area_set_rectangle) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_device *d = dev->libinput_device; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + double x, y; + double *scaled, *unscaled; + bool use_vertical = !!_i; /* ranged test */ + + if (libevdev_has_property(dev->evdev, INPUT_PROP_DIRECT)) + return LITEST_NOT_APPLICABLE; + + struct libinput_config_area_rectangle rect; + if (use_vertical) { + rect = (struct libinput_config_area_rectangle){ + 0.25, 0.0, 0.75, 1.0, + }; + scaled = &x; + unscaled = &y; + } else { + rect = (struct libinput_config_area_rectangle){ + 0.0, 0.25, 1.0, 0.75, + }; + scaled = &y; + unscaled = &x; + } + + enum libinput_config_status status = libinput_device_config_area_set_rectangle(d, &rect); + litest_assert_enum_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + + litest_drain_events(li); + + /* move vertically through the center */ + litest_tablet_proximity_in(dev, 5, 5, axes); + libinput_dispatch(li); + get_tool_xy(li, &x, &y); + litest_assert_double_eq_epsilon(*scaled, 0.0, 2); + litest_assert_double_eq_epsilon(*unscaled, 5.0, 2); + + for (int i = 10; i <= 100; i += 5) { + /* Negate any smoothing */ + litest_tablet_motion(dev, i, i, axes); + litest_tablet_motion(dev, i - 1, i, axes); + litest_tablet_motion(dev, i, i - 1, axes); + litest_drain_events(li); + + litest_tablet_motion(dev, i, i, axes); + libinput_dispatch(li); + get_tool_xy(li, &x, &y); + if (i <= 25) + litest_assert_double_eq(*scaled, 0.0); + else if (i > 75) + litest_assert_double_eq_epsilon(*scaled, 100.0, 1); + else + litest_assert_double_eq_epsilon(*scaled, (i - 25) * 2, 1); + litest_assert_double_eq_epsilon(*unscaled, i, 2); + } + + /* Push through any smoothing */ + litest_tablet_motion(dev, 100, 100, axes); + litest_tablet_motion(dev, 100, 100, axes); + libinput_dispatch(li); + litest_drain_events(li); + + litest_tablet_proximity_out(dev); + litest_timeout_tablet_proxout(); + libinput_dispatch(li); + get_tool_xy(li, &x, &y); + litest_assert_double_eq_epsilon(x, 100, 1); + litest_assert_double_eq_epsilon(y, 100, 1); + +} +END_TEST + static void assert_pressure(struct libinput *li, enum libinput_event_type type, double expected_pressure) { @@ -6571,6 +6768,7 @@ TEST_COLLECTION(tablet) struct range with_timeout = { 0, 2 }; struct range xyaxes = { ABS_X, ABS_Y + 1 }; struct range tilt_cases = {TILT_MINIMUM, TILT_MAXIMUM + 1}; + struct range vert_horiz = { 0, 2 }; litest_add(tool_ref, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY); litest_add(tool_user_data, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY); @@ -6652,6 +6850,10 @@ TEST_COLLECTION(tablet) litest_add(tablet_calibration_set_matrix, LITEST_TABLET, LITEST_TOTEM|LITEST_PRECALIBRATED); litest_add(tablet_calibration_set_matrix_delta, LITEST_TABLET, LITEST_TOTEM|LITEST_PRECALIBRATED); + litest_add(tablet_area_has_rectangle, LITEST_TABLET, LITEST_ANY); + litest_add(tablet_area_set_rectangle_invalid, LITEST_TABLET, LITEST_ANY); + litest_add_ranged(tablet_area_set_rectangle, LITEST_TABLET, LITEST_ANY, &vert_horiz); + litest_add(tablet_pressure_min_max, LITEST_TABLET, LITEST_ANY); /* Tests for pressure offset with distance */ litest_add_for_device(tablet_pressure_range, LITEST_WACOM_INTUOS); -- 2.34.1