From 7164d6eff5c83b8280d1d55eb33fb173160f9de9 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 6 Jan 2016 15:26:49 +1000 Subject: [PATCH] tablet: hook up relative motion events Signed-off-by: Peter Hutterer Acked-by: Jason Gerecke --- doc/pointer-acceleration.dox | 6 ++ src/evdev-tablet.c | 116 ++++++++++++++++++-- src/evdev.c | 13 +++ src/evdev.h | 4 + src/filter.c | 94 ++++++++++++++++ src/filter.h | 3 + src/libinput-util.h | 10 ++ test/tablet.c | 204 +++++++++++++++++++++++++++++++++++ 8 files changed, 440 insertions(+), 10 deletions(-) diff --git a/doc/pointer-acceleration.dox b/doc/pointer-acceleration.dox index 7ec5e74d..2fbb4cc2 100644 --- a/doc/pointer-acceleration.dox +++ b/doc/pointer-acceleration.dox @@ -124,4 +124,10 @@ velocity of the pointer and each delta (dx, dy) results in an accelerated delta (dx * factor, dy * factor). This provides 1:1 movement between the device and the pointer on-screen. +@section ptraccel-tablet Pointer acceleration on tablets + +Pointer acceleration for relative motion on tablet devices is a flat +acceleration, with the speed seeting slowing down or speeding up the pointer +motion by a constant factor. Tablets do not allow for switchable profiles. + */ diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c index 1870e7e3..ae205501 100644 --- a/src/evdev-tablet.c +++ b/src/evdev-tablet.c @@ -285,19 +285,29 @@ normalize_wheel(struct tablet_dispatch *tablet, return value * device->scroll.wheel_click_angle; } -static inline struct device_coords -tablet_handle_xy(struct tablet_dispatch *tablet, struct evdev_device *device) +static inline void +tablet_handle_xy(struct tablet_dispatch *tablet, + struct evdev_device *device, + struct device_coords *point_out, + struct device_coords *delta_out) { struct device_coords point; + struct device_coords delta = { 0, 0 }; const struct input_absinfo *absinfo; + int value; if (bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_X)) { absinfo = libevdev_get_abs_info(device->evdev, ABS_X); if (device->left_handed.enabled) - tablet->axes.point.x = invert_axis(absinfo); + value = invert_axis(absinfo); else - tablet->axes.point.x = absinfo->value; + value = absinfo->value; + + if (!tablet_has_status(tablet, + TABLET_TOOL_ENTERING_PROXIMITY)) + delta.x = value - tablet->axes.point.x; + tablet->axes.point.x = value; } point.x = tablet->axes.point.x; @@ -305,15 +315,41 @@ tablet_handle_xy(struct tablet_dispatch *tablet, struct evdev_device *device) absinfo = libevdev_get_abs_info(device->evdev, ABS_Y); if (device->left_handed.enabled) - tablet->axes.point.y = invert_axis(absinfo); + value = invert_axis(absinfo); else - tablet->axes.point.y = absinfo->value; + value = absinfo->value; + + if (!tablet_has_status(tablet, + TABLET_TOOL_ENTERING_PROXIMITY)) + delta.y = value - tablet->axes.point.y; + tablet->axes.point.y = value; } point.y = tablet->axes.point.y; evdev_transform_absolute(device, &point); + evdev_transform_relative(device, &delta); - return point; + *delta_out = delta; + *point_out = point; +} + +static inline struct normalized_coords +tablet_process_delta(struct tablet_dispatch *tablet, + const struct evdev_device *device, + const struct device_coords *delta, + uint64_t time) +{ + struct normalized_coords accel; + + /* The tablet accel code uses mm as input */ + accel.x = 1.0 * delta->x/device->abs.absinfo_x->resolution; + accel.y = 1.0 * delta->y/device->abs.absinfo_y->resolution; + + if (normalized_is_zero(accel)) + return accel; + + return filter_dispatch(device->pointer.filter, + &accel, tablet, time); } static inline double @@ -445,19 +481,22 @@ static bool tablet_check_notify_axes(struct tablet_dispatch *tablet, struct evdev_device *device, struct libinput_tablet_tool *tool, - struct tablet_axes *axes_out) + struct tablet_axes *axes_out, + uint64_t time) { struct tablet_axes axes = {0}; const char tmp[sizeof(tablet->changed_axes)] = {0}; + struct device_coords delta; if (memcmp(tmp, tablet->changed_axes, sizeof(tmp)) == 0) return false; - axes.point = tablet_handle_xy(tablet, device); + tablet_handle_xy(tablet, device, &axes.point, &delta); axes.pressure = tablet_handle_pressure(tablet, device, tool); axes.distance = tablet_handle_distance(tablet, device); axes.slider = tablet_handle_slider(tablet, device); axes.tilt = tablet_handle_tilt(tablet, device); + axes.delta = tablet_process_delta(tablet, device, &delta, time); /* We must check ROTATION_Z after TILT_X/Y so that the tilt axes are * already normalized and set if we have the mouse/lens tool */ @@ -1186,7 +1225,8 @@ tablet_send_axis_proximity_tip_down_events(struct tablet_dispatch *tablet, } else if (!tablet_check_notify_axes(tablet, device, tool, - &axes)) { + &axes, + time)) { goto out; } @@ -1463,11 +1503,64 @@ tablet_init_proximity_threshold(struct tablet_dispatch *tablet, tablet->cursor_proximity_threshold = 42; } +static uint32_t +tablet_accel_config_get_profiles(struct libinput_device *libinput_device) +{ + return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; +} + +static enum libinput_config_status +tablet_accel_config_set_profile(struct libinput_device *libinput_device, + enum libinput_config_accel_profile profile) +{ + return LIBINPUT_CONFIG_STATUS_UNSUPPORTED; +} + +static enum libinput_config_accel_profile +tablet_accel_config_get_profile(struct libinput_device *libinput_device) +{ + return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; +} + +static enum libinput_config_accel_profile +tablet_accel_config_get_default_profile(struct libinput_device *libinput_device) +{ + return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; +} + +static int +tablet_init_accel(struct tablet_dispatch *tablet, struct evdev_device *device) +{ + const struct input_absinfo *x, *y; + struct motion_filter *filter; + int rc; + + x = device->abs.absinfo_x; + y = device->abs.absinfo_y; + + filter = create_pointer_accelerator_filter_tablet(x->resolution, + y->resolution); + + rc = evdev_device_init_pointer_acceleration(device, filter); + if (rc != 0) + return rc; + + /* we override the profile hooks for accel configuration with hooks + * that don't allow selection of profiles */ + device->pointer.config.get_profiles = tablet_accel_config_get_profiles; + device->pointer.config.set_profile = tablet_accel_config_set_profile; + device->pointer.config.get_profile = tablet_accel_config_get_profile; + device->pointer.config.get_default_profile = tablet_accel_config_get_default_profile; + + return 0; +} + static int tablet_init(struct tablet_dispatch *tablet, struct evdev_device *device) { enum libinput_tablet_tool_axis axis; + int rc; tablet->base.interface = &tablet_interface; tablet->device = device; @@ -1477,6 +1570,9 @@ tablet_init(struct tablet_dispatch *tablet, tablet_init_calibration(tablet, device); tablet_init_proximity_threshold(tablet, device); + rc = tablet_init_accel(tablet, device); + if (rc != 0) + return rc; for (axis = LIBINPUT_TABLET_TOOL_AXIS_X; axis <= LIBINPUT_TABLET_TOOL_AXIS_MAX; diff --git a/src/evdev.c b/src/evdev.c index 82359237..8f0a6079 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -222,6 +222,19 @@ evdev_transform_absolute(struct evdev_device *device, matrix_mult_vec(&device->abs.calibration, &point->x, &point->y); } +void +evdev_transform_relative(struct evdev_device *device, + struct device_coords *point) +{ + struct matrix rel_matrix; + + if (!device->abs.apply_calibration) + return; + + matrix_to_relative(&rel_matrix, &device->abs.calibration); + matrix_mult_vec(&rel_matrix, &point->x, &point->y); +} + static inline double scale_axis(const struct input_absinfo *absinfo, double val, double to_range) { diff --git a/src/evdev.h b/src/evdev.h index cf0eda9b..02b51126 100644 --- a/src/evdev.h +++ b/src/evdev.h @@ -292,6 +292,10 @@ void evdev_transform_absolute(struct evdev_device *device, struct device_coords *point); +void +evdev_transform_relative(struct evdev_device *device, + struct device_coords *point); + void evdev_init_calibration(struct evdev_device *device, struct evdev_dispatch *dispatch); diff --git a/src/filter.c b/src/filter.c index 0d0b95d4..4c39b0e2 100644 --- a/src/filter.c +++ b/src/filter.c @@ -163,6 +163,13 @@ struct pointer_accelerator_flat { double dpi_factor; }; +struct tablet_accelerator_flat { + struct motion_filter base; + + double factor; + int xres, yres; +}; + static void feed_trackers(struct pointer_accelerator *accel, const struct normalized_coords *delta, @@ -964,3 +971,90 @@ create_pointer_accelerator_filter_flat(int dpi) return &filter->base; } + +/* The tablet accel code uses mm as input */ +static struct normalized_coords +tablet_accelerator_filter_flat(struct motion_filter *filter, + const struct normalized_coords *mm, + void *data, uint64_t time) +{ + struct tablet_accelerator_flat *accel_filter = + (struct tablet_accelerator_flat *)filter; + struct normalized_coords accelerated; + + /* Tablet input is in mm, output is supposed to be in logical + * pixels roughly equivalent to a mouse/touchpad. + * + * This is a magical constant found by trial and error. On a 96dpi + * screen 0.4mm of movement correspond to 1px logical pixel which + * is almost identical to the tablet mapped to screen in absolute + * mode. Tested on a Intuos5, other tablets may vary. + */ + const double DPI_CONVERSION = 96.0/25.4 * 2.5; /* unitless factor */ + + accelerated.x = mm->x * accel_filter->factor * DPI_CONVERSION; + accelerated.y = mm->y * accel_filter->factor * DPI_CONVERSION; + + return accelerated; +} + +static bool +tablet_accelerator_set_speed(struct motion_filter *filter, + double speed_adjustment) +{ + struct tablet_accelerator_flat *accel_filter = + (struct tablet_accelerator_flat *)filter; + + assert(speed_adjustment >= -1.0 && speed_adjustment <= 1.0); + + accel_filter->factor = speed_adjustment + 1.0; + + return true; +} + +static void +tablet_accelerator_destroy(struct motion_filter *filter) +{ + struct tablet_accelerator_flat *accel_filter = + (struct tablet_accelerator_flat *)filter; + + free(accel_filter); +} + +struct motion_filter_interface accelerator_interface_tablet = { + .type = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, + .filter = tablet_accelerator_filter_flat, + .filter_constant = NULL, + .restart = NULL, + .destroy = tablet_accelerator_destroy, + .set_speed = tablet_accelerator_set_speed, +}; + +static struct tablet_accelerator_flat * +create_tablet_filter_flat(int xres, int yres) +{ + struct tablet_accelerator_flat *filter; + + filter = zalloc(sizeof *filter); + if (filter == NULL) + return NULL; + + filter->xres = xres; + filter->yres = yres; + + return filter; +} + +struct motion_filter * +create_pointer_accelerator_filter_tablet(int xres, int yres) +{ + struct tablet_accelerator_flat *filter; + + filter = create_tablet_filter_flat(xres, yres); + if (!filter) + return NULL; + + filter->base.interface = &accelerator_interface_tablet; + + return &filter->base; +} diff --git a/src/filter.h b/src/filter.h index e1566429..c1b43a5d 100644 --- a/src/filter.h +++ b/src/filter.h @@ -101,6 +101,9 @@ create_pointer_accelerator_filter_lenovo_x230(int dpi); struct motion_filter * create_pointer_accelerator_filter_trackpoint(int dpi); +struct motion_filter * +create_pointer_accelerator_filter_tablet(int xres, int yres); + /* * Pointer acceleration profiles. */ diff --git a/src/libinput-util.h b/src/libinput-util.h index 93f7f0e4..6adbbc91 100644 --- a/src/libinput-util.h +++ b/src/libinput-util.h @@ -270,6 +270,16 @@ matrix_to_farray6(const struct matrix *m, float out[6]) out[5] = m->val[1][2]; } +static inline void +matrix_to_relative(struct matrix *dest, const struct matrix *src) +{ + matrix_init_identity(dest); + dest->val[0][0] = src->val[0][0]; + dest->val[0][1] = src->val[0][1]; + dest->val[1][0] = src->val[1][0]; + dest->val[1][1] = src->val[1][1]; +} + /** * Simple wrapper for asprintf that ensures the passed in-pointer is set * to NULL upon error. diff --git a/test/tablet.c b/test/tablet.c index 4976d1eb..59aefe5b 100644 --- a/test/tablet.c +++ b/test/tablet.c @@ -3205,6 +3205,205 @@ START_TEST(tilt_y) } END_TEST +START_TEST(relative_no_profile) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device *device = dev->libinput_device; + enum libinput_config_accel_profile profile; + enum libinput_config_status status; + uint32_t profiles; + + ck_assert(libinput_device_config_accel_is_available(device)); + + profile = libinput_device_config_accel_get_default_profile(device); + ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE); + + profile = libinput_device_config_accel_get_profile(device); + ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE); + + profiles = libinput_device_config_accel_get_profiles(device); + ck_assert_int_eq(profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE, 0); + ck_assert_int_eq(profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, 0); + + status = libinput_device_config_accel_set_profile(device, + LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED); + profile = libinput_device_config_accel_get_profile(device); + ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE); + + status = libinput_device_config_accel_set_profile(device, + LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED); + profile = libinput_device_config_accel_get_profile(device); + ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE); +} +END_TEST + +START_TEST(relative_no_delta_prox_in) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + double dx, dy; + + litest_drain_events(li); + + litest_tablet_proximity_in(dev, 10, 10, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + dx = libinput_event_tablet_tool_get_dx(tev); + dy = libinput_event_tablet_tool_get_dy(tev); + ck_assert(dx == 0.0); + ck_assert(dy == 0.0); + + libinput_event_destroy(event); +} +END_TEST + +START_TEST(relative_delta) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + double dx, dy; + + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_drain_events(li); + + litest_tablet_motion(dev, 20, 10, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + dx = libinput_event_tablet_tool_get_dx(tev); + dy = libinput_event_tablet_tool_get_dy(tev); + ck_assert(dx > 0.0); + ck_assert(dy == 0.0); + libinput_event_destroy(event); + + litest_tablet_motion(dev, 10, 10, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + dx = libinput_event_tablet_tool_get_dx(tev); + dy = libinput_event_tablet_tool_get_dy(tev); + ck_assert(dx < 0.0); + ck_assert(dy == 0.0); + libinput_event_destroy(event); + + litest_tablet_motion(dev, 10, 20, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + dx = libinput_event_tablet_tool_get_dx(tev); + dy = libinput_event_tablet_tool_get_dy(tev); + ck_assert(dx == 0.0); + ck_assert(dy > 0.0); + libinput_event_destroy(event); + + litest_tablet_motion(dev, 10, 10, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + dx = libinput_event_tablet_tool_get_dx(tev); + dy = libinput_event_tablet_tool_get_dy(tev); + ck_assert(dx == 0.0); + ck_assert(dy < 0.0); + libinput_event_destroy(event); +} +END_TEST + +START_TEST(relative_calibration) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + double dx, dy; + float calibration[] = { -1, 0, 1, 0, -1, 1 }; + enum libinput_config_status status; + + if (!libinput_device_config_calibration_has_matrix(dev->libinput_device)) + return; + + status = libinput_device_config_calibration_set_matrix( + dev->libinput_device, + calibration); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_drain_events(li); + + litest_tablet_motion(dev, 20, 10, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + dx = libinput_event_tablet_tool_get_dx(tev); + dy = libinput_event_tablet_tool_get_dy(tev); + ck_assert(dx < 0.0); + ck_assert(dy == 0.0); + libinput_event_destroy(event); + + litest_tablet_motion(dev, 10, 10, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + dx = libinput_event_tablet_tool_get_dx(tev); + dy = libinput_event_tablet_tool_get_dy(tev); + ck_assert(dx > 0.0); + ck_assert(dy == 0.0); + libinput_event_destroy(event); + + litest_tablet_motion(dev, 10, 20, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + dx = libinput_event_tablet_tool_get_dx(tev); + dy = libinput_event_tablet_tool_get_dy(tev); + ck_assert(dx == 0.0); + ck_assert(dy < 0.0); + libinput_event_destroy(event); + + litest_tablet_motion(dev, 10, 10, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + dx = libinput_event_tablet_tool_get_dx(tev); + dy = libinput_event_tablet_tool_get_dy(tev); + ck_assert(dx == 0.0); + ck_assert(dy > 0.0); + libinput_event_destroy(event); +} +END_TEST + void litest_setup_tests(void) { @@ -3271,4 +3470,9 @@ litest_setup_tests(void) litest_add_for_device("tablet:pressure", tablet_pressure_offset_exceed_threshold, LITEST_WACOM_INTUOS); litest_add_for_device("tablet:pressure", tablet_pressure_offset_none_for_zero_distance, LITEST_WACOM_INTUOS); litest_add_for_device("tablet:pressure", tablet_pressure_offset_none_for_small_distance, LITEST_WACOM_INTUOS); + + litest_add("tablet:relative", relative_no_profile, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:relative", relative_no_delta_prox_in, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:relative", relative_delta, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:relative", relative_calibration, LITEST_TABLET, LITEST_ANY); }