diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c index cec05c49..3834e56d 100644 --- a/src/evdev-tablet.c +++ b/src/evdev-tablet.c @@ -68,6 +68,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) @@ -1063,6 +1079,63 @@ tablet_mark_all_axes_changed(struct tablet_dispatch *tablet, sizeof(tablet->changed_axes)); } +static void +tablet_update_proximity_state(struct tablet_dispatch *tablet, + struct evdev_device *device, + struct libinput_tablet_tool *tool) +{ + 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, tool); + + 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, @@ -1074,7 +1147,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, tool); + + 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)) { @@ -1271,6 +1349,30 @@ 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. + * + * Value is in device coordinates. + */ + tablet->cursor_proximity_threshold = 42; +} + static int tablet_init(struct tablet_dispatch *tablet, struct evdev_device *device) @@ -1284,6 +1386,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 4dcbccc6..0225dcf4 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 fd5e3b9c..4446f7b7 100644 --- a/test/litest.c +++ b/test/litest.c @@ -2403,6 +2403,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 89377906..406e47fd 100644 --- a/test/litest.h +++ b/test/litest.h @@ -457,7 +457,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 66c19902..122a1ba2 100644 --- a/test/tablet.c +++ b/test/tablet.c @@ -811,6 +811,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, 90 }, + { -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 = 90; + 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 = 90; + 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, 90 }, + { -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 = 90; + 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, 90 }, + { -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(); @@ -2872,6 +3088,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);