Skip to content

Commit

Permalink
touchpad: rotate the touch part of tablets
Browse files Browse the repository at this point in the history
Tablets in left-handed mode are rotated, so we need to rotate the touchpad
part of them too. This doesn't affect all tablets though, some of them are
symmetrical and the left-handed mode merely changes the button order around
(some of the earlier Bamboos). So we rely on libwacom to tell us which device
must be rotated.

The rotation itself is done on the input coordinate itself as we get it. This
way any software buttons, palm zones, etc. are automatically handled by rest
of the code.

Fixes #274

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
  • Loading branch information
whot committed Apr 30, 2019
1 parent 3542855 commit 6229df1
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 9 deletions.
112 changes: 103 additions & 9 deletions src/evdev-mt-touchpad.c
Expand Up @@ -28,6 +28,10 @@
#include <stdbool.h>
#include <limits.h>

#if HAVE_LIBWACOM
#include <libwacom/libwacom.h>
#endif

#include "quirks.h"
#include "evdev-mt-touchpad.h"

Expand Down Expand Up @@ -464,6 +468,30 @@ tp_get_delta(struct tp_touch *t)
return delta;
}

static inline int32_t
rotated(struct tp_dispatch *tp, unsigned int code, int value)
{
const struct input_absinfo *absinfo;

if (!tp->device->left_handed.enabled ||
!tp->left_handed.rotate)
return value;

switch (code) {
case ABS_X:
case ABS_MT_POSITION_X:
absinfo = tp->device->abs.absinfo_x;
break;
case ABS_Y:
case ABS_MT_POSITION_Y:
absinfo = tp->device->abs.absinfo_y;
break;
default:
abort();
}
return absinfo->maximum - (value - absinfo->minimum);
}

static void
tp_process_absolute(struct tp_dispatch *tp,
const struct input_event *e,
Expand All @@ -476,7 +504,7 @@ tp_process_absolute(struct tp_dispatch *tp,
evdev_device_check_abs_axis_range(tp->device,
e->code,
e->value);
t->point.x = e->value;
t->point.x = rotated(tp, e->code, e->value);
t->time = time;
t->dirty = true;
tp->queued |= TOUCHPAD_EVENT_MOTION;
Expand All @@ -485,7 +513,7 @@ tp_process_absolute(struct tp_dispatch *tp,
evdev_device_check_abs_axis_range(tp->device,
e->code,
e->value);
t->point.y = e->value;
t->point.y = rotated(tp, e->code, e->value);
t->time = time;
t->dirty = true;
tp->queued |= TOUCHPAD_EVENT_MOTION;
Expand Down Expand Up @@ -536,7 +564,7 @@ tp_process_absolute_st(struct tp_dispatch *tp,
evdev_device_check_abs_axis_range(tp->device,
e->code,
e->value);
t->point.x = e->value;
t->point.x = rotated(tp, e->code, e->value);
t->time = time;
t->dirty = true;
tp->queued |= TOUCHPAD_EVENT_MOTION;
Expand All @@ -545,7 +573,7 @@ tp_process_absolute_st(struct tp_dispatch *tp,
evdev_device_check_abs_axis_range(tp->device,
e->code,
e->value);
t->point.y = e->value;
t->point.y = rotated(tp, e->code, e->value);
t->time = time;
t->dirty = true;
tp->queued |= TOUCHPAD_EVENT_MOTION;
Expand Down Expand Up @@ -3700,11 +3728,80 @@ tp_change_to_left_handed(struct evdev_device *device)
device->left_handed.enabled = device->left_handed.want_enabled;
}

static bool
tp_init_left_handed_rotation(struct tp_dispatch *tp,
struct evdev_device *device)
{
bool rotate = false;
#if HAVE_LIBWACOM
WacomDeviceDatabase *db;
WacomDevice **devices = NULL,
**d;
WacomDevice *dev;
uint32_t vid = evdev_device_get_id_vendor(device),
pid = evdev_device_get_id_product(device);

db = libwacom_database_new();
if (!db) {
evdev_log_info(device,
"Failed to initialize libwacom context.\n");
goto out;
}

/* Check if we have a device with the same vid/pid. If not,
we need to loop through all devices and check their paired
device. */
dev = libwacom_new_from_usbid(db, vid, pid, NULL);
if (dev) {
rotate = libwacom_is_reversible(dev);
libwacom_destroy(dev);
goto out;
}

devices = libwacom_list_devices_from_database(db, NULL);
if (!devices)
goto out;
d = devices;
while(*d) {
const WacomMatch *paired;

paired = libwacom_get_paired_device(*d);
if (paired &&
libwacom_match_get_vendor_id(paired) == vid &&
libwacom_match_get_product_id(paired) == pid) {
rotate = libwacom_is_reversible(dev);
break;
}
d++;
}

free(devices);
out:
if (db)
libwacom_database_destroy(db);
#endif

return rotate;
}

static void
tp_init_left_handed(struct tp_dispatch *tp,
struct evdev_device *device)
{
bool want_left_handed = true;

if (device->model_flags & EVDEV_MODEL_APPLE_TOUCHPAD_ONEBUTTON)
want_left_handed = false;
if (want_left_handed)
evdev_init_left_handed(device, tp_change_to_left_handed);

tp->left_handed.rotate = tp_init_left_handed_rotation(tp, device);
}

struct evdev_dispatch *
evdev_mt_touchpad_create(struct evdev_device *device)
{
struct tp_dispatch *tp;
bool want_left_handed = true;

evdev_tag_touchpad(device, device->udev_device);

Expand All @@ -3723,10 +3820,7 @@ evdev_mt_touchpad_create(struct evdev_device *device)
tp->sendevents.config.get_mode = tp_sendevents_get_mode;
tp->sendevents.config.get_default_mode = tp_sendevents_get_default_mode;

if (device->model_flags & EVDEV_MODEL_APPLE_TOUCHPAD_ONEBUTTON)
want_left_handed = false;
if (want_left_handed)
evdev_init_left_handed(device, tp_change_to_left_handed);
tp_init_left_handed(tp, device);

return &tp->base;
}
5 changes: 5 additions & 0 deletions src/evdev-mt-touchpad.h
Expand Up @@ -485,6 +485,11 @@ struct tp_dispatch {
struct libinput_event_listener listener;
struct evdev_device *tablet_mode_switch;
} tablet_mode_switch;

struct {
/* true if the axes need rotation when left-handed is on*/
bool rotate;
} left_handed;
};

static inline struct tp_dispatch*
Expand Down
60 changes: 60 additions & 0 deletions test/test-touchpad.c
Expand Up @@ -2096,6 +2096,7 @@ START_TEST(touchpad_palm_clickfinger_size_2fg)
litest_assert_empty_queue(li);
}
END_TEST

START_TEST(touchpad_left_handed)
{
struct litest_device *dev = litest_current_device();
Expand Down Expand Up @@ -2430,6 +2431,64 @@ START_TEST(touchpad_left_handed_clickpad_delayed)
}
END_TEST

static inline bool
touchpad_has_rotation(struct libevdev *evdev)
{
return libevdev_get_id_vendor(evdev) == VENDOR_ID_WACOM;
}

START_TEST(touchpad_left_handed_rotation)
{
struct litest_device *dev = litest_current_device();
struct libinput_device *d = dev->libinput_device;
struct libinput *li = dev->libinput;
enum libinput_config_status status;
struct libinput_event *event;
struct libinput_event_pointer *p;
bool rotate = touchpad_has_rotation(dev->evdev);

if (!libinput_device_config_left_handed_is_available(d))
return;

status = libinput_device_config_left_handed_set(d, 1);
ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);

litest_drain_events(li);

litest_touch_down(dev, 0, 20, 80);
litest_touch_move_to(dev, 0, 20, 80, 80, 20, 20);
litest_touch_up(dev, 0);
libinput_dispatch(li);

event = libinput_get_event(li);
ck_assert_notnull(event);
do {
double x, y, ux, uy;

p = litest_is_motion_event(event);

x = libinput_event_pointer_get_dx(p);
y = libinput_event_pointer_get_dy(p);
ux = libinput_event_pointer_get_dx_unaccelerated(p);
uy = libinput_event_pointer_get_dy_unaccelerated(p);

if (rotate) {
ck_assert_double_lt(x, 0);
ck_assert_double_gt(y, 0);
ck_assert_double_lt(ux, 0);
ck_assert_double_gt(uy, 0);
} else {
ck_assert_double_gt(x, 0);
ck_assert_double_lt(y, 0);
ck_assert_double_gt(ux, 0);
ck_assert_double_lt(uy, 0);
}

libinput_event_destroy(event);
} while ((event = libinput_get_event(li)));
}
END_TEST

static void
hover_continue(struct litest_device *dev, unsigned int slot,
int x, int y)
Expand Down Expand Up @@ -7010,6 +7069,7 @@ TEST_COLLECTION(touchpad)
litest_add("touchpad:left-handed", touchpad_left_handed_tapping_2fg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
litest_add("touchpad:left-handed", touchpad_left_handed_delayed, LITEST_TOUCHPAD|LITEST_BUTTON, LITEST_CLICKPAD);
litest_add("touchpad:left-handed", touchpad_left_handed_clickpad_delayed, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
litest_add("touchpad:left-handed", touchpad_left_handed_rotation, LITEST_TOUCHPAD, LITEST_ANY);

/* Semi-MT hover tests aren't generic, they only work on this device and
* ignore the semi-mt capability (it doesn't matter for the tests) */
Expand Down

0 comments on commit 6229df1

Please sign in to comment.