---
src/evdev-tablet.c | 103 ++++++++++++++++++++++++-
src/evdev-tablet.h | 3 +
test/litest.c | 18 +++++
test/litest.h | 3 +-
test/tablet.c | 221 +++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 346 insertions(+), 2 deletions(-)
diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c
index 7e3298c..777ffad 100644
--- a/src/evdev-tablet.c
+++ b/src/evdev-tablet.c
@@ -67,6 +67,22 @@ tablet_get_released_buttons(struct tablet_dispatch *tablet,
~(state->stylus_buttons[i]);
}
+/* Merge the previous state with the current one so all buttons look like
+ * they just got pressed in this frame */
+static inline void
+tablet_force_button_presses(struct tablet_dispatch *tablet)
+{
+ struct button_state *state = &tablet->button_state,
+ *prev_state = &tablet->prev_button_state;
+ size_t i;
+
+ for (i = 0; i < sizeof(state->stylus_buttons); i++) {
+ state->stylus_buttons[i] = state->stylus_buttons[i] |
+ prev_state->stylus_buttons[i];
+ prev_state->stylus_buttons[i] = 0;
+ }
+}
+
static int
tablet_device_has_axis(struct tablet_dispatch *tablet,
enum libinput_tablet_tool_axis axis)
@@ -994,6 +1010,62 @@ sanitize_tablet_axes(struct tablet_dispatch *tablet)
}
static void
+tablet_update_proximity_state(struct tablet_dispatch *tablet,
+ struct evdev_device *device)
+{
+ const struct input_absinfo *distance;
+ int dist_max = tablet->cursor_proximity_threshold;
+ int dist;
+
+ distance = libevdev_get_abs_info(tablet->device->evdev, ABS_DISTANCE);
+ if (!distance)
+ return;
+
+ dist = distance->value;
+ if (dist == 0)
+ return;
+
+ /* Tool got into permitted range */
+ if (dist < dist_max &&
+ (tablet_has_status(tablet, TABLET_TOOL_OUT_OF_RANGE) ||
+ tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY))) {
+ tablet_unset_status(tablet,
+ TABLET_TOOL_OUT_OF_RANGE);
+ tablet_unset_status(tablet,
+ TABLET_TOOL_OUT_OF_PROXIMITY);
+ tablet_set_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY);
+ tablet_mark_all_axes_changed(tablet, device);
+
+ tablet_set_status(tablet, TABLET_BUTTONS_PRESSED);
+ tablet_force_button_presses(tablet);
+ return;
+ }
+
+ if (dist < dist_max)
+ return;
+
+ /* Still out of range/proximity */
+ if (tablet_has_status(tablet, TABLET_TOOL_OUT_OF_RANGE) ||
+ tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY))
+ return;
+
+ /* Tool entered prox but is outside of permitted range */
+ if (tablet_has_status(tablet,
+ TABLET_TOOL_ENTERING_PROXIMITY)) {
+ tablet_set_status(tablet,
+ TABLET_TOOL_OUT_OF_RANGE);
+ tablet_unset_status(tablet,
+ TABLET_TOOL_ENTERING_PROXIMITY);
+ return;
+ }
+
+ /* Tool was in prox and is now outside of range. Set leaving
+ * proximity, on the next event it will be OUT_OF_PROXIMITY and thus
+ * caught by the above conditions */
+ tablet_set_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY);
+}
+
+static void
tablet_flush(struct tablet_dispatch *tablet,
struct evdev_device *device,
uint64_t time)
@@ -1004,7 +1076,12 @@ tablet_flush(struct tablet_dispatch *tablet,
tablet->current_tool_id,
tablet->current_tool_serial);
- if (tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY))
+ if (tool->type == LIBINPUT_TABLET_TOOL_TYPE_MOUSE ||
+ tool->type == LIBINPUT_TABLET_TOOL_TYPE_LENS)
+ tablet_update_proximity_state(tablet, device);
+
+ if (tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY) ||
+ tablet_has_status(tablet, TABLET_TOOL_OUT_OF_RANGE))
return;
if (tablet_has_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY)) {
@@ -1197,6 +1274,29 @@ tablet_init_calibration(struct tablet_dispatch *tablet,
evdev_init_calibration(device, &tablet->base);
}
+static void
+tablet_init_proximity_threshold(struct tablet_dispatch *tablet,
+ struct evdev_device *device)
+{
+ /* This rules out most of the bamboos and other devices, we're
+ * pretty much down to
+ */
+ if (!libevdev_has_event_code(device->evdev, EV_KEY, BTN_TOOL_MOUSE) &&
+ !libevdev_has_event_code(device->evdev, EV_KEY, BTN_TOOL_LENS))
+ return;
+
+ /* 42 is the default proximity threshold the xf86-input-wacom driver
+ * uses for Intuos/Cintiq models. Graphire models have a threshold
+ * of 10 but since they haven't been manufactured in ages and the
+ * intersection of users having a graphire, running libinput and
+ * wanting to use the mouse/lens cursor tool is small enough to not
+ * worry about it for now. If we need to, we can introduce a udev
+ * property later.
+ */
+
+ tablet->cursor_proximity_threshold = 42;
+}
+
static int
tablet_init(struct tablet_dispatch *tablet,
struct evdev_device *device)
@@ -1210,6 +1310,7 @@ tablet_init(struct tablet_dispatch *tablet,
list_init(&tablet->tool_list);
tablet_init_calibration(tablet, device);
+ tablet_init_proximity_threshold(tablet, device);
for (axis = LIBINPUT_TABLET_TOOL_AXIS_X;
axis <= LIBINPUT_TABLET_TOOL_AXIS_MAX;
diff --git a/src/evdev-tablet.h b/src/evdev-tablet.h
index 162b536..918bd51 100644
--- a/src/evdev-tablet.h
+++ b/src/evdev-tablet.h
@@ -41,6 +41,7 @@ enum tablet_status {
TABLET_TOOL_ENTERING_PROXIMITY = 1 << 6,
TABLET_TOOL_ENTERING_CONTACT = 1 << 7,
TABLET_TOOL_LEAVING_CONTACT = 1 << 8,
+ TABLET_TOOL_OUT_OF_RANGE = 1 << 9,
};
struct button_state {
@@ -65,6 +66,8 @@ struct tablet_dispatch {
enum libinput_tablet_tool_type current_tool_type;
uint32_t current_tool_id;
uint32_t current_tool_serial;
+
+ uint32_t cursor_proximity_threshold;
};
static inline enum libinput_tablet_tool_axis
diff --git a/test/litest.c b/test/litest.c
index 01d97d9..48291e5 100644
--- a/test/litest.c
+++ b/test/litest.c
@@ -2370,6 +2370,24 @@ litest_assert_tablet_button_event(struct libinput *li,
unsigned int button,
libinput_event_destroy(event);
}
+void litest_assert_tablet_proximity_event(struct libinput *li,
+ enum
libinput_tablet_tool_proximity_state state)
+{
+ struct libinput_event *event;
+ struct libinput_event_tablet_tool *tev;
+ enum libinput_event_type type = LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY;
+
+ litest_wait_for_event(li);
+ event = libinput_get_event(li);
+
+ litest_assert_notnull(event);
+ litest_assert_int_eq(libinput_event_get_type(event), type);
+ tev = libinput_event_get_tablet_tool_event(event);
+
litest_assert_int_eq(libinput_event_tablet_tool_get_proximity_state(tev),
+ state);
+ libinput_event_destroy(event);
+}
+
void
litest_assert_scroll(struct libinput *li,
enum libinput_pointer_axis axis,
diff --git a/test/litest.h b/test/litest.h
index ed9ddfe..f1ba4ae 100644
--- a/test/litest.h
+++ b/test/litest.h
@@ -419,7 +419,8 @@ void litest_assert_only_typed_events(struct libinput *li,
void litest_assert_tablet_button_event(struct libinput *li,
unsigned int button,
enum libinput_button_state state);
-
+void litest_assert_tablet_proximity_event(struct libinput *li,
+ enum
libinput_tablet_tool_proximity_state state);
struct libevdev_uinput * litest_create_uinput_device(const char *name,
struct input_id *id,
...);
diff --git a/test/tablet.c b/test/tablet.c
index c82be49..ba5b1ea 100644
--- a/test/tablet.c
+++ b/test/tablet.c
@@ -854,6 +854,222 @@ START_TEST(proximity_has_axes)
}
END_TEST
+START_TEST(proximity_range_enter)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+ struct axis_replacement axes[] = {
+ { ABS_DISTANCE, 60 },
+ { -1, -1 }
+ };
+
+ if (!libevdev_has_event_code(dev->evdev,
+ EV_KEY,
+ BTN_TOOL_MOUSE))
+ return;
+
+ litest_drain_events(li);
+
+ litest_push_event_frame(dev);
+ litest_tablet_proximity_in(dev, 10, 10, axes);
+ litest_event(dev, EV_KEY, BTN_TOOL_MOUSE, 1);
+ litest_pop_event_frame(dev);
+ litest_assert_empty_queue(li);
+
+ axes[0].value = 20;
+ litest_tablet_motion(dev, 10, 10, axes);
+ libinput_dispatch(li);
+
+ litest_assert_tablet_proximity_event(li,
+ LIBINPUT_TABLET_TOOL_PROXIMITY_IN);
+ axes[0].value = 60;
+ litest_tablet_motion(dev, 10, 10, axes);
+ libinput_dispatch(li);
+ litest_assert_tablet_proximity_event(li,
+
LIBINPUT_TABLET_TOOL_PROXIMITY_OUT);
+
+ litest_tablet_proximity_out(dev);
+ litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(proximity_range_in_out)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+ struct axis_replacement axes[] = {
+ { ABS_DISTANCE, 20 },
+ { -1, -1 }
+ };
+
+ if (!libevdev_has_event_code(dev->evdev,
+ EV_KEY,
+ BTN_TOOL_MOUSE))
+ return;
+
+ litest_drain_events(li);
+
+ litest_push_event_frame(dev);
+ litest_tablet_proximity_in(dev, 10, 10, axes);
+ litest_event(dev, EV_KEY, BTN_TOOL_MOUSE, 1);
+ litest_pop_event_frame(dev);
+ libinput_dispatch(li);
+ litest_assert_tablet_proximity_event(li,
+ LIBINPUT_TABLET_TOOL_PROXIMITY_IN);
+
+ axes[0].value = 60;
+ litest_tablet_motion(dev, 10, 10, axes);
+ libinput_dispatch(li);
+ litest_assert_tablet_proximity_event(li,
+
LIBINPUT_TABLET_TOOL_PROXIMITY_OUT);
+
+ litest_tablet_motion(dev, 30, 30, axes);
+ litest_assert_empty_queue(li);
+
+ axes[0].value = 20;
+ litest_tablet_motion(dev, 10, 10, axes);
+ libinput_dispatch(li);
+ litest_assert_tablet_proximity_event(li,
+ LIBINPUT_TABLET_TOOL_PROXIMITY_IN);
+
+ litest_tablet_proximity_out(dev);
+ litest_assert_tablet_proximity_event(li,
+
LIBINPUT_TABLET_TOOL_PROXIMITY_OUT);
+ litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(proximity_range_button_click)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+ struct axis_replacement axes[] = {
+ { ABS_DISTANCE, 60 },
+ { -1, -1 }
+ };
+
+ if (!libevdev_has_event_code(dev->evdev,
+ EV_KEY,
+ BTN_TOOL_MOUSE))
+ return;
+
+ litest_drain_events(li);
+
+ litest_push_event_frame(dev);
+ litest_tablet_proximity_in(dev, 10, 10, axes);
+ litest_event(dev, EV_KEY, BTN_TOOL_MOUSE, 1);
+ litest_pop_event_frame(dev);
+ litest_drain_events(li);
+
+ litest_event(dev, EV_KEY, BTN_STYLUS, 1);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ libinput_dispatch(li);
+ litest_event(dev, EV_KEY, BTN_STYLUS, 0);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ libinput_dispatch(li);
+
+ litest_tablet_proximity_out(dev);
+ litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(proximity_range_button_press)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+ struct axis_replacement axes[] = {
+ { ABS_DISTANCE, 20 },
+ { -1, -1 }
+ };
+
+ if (!libevdev_has_event_code(dev->evdev,
+ EV_KEY,
+ BTN_TOOL_MOUSE))
+ return;
+
+ litest_push_event_frame(dev);
+ litest_tablet_proximity_in(dev, 10, 10, axes);
+ litest_event(dev, EV_KEY, BTN_TOOL_MOUSE, 1);
+ litest_pop_event_frame(dev);
+ litest_drain_events(li);
+
+ litest_event(dev, EV_KEY, BTN_STYLUS, 1);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ libinput_dispatch(li);
+
+ litest_assert_tablet_button_event(li,
+ BTN_STYLUS,
+ LIBINPUT_BUTTON_STATE_PRESSED);
+
+ axes[0].value = 60;
+ litest_tablet_motion(dev, 15, 15, axes);
+ libinput_dispatch(li);
+
+ /* expect fake button release */
+ litest_assert_tablet_button_event(li,
+ BTN_STYLUS,
+ LIBINPUT_BUTTON_STATE_RELEASED);
+ litest_assert_tablet_proximity_event(li,
+
LIBINPUT_TABLET_TOOL_PROXIMITY_OUT);
+
+ litest_event(dev, EV_KEY, BTN_STYLUS, 0);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ libinput_dispatch(li);
+
+ litest_tablet_proximity_out(dev);
+ litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(proximity_range_button_release)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+ struct axis_replacement axes[] = {
+ { ABS_DISTANCE, 60 },
+ { -1, -1 }
+ };
+
+ if (!libevdev_has_event_code(dev->evdev,
+ EV_KEY,
+ BTN_TOOL_MOUSE))
+ return;
+
+ litest_push_event_frame(dev);
+ litest_tablet_proximity_in(dev, 10, 10, axes);
+ litest_event(dev, EV_KEY, BTN_TOOL_MOUSE, 1);
+ litest_pop_event_frame(dev);
+ litest_drain_events(li);
+
+ litest_event(dev, EV_KEY, BTN_STYLUS, 1);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ litest_assert_empty_queue(li);
+
+ axes[0].value = 20;
+ litest_tablet_motion(dev, 15, 15, axes);
+ libinput_dispatch(li);
+
+ litest_assert_tablet_proximity_event(li,
+ LIBINPUT_TABLET_TOOL_PROXIMITY_IN);
+ /* expect fake button press */
+ litest_assert_tablet_button_event(li,
+ BTN_STYLUS,
+ LIBINPUT_BUTTON_STATE_PRESSED);
+ litest_assert_empty_queue(li);
+
+ litest_event(dev, EV_KEY, BTN_STYLUS, 0);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ libinput_dispatch(li);
+ litest_assert_tablet_button_event(li,
+ BTN_STYLUS,
+ LIBINPUT_BUTTON_STATE_RELEASED);
+
+ litest_tablet_proximity_out(dev);
+ litest_assert_tablet_proximity_event(li,
+
LIBINPUT_TABLET_TOOL_PROXIMITY_OUT);
+}
+END_TEST
+
START_TEST(motion)
{
struct litest_device *dev = litest_current_device();
@@ -2695,6 +2911,11 @@ litest_setup_tests(void)
litest_add("tablet:proximity", proximity_in_out, LITEST_TABLET,
LITEST_ANY);
litest_add("tablet:proximity", proximity_has_axes, LITEST_TABLET,
LITEST_ANY);
litest_add("tablet:proximity", bad_distance_events, LITEST_TABLET |
LITEST_DISTANCE, LITEST_ANY);
+ litest_add("tablet:proximity", proximity_range_enter, LITEST_TABLET |
LITEST_DISTANCE, LITEST_ANY);
+ litest_add("tablet:proximity", proximity_range_in_out, LITEST_TABLET |
LITEST_DISTANCE, LITEST_ANY);
+ litest_add("tablet:proximity", proximity_range_button_click,
LITEST_TABLET | LITEST_DISTANCE, LITEST_ANY);
+ litest_add("tablet:proximity", proximity_range_button_press,
LITEST_TABLET | LITEST_DISTANCE, LITEST_ANY);
+ litest_add("tablet:proximity", proximity_range_button_release,
LITEST_TABLET | LITEST_DISTANCE, LITEST_ANY);
litest_add("tablet:tip", tip_down_up, LITEST_TABLET, LITEST_ANY);
litest_add("tablet:tip", tip_down_prox_in, LITEST_TABLET, LITEST_ANY);
litest_add("tablet:tip", tip_up_prox_out, LITEST_TABLET, LITEST_ANY);