summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/evdev-tablet.c88
-rw-r--r--src/evdev-tablet.h1
-rw-r--r--test/test-tablet.c221
3 files changed, 280 insertions, 30 deletions
diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c
index 27177b53..7ff97f2d 100644
--- a/src/evdev-tablet.c
+++ b/src/evdev-tablet.c
@@ -476,6 +476,27 @@ normalize_wheel(struct tablet_dispatch *tablet,
return value * device->scroll.wheel_click_angle.x;
}
+static bool
+is_inside_area(struct tablet_dispatch *tablet,
+ const struct device_coords *point,
+ double normalized_margin)
+{
+ 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 true;
+
+ assert(normalized_margin > 0.0);
+ assert(normalized_margin <= 1.0);
+
+ int xmargin = (tablet->area.x.maximum - tablet->area.x.minimum) * normalized_margin;
+ int ymargin = (tablet->area.y.maximum - tablet->area.y.minimum) * normalized_margin;
+
+ return (point->x >= tablet->area.x.minimum - xmargin &&
+ point->x <= tablet->area.x.maximum + xmargin &&
+ point->y >= tablet->area.y.minimum - ymargin &&
+ point->y <= tablet->area.y.maximum + ymargin);
+}
+
static void
apply_tablet_area(struct tablet_dispatch *tablet,
struct evdev_device *device,
@@ -1818,17 +1839,20 @@ tablet_send_proximity_out(struct tablet_dispatch *tablet,
if (!tablet_has_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY))
return false;
- tablet_notify_proximity(&device->base,
- time,
- tool,
- LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT,
- tablet->changed_axes,
- axes,
- &tablet->area.x,
- &tablet->area.y);
+ if (!tablet_has_status(tablet, TABLET_TOOL_OUTSIDE_AREA)) {
+ tablet_notify_proximity(&device->base,
+ time,
+ tool,
+ LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT,
+ tablet->changed_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);
+ tablet_unset_status(tablet, TABLET_TOOL_OUTSIDE_AREA);
tablet_reset_changed_axes(tablet);
axes->delta.x = 0;
@@ -2172,19 +2196,45 @@ reprocess:
if (tablet_has_status(tablet, TABLET_TOOL_IN_CONTACT))
tablet_set_status(tablet, TABLET_TOOL_LEAVING_CONTACT);
apply_pressure_range_configuration(tablet, tool);
- } else if (tablet_has_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY)) {
- tablet_mark_all_axes_changed(tablet, tool);
- update_pressure_offset(tablet, device, tool);
- detect_pressure_offset(tablet, device, tool);
- detect_tool_contact(tablet, device, tool);
- sanitize_tablet_axes(tablet, tool);
- } else if (tablet_has_status(tablet, TABLET_AXES_UPDATED)) {
- update_pressure_offset(tablet, device, tool);
- detect_tool_contact(tablet, device, tool);
- sanitize_tablet_axes(tablet, tool);
+ } else if (!tablet_has_status(tablet, TABLET_TOOL_OUTSIDE_AREA)) {
+ if (tablet_has_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY)) {
+ /* If we get into proximity outside the tablet area, we ignore
+ * that whole sequence of events even if we later move into
+ * the allowed area. This may be bad UX but it's complicated to
+ * implement so let's wait for someone to actually complain
+ * about it.
+ *
+ * We allow a margin of 3% (6mm on a 200mm tablet) to be "within"
+ * the area - there we clip to the area but do not ignore the
+ * sequence.
+ */
+ const struct device_coords point = {
+ device->abs.absinfo_x->value,
+ device->abs.absinfo_y->value,
+ };
+
+ const double margin = 0.03;
+ if (is_inside_area(tablet, &point, margin)) {
+ tablet_mark_all_axes_changed(tablet, tool);
+ update_pressure_offset(tablet, device, tool);
+ detect_pressure_offset(tablet, device, tool);
+ detect_tool_contact(tablet, device, tool);
+ sanitize_tablet_axes(tablet, tool);
+ } else {
+ tablet_set_status(tablet, TABLET_TOOL_OUTSIDE_AREA);
+ tablet_unset_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY);
+ }
+ } else if (tablet_has_status(tablet, TABLET_AXES_UPDATED)) {
+ update_pressure_offset(tablet, device, tool);
+ detect_tool_contact(tablet, device, tool);
+ sanitize_tablet_axes(tablet, tool);
+ }
+
}
- tablet_send_events(tablet, tool, device, time);
+ if (!tablet_has_status(tablet, TABLET_TOOL_OUTSIDE_AREA)) {
+ tablet_send_events(tablet, tool, device, time);
+ }
if (process_tool_twice)
goto reprocess;
diff --git a/src/evdev-tablet.h b/src/evdev-tablet.h
index 3d34c446..de0349ef 100644
--- a/src/evdev-tablet.h
+++ b/src/evdev-tablet.h
@@ -51,6 +51,7 @@ enum tablet_status {
TABLET_TOOL_ENTERING_CONTACT = bit(9),
TABLET_TOOL_LEAVING_CONTACT = bit(10),
TABLET_TOOL_OUT_OF_RANGE = bit(11),
+ TABLET_TOOL_OUTSIDE_AREA = bit(12),
};
struct button_state {
diff --git a/test/test-tablet.c b/test/test-tablet.c
index e7daa219..6ee40e53 100644
--- a/test/test-tablet.c
+++ b/test/test-tablet.c
@@ -4021,7 +4021,8 @@ START_TEST(tablet_area_set_rectangle)
};
double x, y;
double *scaled, *unscaled;
- bool use_vertical = !!_i; /* ranged test */
+ bool use_vertical = abs(_i) % 2 == 0; /* ranged test */
+ int direction = _i < 0 ? -1 : 1; /* ranged test */
if (libevdev_has_property(dev->evdev, INPUT_PROP_DIRECT))
return LITEST_NOT_APPLICABLE;
@@ -4046,14 +4047,15 @@ START_TEST(tablet_area_set_rectangle)
litest_drain_events(li);
- /* move vertically through the center */
- litest_tablet_proximity_in(dev, 5, 5, axes);
+ /* move from the center out */
+ litest_tablet_proximity_in(dev, 50, 50, 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);
+ litest_assert_double_eq_epsilon(*scaled, 50.0, 2);
+ litest_assert_double_eq_epsilon(*unscaled, 50.0, 2);
- for (int i = 10; i <= 100; i += 5) {
+ int i;
+ for (i = 50; i > 0 && i <= 100; i += 5 * direction) {
/* Negate any smoothing */
litest_tablet_motion(dev, i, i, axes);
litest_tablet_motion(dev, i - 1, i, axes);
@@ -4072,9 +4074,10 @@ START_TEST(tablet_area_set_rectangle)
litest_assert_double_eq_epsilon(*unscaled, i, 2);
}
+ double final_stop = max(0.0, min(100.0, i));
/* Push through any smoothing */
- litest_tablet_motion(dev, 100, 100, axes);
- litest_tablet_motion(dev, 100, 100, axes);
+ litest_tablet_motion(dev, final_stop, final_stop, axes);
+ litest_tablet_motion(dev, final_stop, final_stop, axes);
libinput_dispatch(li);
litest_drain_events(li);
@@ -4082,9 +4085,202 @@ START_TEST(tablet_area_set_rectangle)
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);
+ litest_assert_double_eq_epsilon(x, final_stop, 1);
+ litest_assert_double_eq_epsilon(y, final_stop, 1);
+
+}
+END_TEST
+
+START_TEST(tablet_area_set_rectangle_move_outside)
+{
+ 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;
+
+ if (libevdev_has_property(dev->evdev, INPUT_PROP_DIRECT))
+ return LITEST_NOT_APPLICABLE;
+
+ struct libinput_config_area_rectangle rect = {
+ 0.25, 0.25, 0.75, 0.75,
+ };
+
+ 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 in/out of prox outside the area */
+ litest_tablet_proximity_in(dev, 5, 5, axes);
+ litest_tablet_proximity_out(dev);
+ libinput_dispatch(li);
+ litest_timeout_tablet_proxout();
+ libinput_dispatch(li);
+ litest_assert_empty_queue(li);
+
+ x = 5;
+ y = 5;
+ /* Move around the area - since we stay outside the area expect no events */
+ litest_tablet_proximity_in(dev, x, y, axes);
+ libinput_dispatch(li);
+ for (; x < 90; x += 5) {
+ litest_tablet_motion(dev, x, y, axes);
+ libinput_dispatch(li);
+ litest_assert_empty_queue(li);
+ }
+ litest_axis_set_value(axes, ABS_PRESSURE, 30);
+ litest_tablet_tip_down(dev, x, y, axes);
+ for (; y < 90; y += 5) {
+ litest_tablet_motion(dev, x, y, axes);
+ libinput_dispatch(li);
+ litest_assert_empty_queue(li);
+ }
+ litest_axis_set_value(axes, ABS_PRESSURE, 0);
+ litest_tablet_tip_up(dev, x, y, axes);
+ for (; x > 5; x -= 5) {
+ litest_tablet_motion(dev, x, y, axes);
+ libinput_dispatch(li);
+ litest_assert_empty_queue(li);
+ }
+ litest_button_click(dev, BTN_STYLUS, LIBINPUT_BUTTON_STATE_PRESSED);
+ litest_button_click(dev, BTN_STYLUS, LIBINPUT_BUTTON_STATE_RELEASED);
+ litest_axis_set_value(axes, ABS_PRESSURE, 30);
+ litest_tablet_tip_down(dev, x, y, axes);
+ for (; y > 5; y -= 5) {
+ litest_tablet_motion(dev, x, y, axes);
+ libinput_dispatch(li);
+ litest_assert_empty_queue(li);
+ }
+ litest_axis_set_value(axes, ABS_PRESSURE, 0);
+ litest_tablet_tip_up(dev, x, y, axes);
+
+ litest_tablet_proximity_out(dev);
+ litest_timeout_tablet_proxout();
+ libinput_dispatch(li);
+ litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(tablet_area_set_rectangle_move_outside_to_inside)
+{
+ 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;
+
+ if (libevdev_has_property(dev->evdev, INPUT_PROP_DIRECT))
+ return LITEST_NOT_APPLICABLE;
+
+ struct libinput_config_area_rectangle rect = {
+ 0.25, 0.25, 0.75, 0.75,
+ };
+
+ 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);
+
+ x = 5;
+ y = 50;
+ /* Move into the center of the area - since we started outside the area
+ * expect no events */
+ litest_tablet_proximity_in(dev, x, y, axes);
+ libinput_dispatch(li);
+ for (; x < 50; x += 5) {
+ litest_tablet_motion(dev, x, y, axes);
+ libinput_dispatch(li);
+ litest_assert_empty_queue(li);
+ }
+ litest_button_click(dev, BTN_STYLUS, LIBINPUT_BUTTON_STATE_PRESSED);
+ litest_button_click(dev, BTN_STYLUS, LIBINPUT_BUTTON_STATE_RELEASED);
+ litest_axis_set_value(axes, ABS_PRESSURE, 30);
+ litest_tablet_tip_down(dev, x, y, axes);
+ litest_axis_set_value(axes, ABS_PRESSURE, 0);
+ litest_tablet_tip_up(dev, x, y, axes);
+ litest_tablet_proximity_out(dev);
+ litest_timeout_tablet_proxout();
+ libinput_dispatch(li);
+ litest_assert_empty_queue(li);
+
+ y = 5;
+ x = 50;
+ litest_tablet_proximity_in(dev, x, y, axes);
+ for (; y < 50; y += 5) {
+ litest_tablet_motion(dev, x, y, axes);
+ libinput_dispatch(li);
+ litest_assert_empty_queue(li);
+ }
+ litest_button_click(dev, BTN_STYLUS, LIBINPUT_BUTTON_STATE_PRESSED);
+ litest_button_click(dev, BTN_STYLUS, LIBINPUT_BUTTON_STATE_RELEASED);
+ litest_axis_set_value(axes, ABS_PRESSURE, 30);
+ litest_tablet_tip_down(dev, x, y, axes);
+ litest_axis_set_value(axes, ABS_PRESSURE, 0);
+ litest_tablet_tip_up(dev, x, y, axes);
+ litest_tablet_proximity_out(dev);
+
+ litest_timeout_tablet_proxout();
+ libinput_dispatch(li);
+ litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(tablet_area_set_rectangle_move_in_margin)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+ struct libinput_device *d = dev->libinput_device;
+ struct libinput_event *ev;
+ struct libinput_event_tablet_tool *tev;
+ struct axis_replacement axes[] = {
+ { ABS_DISTANCE, 10 },
+ { ABS_PRESSURE, 0 },
+ { -1, -1 }
+ };
+ double x, y;
+
+ if (libevdev_has_property(dev->evdev, INPUT_PROP_DIRECT))
+ return LITEST_NOT_APPLICABLE;
+
+ struct libinput_config_area_rectangle rect = {
+ 0.25, 0.25, 0.75, 0.75,
+ };
+
+ 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 in/out of prox outside the area but within the margin */
+ litest_tablet_proximity_in(dev, 24, 24, axes);
+ litest_tablet_proximity_out(dev);
+ libinput_dispatch(li);
+ litest_timeout_tablet_proxout();
+ libinput_dispatch(li);
+ ev = libinput_get_event(li);
+ tev = litest_is_proximity_event(ev, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN);
+ x = libinput_event_tablet_tool_get_x(tev);
+ y = libinput_event_tablet_tool_get_y(tev);
+ litest_assert_double_eq(x, 0.0);
+ litest_assert_double_eq(y, 0.0);
+ libinput_event_destroy(ev);
+ ev = libinput_get_event(li);
+ tev = litest_is_proximity_event(ev, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT);
+ x = libinput_event_tablet_tool_get_x(tev);
+ y = libinput_event_tablet_tool_get_y(tev);
+ litest_assert_double_eq(x, 0.0);
+ litest_assert_double_eq(y, 0.0);
+ libinput_event_destroy(ev);
}
END_TEST
@@ -6768,7 +6964,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 };
+ struct range vert_horiz = { -2, 2 };
litest_add(tool_ref, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY);
litest_add(tool_user_data, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY);
@@ -6853,6 +7049,9 @@ TEST_COLLECTION(tablet)
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_area_set_rectangle_move_outside, LITEST_TABLET, LITEST_ANY);
+ litest_add(tablet_area_set_rectangle_move_outside_to_inside, LITEST_TABLET, LITEST_ANY);
+ litest_add(tablet_area_set_rectangle_move_in_margin, LITEST_TABLET, LITEST_ANY);
litest_add(tablet_pressure_min_max, LITEST_TABLET, LITEST_ANY);
/* Tests for pressure offset with distance */