tablet: implement support for area configuration for external tablets
authorPeter Hutterer <peter.hutterer@who-t.net>
Thu, 13 Jun 2024 00:31:37 +0000 (10:31 +1000)
committerPeter Hutterer <peter.hutterer@who-t.net>
Tue, 5 Nov 2024 02:10:48 +0000 (12:10 +1000)
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: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1013>

src/evdev-tablet.c
src/evdev-tablet.h
src/evdev-totem.c
src/libinput-private.h
src/libinput.c
test/test-tablet.c

index fe5db8e07520a8fca010d0940cf406d1a7b1569f..27177b5356994181535e5cb7b73cf6b7f8c05efb 100644 (file)
@@ -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)
index e3490cd8c77182ff0bc49ef852534684c818d882..3d34c446e865bdf85a092f95e55c2baf80f30139 100644 (file)
@@ -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;
index 94a3932bcf95cc838ffbf0ae41740a28e35cac9d..412192de62971fd803b064032b61827395fdc35d 100644 (file)
@@ -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;
        }
index 6a0267f9aabd849b496c99cdfe143371588bdc07..baca85aa1a1a7f66a50a38f382a7e0e2bab750dc 100644 (file)
@@ -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,
index 7bf26c7dfdc2c86047c86bb23f92d8b3243168d6..783926d047a4082554948b4d55d8cbabdc978f77 100644 (file)
@@ -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);
 }
 
index f33dc4066852ce7fdc4a0a11f04314b1579ff91b..e7daa219d9f59d5e77a0615c8a2caa24c3a057b8 100644 (file)
@@ -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);