diff --git a/doc/tablet-support.dox b/doc/tablet-support.dox index 5e6f3932..7207a473 100644 --- a/doc/tablet-support.dox +++ b/doc/tablet-support.dox @@ -45,7 +45,8 @@ pressure value while the tip is logically up. Most application can and should ignore pressure information until they receive the event of type @ref LIBINPUT_EVENT_TABLET_TOOL_TIP. Applications that require extremely fine-grained pressure sensitivity should use the pressure data instead of -the tip events. +the tip events to determine a logical tip down state and treat the tip +events like axis events otherwise. Note that the pressure threshold to trigger a logical tip event may be zero on some devices. On tools without pressure sensitivity, determining when a diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c index 070ac9a1..1dc607be 100644 --- a/src/evdev-tablet.c +++ b/src/evdev-tablet.c @@ -452,13 +452,14 @@ tablet_handle_wheel(struct tablet_dispatch *tablet, return tablet->axes[a]; } -static void +static bool tablet_check_notify_axes(struct tablet_dispatch *tablet, struct evdev_device *device, - uint64_t time, - struct libinput_tablet_tool *tool) + struct libinput_tablet_tool *tool, + double *axes_out, + size_t axes_sz, + int *wheel_discrete_out) { - struct libinput_device *base = &device->base; double axes[LIBINPUT_TABLET_TOOL_AXIS_MAX + 1] = {0}; int wheel_discrete = 0; struct device_coords point; @@ -466,8 +467,9 @@ tablet_check_notify_axes(struct tablet_dispatch *tablet, const char tmp[sizeof(tablet->changed_axes)] = {0}; if (memcmp(tmp, tablet->changed_axes, sizeof(tmp)) == 0) - return; + return false; + assert(axes_sz == sizeof(axes)); point = tablet_handle_xy(tablet, device); axes[LIBINPUT_TABLET_TOOL_AXIS_X] = point.x; axes[LIBINPUT_TABLET_TOOL_AXIS_Y] = point.y; @@ -500,41 +502,10 @@ tablet_check_notify_axes(struct tablet_dispatch *tablet, axes[LIBINPUT_TABLET_TOOL_AXIS_REL_WHEEL] = tablet_handle_wheel(tablet, device, &wheel_discrete); - /* We need to make sure that we check that the tool is not out of - * proximity before we send any axis updates. This is because many - * tablets will send axis events with incorrect values if the tablet - * tool is close enough so that the tablet can partially detect that - * it's there, but can't properly receive any data from the tool. */ - if (!tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY) && - !tablet_has_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY)) { - if (tablet_has_status(tablet, - TABLET_TOOL_ENTERING_PROXIMITY)) { - tablet_notify_proximity(&device->base, - time, - tool, - LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN, - tablet->changed_axes, - axes); - } else { - enum libinput_tablet_tool_tip_state tip_state; + memcpy(axes_out, axes, sizeof(axes)); + *wheel_discrete_out = wheel_discrete; - if (tablet_has_status(tablet, - TABLET_TOOL_IN_CONTACT)) - tip_state = LIBINPUT_TABLET_TOOL_TIP_DOWN; - else - tip_state = LIBINPUT_TABLET_TOOL_TIP_UP; - - tablet_notify_axis(base, - time, - tool, - tip_state, - tablet->changed_axes, - axes, - wheel_discrete); - } - } - - memset(tablet->changed_axes, 0, sizeof(tablet->changed_axes)); + return true; } static void @@ -1215,6 +1186,98 @@ tablet_update_proximity_state(struct tablet_dispatch *tablet, tablet_set_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY); } +static void +tablet_send_axis_proximity_tip_down_events(struct tablet_dispatch *tablet, + struct evdev_device *device, + struct libinput_tablet_tool *tool, + uint64_t time) +{ + double axes[LIBINPUT_TABLET_TOOL_AXIS_MAX + 1] = {0}; + int wheel_discrete = 0; + + /* We need to make sure that we check that the tool is not out of + * proximity before we send any axis updates. This is because many + * tablets will send axis events with incorrect values if the tablet + * tool is close enough so that the tablet can partially detect that + * it's there, but can't properly receive any data from the tool. */ + if (tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY)) + goto out; + else if (tablet_has_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY)) { + /* Tool is leaving proximity, we can't rely on the last axis + * information (it'll be mostly 0), so we just get the + * current state and skip over updating the axes. + */ + static_assert(sizeof(axes) == sizeof(tablet->axes), + "Mismatching array sizes"); + memcpy(axes, tablet->axes, sizeof(axes)); + + /* Dont' send an axis event, but we may have a tip event + * update */ + tablet_unset_status(tablet, TABLET_AXES_UPDATED); + } else if (!tablet_check_notify_axes(tablet, + device, + tool, + axes, + sizeof(axes), + &wheel_discrete)) { + goto out; + } + + if (tablet_has_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY)) { + tablet_notify_proximity(&device->base, + time, + tool, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN, + tablet->changed_axes, + axes); + tablet_unset_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY); + tablet_unset_status(tablet, TABLET_AXES_UPDATED); + } + + if (tablet_has_status(tablet, TABLET_TOOL_ENTERING_CONTACT)) { + tablet_notify_tip(&device->base, + time, + tool, + LIBINPUT_TABLET_TOOL_TIP_DOWN, + tablet->changed_axes, + tablet->axes); + tablet_unset_status(tablet, TABLET_AXES_UPDATED); + tablet_unset_status(tablet, TABLET_TOOL_ENTERING_CONTACT); + tablet_set_status(tablet, TABLET_TOOL_IN_CONTACT); + } else if (tablet_has_status(tablet, TABLET_TOOL_LEAVING_CONTACT)) { + tablet_notify_tip(&device->base, + time, + tool, + LIBINPUT_TABLET_TOOL_TIP_UP, + tablet->changed_axes, + tablet->axes); + tablet_unset_status(tablet, TABLET_AXES_UPDATED); + tablet_unset_status(tablet, TABLET_TOOL_LEAVING_CONTACT); + tablet_unset_status(tablet, TABLET_TOOL_IN_CONTACT); + } else if (tablet_has_status(tablet, TABLET_AXES_UPDATED)) { + enum libinput_tablet_tool_tip_state tip_state; + + if (tablet_has_status(tablet, + TABLET_TOOL_IN_CONTACT)) + tip_state = LIBINPUT_TABLET_TOOL_TIP_DOWN; + else + tip_state = LIBINPUT_TABLET_TOOL_TIP_UP; + + tablet_notify_axis(&device->base, + time, + tool, + tip_state, + tablet->changed_axes, + axes, + wheel_discrete); + tablet_unset_status(tablet, TABLET_AXES_UPDATED); + } + +out: + memset(tablet->changed_axes, 0, sizeof(tablet->changed_axes)); + tablet_unset_status(tablet, TABLET_TOOL_ENTERING_CONTACT); +} + static void tablet_flush(struct tablet_dispatch *tablet, struct evdev_device *device, @@ -1250,21 +1313,12 @@ tablet_flush(struct tablet_dispatch *tablet, detect_pressure_offset(tablet, device, tool); detect_tool_contact(tablet, device, tool); sanitize_tablet_axes(tablet, tool); - tablet_check_notify_axes(tablet, device, time, tool); - - tablet_unset_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY); - tablet_unset_status(tablet, TABLET_AXES_UPDATED); } - if (tablet_has_status(tablet, TABLET_TOOL_ENTERING_CONTACT)) { - tablet_notify_tip(&device->base, - time, - tool, - LIBINPUT_TABLET_TOOL_TIP_DOWN, - tablet->axes); - tablet_unset_status(tablet, TABLET_TOOL_ENTERING_CONTACT); - tablet_set_status(tablet, TABLET_TOOL_IN_CONTACT); - } + tablet_send_axis_proximity_tip_down_events(tablet, + device, + tool, + time); if (tablet_has_status(tablet, TABLET_BUTTONS_RELEASED)) { tablet_notify_buttons(tablet, @@ -1284,16 +1338,6 @@ tablet_flush(struct tablet_dispatch *tablet, tablet_unset_status(tablet, TABLET_BUTTONS_PRESSED); } - if (tablet_has_status(tablet, TABLET_TOOL_LEAVING_CONTACT)) { - tablet_notify_tip(&device->base, - time, - tool, - LIBINPUT_TABLET_TOOL_TIP_UP, - tablet->axes); - tablet_unset_status(tablet, TABLET_TOOL_LEAVING_CONTACT); - tablet_unset_status(tablet, TABLET_TOOL_IN_CONTACT); - } - if (tablet_has_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY)) { memset(tablet->changed_axes, 0, sizeof(tablet->changed_axes)); tablet_notify_proximity(&device->base, diff --git a/src/libinput-private.h b/src/libinput-private.h index 5238e646..2dbc555a 100644 --- a/src/libinput-private.h +++ b/src/libinput-private.h @@ -509,6 +509,7 @@ tablet_notify_tip(struct libinput_device *device, uint64_t time, struct libinput_tablet_tool *tool, enum libinput_tablet_tool_tip_state tip_state, + unsigned char *changed_axes, double *axes); void diff --git a/src/libinput.c b/src/libinput.c index d73637a8..e9883ffd 100644 --- a/src/libinput.c +++ b/src/libinput.c @@ -2226,6 +2226,7 @@ tablet_notify_tip(struct libinput_device *device, uint64_t time, struct libinput_tablet_tool *tool, enum libinput_tablet_tool_tip_state tip_state, + unsigned char *changed_axes, double *axes) { struct libinput_event_tablet_tool *tip_event; @@ -2243,6 +2244,9 @@ tablet_notify_tip(struct libinput_device *device, memcpy(tip_event->axes, axes, sizeof(tip_event->axes)); + memcpy(tip_event->changed_axes, + changed_axes, + sizeof(tip_event->changed_axes)); post_device_event(device, time, diff --git a/src/libinput.h b/src/libinput.h index ab57b4cb..7b9236de 100644 --- a/src/libinput.h +++ b/src/libinput.h @@ -273,6 +273,10 @@ enum libinput_event_type { * changes from this initial state. It is possible for a tool to * enter and leave proximity without sending an event of type @ref * LIBINPUT_EVENT_TABLET_TOOL_AXIS. + * + * An event of type @ref LIBINPUT_EVENT_TABLET_TOOL_AXIS is sent + * when the tip state does not change. See the documentation for + * @ref LIBINPUT_EVENT_TABLET_TOOL_TIP for more details. */ LIBINPUT_EVENT_TABLET_TOOL_AXIS = 600, /** @@ -309,10 +313,30 @@ enum libinput_event_type { * LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY for the tip down event, and * immediately before for the tip up event. * - * If a button and/or axis state change occurs at the same time as a - * tip state change, the order of events is device-dependent. + * The decision when a tip touches the surface is device-dependent + * and may be derived from pressure data or other means. If the tip + * state is changed by axes changing state, the + * @ref LIBINPUT_EVENT_TABLET_TOOL_TIP event includes the changed + * axes and no additional axis event is sent for this state change. + * In other words, a caller must look at both @ref + * LIBINPUT_EVENT_TABLET_TOOL_AXIS and @ref + * LIBINPUT_EVENT_TABLET_TOOL_TIP events to know the current state + * of the axes. + * + * If a button state change occurs at the same time as a tip state + * change, the order of events is device-dependent. */ LIBINPUT_EVENT_TABLET_TOOL_TIP, + /** + * Signals that a tool has changed a logical button state on a + * device with the @ref LIBINPUT_DEVICE_CAP_TABLET_TOOL capability. + * + * Button state changes occur on their own and do not include axis + * state changes. If button and axis state changes occur within the + * same logical hardware event, the order of the @ref + * LIBINPUT_EVENT_TABLET_TOOL_BUTTON and @ref + * LIBINPUT_EVENT_TABLET_TOOL_AXIS event is device-specific. + */ LIBINPUT_EVENT_TABLET_TOOL_BUTTON, LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN = 800, diff --git a/test/tablet.c b/test/tablet.c index 421deb06..4976d1eb 100644 --- a/test/tablet.c +++ b/test/tablet.c @@ -1,5 +1,5 @@ /* - * Copyright © 2014 Red Hat, Inc. + * Copyright © 2014-2015 Red Hat, Inc. * Copyright © 2014 Stephen Chandler "Lyude" Paul * * Permission to use, copy, modify, distribute, and sell this software and @@ -58,11 +58,6 @@ START_TEST(tip_down_up) libinput_dispatch(li); - event = libinput_get_event(li); - tablet_event = litest_is_tablet_event(event, - LIBINPUT_EVENT_TABLET_TOOL_AXIS); - libinput_event_destroy(event); - event = libinput_get_event(li); tablet_event = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_TIP); @@ -79,11 +74,6 @@ START_TEST(tip_down_up) litest_pop_event_frame(dev); libinput_dispatch(li); - event = libinput_get_event(li); - tablet_event = litest_is_tablet_event(event, - LIBINPUT_EVENT_TABLET_TOOL_AXIS); - libinput_event_destroy(event); - event = libinput_get_event(li); tablet_event = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_TIP); @@ -125,7 +115,6 @@ START_TEST(tip_down_prox_in) LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN); libinput_event_destroy(event); - libinput_dispatch(li); event = libinput_get_event(li); tablet_event = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_TIP); @@ -212,9 +201,12 @@ START_TEST(tip_up_btn_change) litest_pop_event_frame(dev); libinput_dispatch(li); + event = libinput_get_event(li); tablet_event = litest_is_tablet_event(event, - LIBINPUT_EVENT_TABLET_TOOL_AXIS); + LIBINPUT_EVENT_TABLET_TOOL_TIP); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), + LIBINPUT_TABLET_TOOL_TIP_UP); libinput_event_destroy(event); event = libinput_get_event(li); @@ -226,13 +218,6 @@ START_TEST(tip_up_btn_change) LIBINPUT_BUTTON_STATE_PRESSED); libinput_event_destroy(event); - event = libinput_get_event(li); - tablet_event = litest_is_tablet_event(event, - LIBINPUT_EVENT_TABLET_TOOL_TIP); - ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), - LIBINPUT_TABLET_TOOL_TIP_UP); - libinput_event_destroy(event); - litest_assert_empty_queue(li); litest_axis_set_value(axes, ABS_DISTANCE, 0); @@ -253,9 +238,12 @@ START_TEST(tip_up_btn_change) litest_pop_event_frame(dev); libinput_dispatch(li); + event = libinput_get_event(li); tablet_event = litest_is_tablet_event(event, - LIBINPUT_EVENT_TABLET_TOOL_AXIS); + LIBINPUT_EVENT_TABLET_TOOL_TIP); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), + LIBINPUT_TABLET_TOOL_TIP_UP); libinput_event_destroy(event); event = libinput_get_event(li); @@ -267,14 +255,6 @@ START_TEST(tip_up_btn_change) LIBINPUT_BUTTON_STATE_RELEASED); libinput_event_destroy(event); - libinput_dispatch(li); - event = libinput_get_event(li); - tablet_event = litest_is_tablet_event(event, - LIBINPUT_EVENT_TABLET_TOOL_TIP); - ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), - LIBINPUT_TABLET_TOOL_TIP_UP); - libinput_event_destroy(event); - litest_assert_empty_queue(li); } END_TEST @@ -303,10 +283,6 @@ START_TEST(tip_down_btn_change) litest_pop_event_frame(dev); libinput_dispatch(li); - event = libinput_get_event(li); - tablet_event = litest_is_tablet_event(event, - LIBINPUT_EVENT_TABLET_TOOL_AXIS); - libinput_event_destroy(event); event = libinput_get_event(li); tablet_event = litest_is_tablet_event(event, @@ -345,10 +321,6 @@ START_TEST(tip_down_btn_change) litest_pop_event_frame(dev); libinput_dispatch(li); - event = libinput_get_event(li); - tablet_event = litest_is_tablet_event(event, - LIBINPUT_EVENT_TABLET_TOOL_AXIS); - libinput_event_destroy(event); event = libinput_get_event(li); tablet_event = litest_is_tablet_event(event, @@ -384,9 +356,18 @@ START_TEST(tip_down_motion) }; double x, y, last_x, last_y; - litest_tablet_proximity_in(dev, 10, 10, axes); litest_drain_events(li); + litest_tablet_proximity_in(dev, 10, 10, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + last_x = libinput_event_tablet_tool_get_x(tablet_event); + last_y = libinput_event_tablet_tool_get_y(tablet_event); + libinput_event_destroy(event); + + /* move x/y on tip down, make sure x/y changed */ litest_axis_set_value(axes, ABS_DISTANCE, 0); litest_axis_set_value(axes, ABS_PRESSURE, 20); litest_push_event_frame(dev); @@ -395,24 +376,18 @@ START_TEST(tip_down_motion) litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_pop_event_frame(dev); - libinput_dispatch(li); - event = libinput_get_event(li); - tablet_event = litest_is_tablet_event(event, - LIBINPUT_EVENT_TABLET_TOOL_AXIS); - last_x = libinput_event_tablet_tool_get_x(tablet_event); - last_y = libinput_event_tablet_tool_get_y(tablet_event); - libinput_event_destroy(event); - libinput_dispatch(li); event = libinput_get_event(li); tablet_event = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_TIP); ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), LIBINPUT_TABLET_TOOL_TIP_DOWN); + ck_assert(libinput_event_tablet_tool_x_has_changed(tablet_event)); + ck_assert(libinput_event_tablet_tool_y_has_changed(tablet_event)); x = libinput_event_tablet_tool_get_x(tablet_event); y = libinput_event_tablet_tool_get_y(tablet_event); - ck_assert_double_eq(last_x, x); - ck_assert_double_eq(last_y, y); + ck_assert_double_lt(last_x, x); + ck_assert_double_lt(last_y, y); libinput_event_destroy(event); litest_assert_empty_queue(li); @@ -427,44 +402,48 @@ START_TEST(tip_up_motion) struct libinput_event_tablet_tool *tablet_event; struct axis_replacement axes[] = { { ABS_DISTANCE, 0 }, - { ABS_PRESSURE, 20 }, + { ABS_PRESSURE, 0 }, { -1, -1 } }; double x, y, last_x, last_y; - litest_push_event_frame(dev); litest_tablet_proximity_in(dev, 10, 10, axes); - litest_tablet_motion(dev, 70, 70, axes); - litest_event(dev, EV_KEY, BTN_TOUCH, 1); - litest_event(dev, EV_SYN, SYN_REPORT, 0); - litest_pop_event_frame(dev); litest_drain_events(li); - litest_axis_set_value(axes, ABS_PRESSURE, 0); + litest_axis_set_value(axes, ABS_PRESSURE, 20); litest_push_event_frame(dev); litest_tablet_motion(dev, 70, 70, axes); - litest_event(dev, EV_KEY, BTN_TOUCH, 0); + litest_event(dev, EV_KEY, BTN_TOUCH, 1); litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_pop_event_frame(dev); libinput_dispatch(li); event = libinput_get_event(li); tablet_event = litest_is_tablet_event(event, - LIBINPUT_EVENT_TABLET_TOOL_AXIS); + LIBINPUT_EVENT_TABLET_TOOL_TIP); last_x = libinput_event_tablet_tool_get_x(tablet_event); last_y = libinput_event_tablet_tool_get_y(tablet_event); libinput_event_destroy(event); + /* move x/y on tip up, make sure x/y changed */ + litest_axis_set_value(axes, ABS_PRESSURE, 0); + litest_push_event_frame(dev); + litest_tablet_motion(dev, 40, 40, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 0); + litest_pop_event_frame(dev); + libinput_dispatch(li); event = libinput_get_event(li); tablet_event = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_TIP); ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), LIBINPUT_TABLET_TOOL_TIP_UP); + ck_assert(libinput_event_tablet_tool_x_has_changed(tablet_event)); + ck_assert(libinput_event_tablet_tool_y_has_changed(tablet_event)); x = libinput_event_tablet_tool_get_x(tablet_event); y = libinput_event_tablet_tool_get_y(tablet_event); - ck_assert_double_eq(last_x, x); - ck_assert_double_eq(last_y, y); + ck_assert_double_ne(last_x, x); + ck_assert_double_ne(last_y, y); libinput_event_destroy(event); litest_assert_empty_queue(li); @@ -2841,7 +2820,7 @@ START_TEST(tablet_pressure_offset_decrease) event = libinput_get_event(li); tev = litest_is_tablet_event(event, - LIBINPUT_EVENT_TABLET_TOOL_AXIS); + LIBINPUT_EVENT_TABLET_TOOL_TIP); pressure = libinput_event_tablet_tool_get_pressure(tev); @@ -2905,7 +2884,7 @@ START_TEST(tablet_pressure_offset_increase) event = libinput_get_event(li); tev = litest_is_tablet_event(event, - LIBINPUT_EVENT_TABLET_TOOL_AXIS); + LIBINPUT_EVENT_TABLET_TOOL_TIP); pressure = libinput_event_tablet_tool_get_pressure(tev);