diff options
-rw-r--r-- | src/evdev-tablet.c | 88 | ||||
-rw-r--r-- | src/evdev-tablet.h | 1 | ||||
-rw-r--r-- | test/test-tablet.c | 221 |
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 */ |