diff options
-rw-r--r-- | meson.build | 4 | ||||
-rw-r--r-- | quirks/50-system-dell.quirks | 9 | ||||
-rw-r--r-- | src/evdev-totem.c | 829 | ||||
-rw-r--r-- | src/evdev.c | 8 | ||||
-rw-r--r-- | src/evdev.h | 5 | ||||
-rw-r--r-- | src/quirks.c | 1 | ||||
-rw-r--r-- | src/quirks.h | 1 | ||||
-rw-r--r-- | test/litest-device-dell-canvas-totem-touch.c | 98 | ||||
-rw-r--r-- | test/litest-device-dell-canvas-totem.c | 129 | ||||
-rw-r--r-- | test/litest.c | 24 | ||||
-rw-r--r-- | test/litest.h | 7 | ||||
-rw-r--r-- | test/test-device.c | 4 | ||||
-rw-r--r-- | test/test-tablet.c | 130 | ||||
-rw-r--r-- | test/test-totem.c | 605 |
14 files changed, 1798 insertions, 56 deletions
diff --git a/meson.build b/meson.build index e54bb051..77f27d05 100644 --- a/meson.build +++ b/meson.build @@ -307,6 +307,7 @@ src_libinput = src_libfilter + [ 'src/evdev-debounce.c', 'src/evdev-fallback.c', 'src/evdev-fallback.h', + 'src/evdev-totem.c', 'src/evdev-middle-button.c', 'src/evdev-mt-touchpad.c', 'src/evdev-mt-touchpad.h', @@ -709,6 +710,8 @@ if get_option('tests') 'test/litest-device-bcm5974.c', 'test/litest-device-calibrated-touchscreen.c', 'test/litest-device-cyborg-rat-5.c', + 'test/litest-device-dell-canvas-totem.c', + 'test/litest-device-dell-canvas-totem-touch.c', 'test/litest-device-elantech-touchpad.c', 'test/litest-device-generic-singletouch.c', 'test/litest-device-gpio-keys.c', @@ -851,6 +854,7 @@ if get_option('tests') 'test/test-touch.c', 'test/test-log.c', 'test/test-tablet.c', + 'test/test-totem.c', 'test/test-pad.c', 'test/test-touchpad.c', 'test/test-touchpad-tap.c', diff --git a/quirks/50-system-dell.quirks b/quirks/50-system-dell.quirks index 58071fbf..465ce28b 100644 --- a/quirks/50-system-dell.quirks +++ b/quirks/50-system-dell.quirks @@ -52,3 +52,12 @@ AttrTrackpointMultiplier=1.5 MatchName=*DualPoint Stick MatchDMIModalias=dmi:*svnDellInc.:pnLatitudeE7470* AttrTrackpointMultiplier=0.125 + +# The touch device has the same vid/pid as the totem, the MatchName +# directive is required here +[Canvas Totem] +MatchName=*System Multi Axis +MatchBus=usb +MatchVendor=0x2575 +MatchProduct=0x0204 +ModelDellCanvasTotem=1 diff --git a/src/evdev-totem.c b/src/evdev-totem.c new file mode 100644 index 00000000..6f0a851c --- /dev/null +++ b/src/evdev-totem.c @@ -0,0 +1,829 @@ +/* + * Copyright © 2018 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" +#include "evdev.h" + +enum totem_slot_state { + SLOT_STATE_NONE, + SLOT_STATE_BEGIN, + SLOT_STATE_UPDATE, + SLOT_STATE_END, +}; + +struct totem_slot { + bool dirty; + unsigned int index; + enum totem_slot_state state; + struct libinput_tablet_tool *tool; + struct tablet_axes axes; + unsigned char changed_axes[NCHARS(LIBINPUT_TABLET_TOOL_AXIS_MAX + 1)]; + + struct device_coords last_point; +}; + +struct totem_dispatch { + struct evdev_dispatch base; + struct evdev_device *device; + + int slot; /* current slot */ + struct totem_slot *slots; + size_t nslots; + + struct evdev_device *touch_device; + + /* We only have one button */ + bool button_state_now; + bool button_state_previous; + + enum evdev_arbitration_state arbitration_state; +}; + +static inline struct totem_dispatch* +totem_dispatch(struct evdev_dispatch *totem) +{ + evdev_verify_dispatch_type(totem, DISPATCH_TOTEM); + + return container_of(totem, struct totem_dispatch, base); +} + +static inline struct libinput * +totem_libinput_context(const struct totem_dispatch *totem) +{ + return evdev_libinput_context(totem->device); +} + +static struct libinput_tablet_tool * +totem_new_tool(struct totem_dispatch *totem) +{ + struct libinput *libinput = totem_libinput_context(totem); + struct libinput_tablet_tool *tool; + + tool = zalloc(sizeof *tool); + + *tool = (struct libinput_tablet_tool) { + .type = LIBINPUT_TABLET_TOOL_TYPE_TOTEM, + .serial = 0, + .tool_id = 0, + .refcount = 1, + }; + + tool->pressure_offset = 0; + tool->has_pressure_offset = false; + tool->pressure_threshold.lower = 0; + tool->pressure_threshold.upper = 1; + + set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_X); + set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_Y); + set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z); + set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_SIZE_MAJOR); + set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_SIZE_MINOR); + set_bit(tool->buttons, BTN_0); + + list_insert(&libinput->tool_list, &tool->link); + + return tool; +} + +static inline void +totem_set_touch_device_enabled(struct totem_dispatch *totem, + bool enable_touch_device, + uint64_t time) +{ + struct evdev_device *touch_device = totem->touch_device; + struct evdev_dispatch *dispatch; + struct phys_rect r, *rect = NULL; + enum evdev_arbitration_state state = ARBITRATION_NOT_ACTIVE; + + if (touch_device == NULL) + return; + + /* We just pick the coordinates of the first touch we find. The + * totem only does one tool right now despite being nominally an MT + * device, so let's not go too hard on ourselves*/ + for (size_t i = 0; !enable_touch_device && i < totem->nslots; i++) { + struct totem_slot *slot = &totem->slots[i]; + struct phys_coords mm; + + if (slot->state == SLOT_STATE_NONE) + continue; + + /* Totem size is ~70mm. We could calculate the real size but + until we need that, hardcoding it is enough */ + mm = evdev_device_units_to_mm(totem->device, &slot->axes.point); + r.x = mm.x - 30; + r.y = mm.y - 30; + r.w = 100; + r.h = 100; + + rect = &r; + + state = ARBITRATION_IGNORE_RECT; + break; + } + + dispatch = touch_device->dispatch; + + if (enable_touch_device) { + if (dispatch->interface->touch_arbitration_toggle) + dispatch->interface->touch_arbitration_toggle(dispatch, + touch_device, + state, + rect, + time); + } else { + switch (totem->arbitration_state) { + case ARBITRATION_IGNORE_ALL: + abort(); + case ARBITRATION_NOT_ACTIVE: + if (dispatch->interface->touch_arbitration_toggle) + dispatch->interface->touch_arbitration_toggle(dispatch, + touch_device, + state, + rect, + time); + break; + case ARBITRATION_IGNORE_RECT: + if (dispatch->interface->touch_arbitration_update_rect) + dispatch->interface->touch_arbitration_update_rect(dispatch, + touch_device, + rect, + time); + break; + } + } + totem->arbitration_state = state; +} + +static void +totem_process_key(struct totem_dispatch *totem, + struct evdev_device *device, + struct input_event *e, + uint64_t time) +{ + switch(e->code) { + case BTN_0: + totem->button_state_now = !!e->value; + break; + default: + evdev_log_info(device, + "Unhandled KEY event code %#x\n", + e->code); + break; + } +} + +static void +totem_process_abs(struct totem_dispatch *totem, + struct evdev_device *device, + struct input_event *e, + uint64_t time) +{ + struct totem_slot *slot = &totem->slots[totem->slot]; + + switch(e->code) { + case ABS_MT_SLOT: + if ((size_t)e->value >= totem->nslots) { + evdev_log_bug_libinput(device, + "exceeded slot count (%d of max %zd)\n", + e->value, + totem->nslots); + e->value = totem->nslots - 1; + } + totem->slot = e->value; + return; + case ABS_MT_TRACKING_ID: + /* If the totem is already down on init, we currently + ignore it */ + if (e->value >= 0) + slot->state = SLOT_STATE_BEGIN; + else if (slot->state != SLOT_STATE_NONE) + slot->state = SLOT_STATE_END; + break; + case ABS_MT_POSITION_X: + set_bit(slot->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_X); + break; + case ABS_MT_POSITION_Y: + set_bit(slot->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_Y); + break; + case ABS_MT_TOUCH_MAJOR: + set_bit(slot->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_SIZE_MAJOR); + break; + case ABS_MT_TOUCH_MINOR: + set_bit(slot->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_SIZE_MINOR); + break; + case ABS_MT_ORIENTATION: + set_bit(slot->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z); + break; + case ABS_MT_TOOL_TYPE: + if (e->value != MT_TOOL_DIAL) { + evdev_log_info(device, + "Unexpected tool type %#x, changing to dial\n", + e->code); + } + break; + default: + evdev_log_info(device, + "Unhandled ABS event code %#x\n", + e->code); + break; + } +} + +static bool +totem_slot_fetch_axes(struct totem_dispatch *totem, + struct totem_slot *slot, + struct libinput_tablet_tool *tool, + struct tablet_axes *axes_out, + uint64_t time) +{ + struct evdev_device *device = totem->device; + const char tmp[sizeof(slot->changed_axes)] = {0}; + struct tablet_axes axes = {0}; + struct device_float_coords delta; + bool rc = false; + + if (memcmp(tmp, slot->changed_axes, sizeof(tmp)) == 0) { + axes = slot->axes; + goto out; + } + + if (bit_is_set(slot->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_X) || + bit_is_set(slot->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_Y)) { + slot->axes.point.x = libevdev_get_slot_value(device->evdev, + slot->index, + ABS_MT_POSITION_X); + slot->axes.point.y = libevdev_get_slot_value(device->evdev, + slot->index, + ABS_MT_POSITION_Y); + } + + if (bit_is_set(slot->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z)) { + int angle = libevdev_get_slot_value(device->evdev, + slot->index, + ABS_MT_ORIENTATION); + /* The kernel gives us ±90 degrees off neutral */ + slot->axes.rotation = (360 - angle) % 360; + } + + if (bit_is_set(slot->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_SIZE_MAJOR) || + bit_is_set(slot->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_SIZE_MINOR)) { + int major, minor; + unsigned int rmajor, rminor; + + major = libevdev_get_slot_value(device->evdev, + slot->index, + ABS_MT_TOUCH_MAJOR); + minor = libevdev_get_slot_value(device->evdev, + slot->index, + ABS_MT_TOUCH_MINOR); + rmajor = libevdev_get_abs_resolution(device->evdev, ABS_MT_TOUCH_MAJOR); + rminor = libevdev_get_abs_resolution(device->evdev, ABS_MT_TOUCH_MINOR); + slot->axes.size.major = (double)major/rmajor; + slot->axes.size.minor = (double)minor/rminor; + } + + axes.point = slot->axes.point; + axes.rotation = slot->axes.rotation; + axes.size = slot->axes.size; + + delta.x = slot->axes.point.x - slot->last_point.x; + delta.y = slot->axes.point.y - slot->last_point.y; + axes.delta = filter_dispatch(device->pointer.filter, &delta, tool, time); + + rc = true; +out: + *axes_out = axes; + return rc; + +} + +static void +totem_slot_mark_all_axes_changed(struct totem_dispatch *totem, + struct totem_slot *slot, + struct libinput_tablet_tool *tool) +{ + static_assert(sizeof(slot->changed_axes) == + sizeof(tool->axis_caps), + "Mismatching array sizes"); + + memcpy(slot->changed_axes, + tool->axis_caps, + sizeof(slot->changed_axes)); +} + +static inline void +totem_slot_reset_changed_axes(struct totem_dispatch *totem, + struct totem_slot *slot) +{ + memset(slot->changed_axes, 0, sizeof(slot->changed_axes)); +} + +static inline void +slot_axes_initialize(struct totem_dispatch *totem, + struct totem_slot *slot) +{ + struct evdev_device *device = totem->device; + + slot->axes.point.x = libevdev_get_slot_value(device->evdev, + slot->index, + ABS_MT_POSITION_X); + slot->axes.point.y = libevdev_get_slot_value(device->evdev, + slot->index, + ABS_MT_POSITION_Y); + slot->last_point.x = slot->axes.point.x; + slot->last_point.y = slot->axes.point.y; +} + +static enum totem_slot_state +totem_handle_slot_state(struct totem_dispatch *totem, + struct totem_slot *slot, + uint64_t time) +{ + struct evdev_device *device = totem->device; + struct tablet_axes axes; + enum libinput_tablet_tool_tip_state tip_state; + bool updated; + + switch (slot->state) { + case SLOT_STATE_BEGIN: + if (!slot->tool) + slot->tool = totem_new_tool(totem); + slot_axes_initialize(totem, slot); + totem_slot_mark_all_axes_changed(totem, slot, slot->tool); + break; + case SLOT_STATE_UPDATE: + case SLOT_STATE_END: + assert(slot->tool); + break; + case SLOT_STATE_NONE: + return SLOT_STATE_NONE; + } + + tip_state = LIBINPUT_TABLET_TOOL_TIP_UP; + updated = totem_slot_fetch_axes(totem, slot, slot->tool, &axes, time); + + switch (slot->state) { + case SLOT_STATE_BEGIN: + tip_state = LIBINPUT_TABLET_TOOL_TIP_DOWN; + tablet_notify_proximity(&device->base, + time, + slot->tool, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN, + slot->changed_axes, + &axes); + totem_slot_reset_changed_axes(totem, slot); + tablet_notify_tip(&device->base, + time, + slot->tool, + tip_state, + slot->changed_axes, + &axes); + slot->state = SLOT_STATE_UPDATE; + break; + case SLOT_STATE_UPDATE: + tip_state = LIBINPUT_TABLET_TOOL_TIP_DOWN; + if (updated) { + tablet_notify_axis(&device->base, + time, + slot->tool, + tip_state, + slot->changed_axes, + &axes); + } + break; + case SLOT_STATE_END: + /* prox out is handled after button events */ + break; + case SLOT_STATE_NONE: + abort(); + break; + } + + /* We only have one button but possibly multiple totems. It's not + * clear how the firmware will work, so for now we just handle the + * button state in the first slot. + * + * Due to the design of the totem we're also less fancy about + * button handling than the tablet code. Worst case, you might get + * tip up before button up but meh. + */ + if (totem->button_state_now != totem->button_state_previous) { + enum libinput_button_state btn_state; + + if (totem->button_state_now) + btn_state = LIBINPUT_BUTTON_STATE_PRESSED; + else + btn_state = LIBINPUT_BUTTON_STATE_RELEASED; + + tablet_notify_button(&device->base, + time, + slot->tool, + tip_state, + &axes, + BTN_0, + btn_state); + + totem->button_state_previous = totem->button_state_now; + } + + switch(slot->state) { + case SLOT_STATE_BEGIN: + case SLOT_STATE_UPDATE: + break; + case SLOT_STATE_END: + tip_state = LIBINPUT_TABLET_TOOL_TIP_UP; + tablet_notify_tip(&device->base, + time, + slot->tool, + tip_state, + slot->changed_axes, + &axes); + totem_slot_reset_changed_axes(totem, slot); + tablet_notify_proximity(&device->base, + time, + slot->tool, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT, + slot->changed_axes, + &axes); + slot->state = SLOT_STATE_NONE; + break; + case SLOT_STATE_NONE: + abort(); + break; + } + + slot->last_point = slot->axes.point; + totem_slot_reset_changed_axes(totem, slot); + + return slot->state; +} + +static enum totem_slot_state +totem_handle_state(struct totem_dispatch *totem, + uint64_t time) +{ + enum totem_slot_state global_state = SLOT_STATE_NONE; + + for (size_t i = 0; i < totem->nslots; i++) { + enum totem_slot_state s; + + s = totem_handle_slot_state(totem, + &totem->slots[i], + time); + + /* If one slot is active, the totem is active */ + if (s != SLOT_STATE_NONE) + global_state = SLOT_STATE_UPDATE; + } + + return global_state; +} + +static void +totem_interface_process(struct evdev_dispatch *dispatch, + struct evdev_device *device, + struct input_event *e, + uint64_t time) +{ + struct totem_dispatch *totem = totem_dispatch(dispatch); + enum totem_slot_state global_state; + bool enable_touch; + + switch(e->type) { + case EV_ABS: + totem_process_abs(totem, device, e, time); + break; + case EV_KEY: + totem_process_key(totem, device, e, time); + break; + case EV_MSC: + /* timestamp, ignore */ + break; + case EV_SYN: + global_state = totem_handle_state(totem, time); + enable_touch = (global_state == SLOT_STATE_NONE); + totem_set_touch_device_enabled(totem, + enable_touch, + time); + break; + default: + evdev_log_error(device, + "Unexpected event type %s (%#x)\n", + libevdev_event_type_get_name(e->type), + e->type); + break; + } +} + +static void +totem_interface_suspend(struct evdev_dispatch *dispatch, + struct evdev_device *device) +{ + struct totem_dispatch *totem = totem_dispatch(dispatch); + uint64_t now = libinput_now(evdev_libinput_context(device)); + + for (size_t i = 0; i < totem->nslots; i++) { + struct totem_slot *slot = &totem->slots[i]; + struct tablet_axes axes; + enum libinput_tablet_tool_tip_state tip_state; + + /* If we never initialized a tool, we can skip everything */ + if (!slot->tool) + continue; + + totem_slot_fetch_axes(totem, slot, slot->tool, &axes, now); + totem_slot_reset_changed_axes(totem, slot); + + if (slot->state == SLOT_STATE_NONE) + tip_state = LIBINPUT_TABLET_TOOL_TIP_UP; + else + tip_state = LIBINPUT_TABLET_TOOL_TIP_DOWN; + + if (totem->button_state_now) { + tablet_notify_button(&device->base, + now, + slot->tool, + tip_state, + &axes, + BTN_0, + LIBINPUT_BUTTON_STATE_RELEASED); + + totem->button_state_now = false; + totem->button_state_previous = false; + } + + if (slot->state != SLOT_STATE_NONE) { + tablet_notify_tip(&device->base, + now, + slot->tool, + LIBINPUT_TABLET_TOOL_TIP_UP, + slot->changed_axes, + &axes); + } + tablet_notify_proximity(&device->base, + now, + slot->tool, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT, + slot->changed_axes, + &axes); + } + totem_set_touch_device_enabled(totem, true, now); +} + +static void +totem_interface_destroy(struct evdev_dispatch *dispatch) +{ + struct totem_dispatch *totem = totem_dispatch(dispatch); + + free(totem->slots); + free(totem); +} + +static void +totem_interface_device_added(struct evdev_device *device, + struct evdev_device *added_device) +{ + struct totem_dispatch *totem = totem_dispatch(device->dispatch); + struct libinput_device_group *g1, *g2; + + if ((evdev_device_get_id_vendor(added_device) != + evdev_device_get_id_vendor(device)) || + (evdev_device_get_id_product(added_device) != + evdev_device_get_id_product(device))) + return; + + /* virtual devices don't have device groups, so check for that + libinput replay */ + g1 = libinput_device_get_device_group(&device->base); + g2 = libinput_device_get_device_group(&added_device->base); + if (g1 && g2 && g1->identifier != g2->identifier) + return; + + if (totem->touch_device != NULL) { + evdev_log_bug_libinput(device, + "already has a paired touch device, ignoring (%s)\n", + added_device->devname); + return; + } + + totem->touch_device = added_device; + evdev_log_info(device, "%s: is the totem touch device\n", added_device->devname); +} + +static void +totem_interface_device_removed(struct evdev_device *device, + struct evdev_device *removed_device) +{ + struct totem_dispatch *totem = totem_dispatch(device->dispatch); + + if (totem->touch_device != removed_device) + return; + + totem_set_touch_device_enabled(totem, true, + libinput_now(evdev_libinput_context(device))); + totem->touch_device = NULL; +} + +static void +totem_interface_initial_proximity(struct evdev_device *device, + struct evdev_dispatch *dispatch) +{ + struct totem_dispatch *totem = totem_dispatch(dispatch); + uint64_t now = libinput_now(evdev_libinput_context(device)); + bool enable_touch = true; + + for (size_t i = 0; i < totem->nslots; i++) { + struct totem_slot *slot = &totem->slots[i]; + struct tablet_axes axes; + int tracking_id; + + tracking_id = libevdev_get_slot_value(device->evdev, + i, + ABS_MT_TRACKING_ID); + if (tracking_id == -1) + continue; + + slot->tool = totem_new_tool(totem); + slot_axes_initialize(totem, slot); + totem_slot_mark_all_axes_changed(totem, slot, slot->tool); + totem_slot_fetch_axes(totem, slot, slot->tool, &axes, now); + tablet_notify_proximity(&device->base, + now, + slot->tool, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN, + slot->changed_axes, + &axes); + totem_slot_reset_changed_axes(totem, slot); + tablet_notify_tip(&device->base, + now, + slot->tool, + LIBINPUT_TABLET_TOOL_TIP_DOWN, + slot->changed_axes, + &axes); + slot->state = SLOT_STATE_UPDATE; + enable_touch = false; + } + + totem_set_touch_device_enabled(totem, enable_touch, now); +} + +struct evdev_dispatch_interface totem_interface = { + .process = totem_interface_process, + .suspend = totem_interface_suspend, + .remove = NULL, + .destroy = totem_interface_destroy, + .device_added = totem_interface_device_added, + .device_removed = totem_interface_device_removed, + .device_suspended = totem_interface_device_added, /* treat as remove */ + .device_resumed = totem_interface_device_removed, /* treat as add */ + .post_added = totem_interface_initial_proximity, + .touch_arbitration_toggle = NULL, + .touch_arbitration_update_rect = NULL, + .get_switch_state = NULL, +}; + +static bool +totem_reject_device(struct evdev_device *device) +{ + struct libevdev *evdev = device->evdev; + bool has_xy, has_slot, has_tool_dial, has_size; + double w, h; + + has_xy = libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) && + libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y); + has_slot = libevdev_has_event_code(evdev, EV_ABS, ABS_MT_SLOT); + has_tool_dial = libevdev_has_event_code(evdev, EV_ABS, ABS_MT_TOOL_TYPE) && + libevdev_get_abs_maximum(evdev, ABS_MT_TOOL_TYPE) >= MT_TOOL_DIAL; + has_size = evdev_device_get_size(device, &w, &h) == 0; + has_size |= libevdev_get_abs_resolution(device->evdev, ABS_MT_TOUCH_MAJOR) > 0; + has_size |= libevdev_get_abs_resolution(device->evdev, ABS_MT_TOUCH_MINOR) > 0; + + if (has_xy && has_slot && has_tool_dial && has_size) + return false; + + evdev_log_bug_libinput(device, + "missing totem capabilities:%s%s%s%s. " + "Ignoring this device.\n", + has_xy ? "" : " xy", + has_slot ? "" : " slot", + has_tool_dial ? "" : " dial", + has_size ? "" : " resolutions"); + return true; +} + +static uint32_t +totem_accel_config_get_profiles(struct libinput_device *libinput_device) +{ + return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; +} + +static enum libinput_config_status +totem_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 +totem_accel_config_get_profile(struct libinput_device *libinput_device) +{ + return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; +} + +static enum libinput_config_accel_profile +totem_accel_config_get_default_profile(struct libinput_device *libinput_device) +{ + return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; +} + +static int +totem_init_accel(struct totem_dispatch *totem, struct evdev_device *device) +{ + const struct input_absinfo *x, *y; + struct motion_filter *filter; + + x = device->abs.absinfo_x; + y = device->abs.absinfo_y; + + /* same filter as the tablet */ + filter = create_pointer_accelerator_filter_tablet(x->resolution, + y->resolution); + if (!filter) + return -1; + + evdev_device_init_pointer_acceleration(device, filter); + + /* we override the profile hooks for accel configuration with hooks + * that don't allow selection of profiles */ + device->pointer.config.get_profiles = totem_accel_config_get_profiles; + device->pointer.config.set_profile = totem_accel_config_set_profile; + device->pointer.config.get_profile = totem_accel_config_get_profile; + device->pointer.config.get_default_profile = totem_accel_config_get_default_profile; + + return 0; +} + +struct evdev_dispatch * +evdev_totem_create(struct evdev_device *device) +{ + struct totem_dispatch *totem; + struct totem_slot *slots; + int num_slots; + + if (totem_reject_device(device)) + return NULL; + + totem = zalloc(sizeof *totem); + totem->device = device; + totem->base.dispatch_type = DISPATCH_TOTEM; + totem->base.interface = &totem_interface; + + num_slots = libevdev_get_num_slots(device->evdev); + if (num_slots <= 0) + goto error; + + totem->slot = libevdev_get_current_slot(device->evdev); + slots = zalloc(num_slots * sizeof(*totem->slots)); + + for (int slot = 0; slot < num_slots; ++slot) { + slots[slot].index = slot; + } + + totem->slots = slots; + totem->nslots = num_slots; + + evdev_init_sendevents(device, &totem->base); + totem_init_accel(totem, device); + + return &totem->base; +error: + totem_interface_destroy(&totem->base); + return NULL; +} diff --git a/src/evdev.c b/src/evdev.c index 5c080590..14fce3ff 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -1728,6 +1728,14 @@ evdev_configure_device(struct evdev_device *device) udev_tags &= ~EVDEV_UDEV_TAG_TOUCHSCREEN; } + if (evdev_device_has_model_quirk(device, + QUIRK_MODEL_DELL_CANVAS_TOTEM)) { + dispatch = evdev_totem_create(device); + device->seat_caps |= EVDEV_DEVICE_TABLET; + evdev_log_info(device, "device is a totem\n"); + return dispatch; + } + /* libwacom assigns touchpad (or touchscreen) _and_ tablet to the tablet touch bits, so make sure we don't initialize the tablet interface for the touch device */ diff --git a/src/evdev.h b/src/evdev.h index 1ebd04f7..dd5b45d5 100644 --- a/src/evdev.h +++ b/src/evdev.h @@ -336,6 +336,7 @@ enum evdev_dispatch_type { DISPATCH_TOUCHPAD, DISPATCH_TABLET, DISPATCH_TABLET_PAD, + DISPATCH_TOTEM, }; struct evdev_dispatch { @@ -431,6 +432,9 @@ evdev_lid_switch_dispatch_create(struct evdev_device *device); struct evdev_dispatch * fallback_dispatch_create(struct libinput_device *libinput_device); +struct evdev_dispatch * +evdev_totem_create(struct evdev_device *device); + bool evdev_is_fake_mt_device(struct evdev_device *device); @@ -922,7 +926,6 @@ evdev_phys_rect_to_units(const struct evdev_device *device, return units; } - static inline void evdev_device_init_abs_range_warnings(struct evdev_device *device) { diff --git a/src/quirks.c b/src/quirks.c index c5cc8e34..50dab47d 100644 --- a/src/quirks.c +++ b/src/quirks.c @@ -254,6 +254,7 @@ quirk_get_name(enum quirk q) case QUIRK_MODEL_TOUCHPAD_VISIBLE_MARKER: return "ModelTouchpadVisibleMarker"; case QUIRK_MODEL_TRACKBALL: return "ModelTrackball"; case QUIRK_MODEL_WACOM_TOUCHPAD: return "ModelWacomTouchpad"; + case QUIRK_MODEL_DELL_CANVAS_TOTEM: return "ModelDellCanvasTotem"; case QUIRK_ATTR_SIZE_HINT: return "AttrSizeHint"; case QUIRK_ATTR_TOUCH_SIZE_RANGE: return "AttrTouchSizeRange"; diff --git a/src/quirks.h b/src/quirks.h index 2d367b83..df9dca19 100644 --- a/src/quirks.h +++ b/src/quirks.h @@ -86,6 +86,7 @@ enum quirk { QUIRK_MODEL_TOUCHPAD_VISIBLE_MARKER, QUIRK_MODEL_TRACKBALL, QUIRK_MODEL_WACOM_TOUCHPAD, + QUIRK_MODEL_DELL_CANVAS_TOTEM, _QUIRK_LAST_MODEL_QUIRK_, /* Guard: do not modify */ diff --git a/test/litest-device-dell-canvas-totem-touch.c b/test/litest-device-dell-canvas-totem-touch.c new file mode 100644 index 00000000..6ffbf0d5 --- /dev/null +++ b/test/litest-device-dell-canvas-totem-touch.c @@ -0,0 +1,98 @@ +/* + * Copyright © 2019 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include "litest.h" +#include "litest-int.h" + +static struct input_event down[] = { + { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct input_event move[] = { + { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct litest_device_interface interface = { + .touch_down_events = down, + .touch_move_events = move, +}; + +static struct input_absinfo absinfo[] = { + { ABS_MT_SLOT, 0, 4, 0, 0, 0 }, + { ABS_X, 0, 32767, 0, 0, 55 }, + { ABS_Y, 0, 32767, 0, 0, 98 }, + { ABS_MT_POSITION_X, 0, 32767, 0, 0, 55 }, + { ABS_MT_POSITION_Y, 0, 32767, 0, 0, 98 }, + { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 }, + { .value = -1 }, +}; + +static struct input_id input_id = { + .bustype = 0x3, + .vendor = 0x2575, + .product = 0x0204, + .version = 0x111, +}; + +static int events[] = { + EV_KEY, BTN_TOUCH, + EV_MSC, MSC_TIMESTAMP, + INPUT_PROP_MAX, INPUT_PROP_DIRECT, + -1, -1, +}; + +static const char udev_rule[] = +"ACTION==\"remove\", GOTO=\"totem_end\"\n" +"KERNEL!=\"event*\", GOTO=\"totem_end\"\n" +"\n" +"ATTRS{name}==\"litest Advanced Silicon S.A. CoolTouch® System*\",\\\n" +" ENV{LIBINPUT_DEVICE_GROUP}=\"dell-canvas-totem-group\"\n" +"\n" +"LABEL=\"totem_end\""; + +TEST_DEVICE("dell-canvas-totem-touch", + .type = LITEST_DELL_CANVAS_TOTEM_TOUCH, + .features = LITEST_TOUCH, + .interface = &interface, + + .name = "Advanced Silicon S.A. CoolTouch® System", + .id = &input_id, + .events = events, + .absinfo = absinfo, + .udev_rule = udev_rule, +) diff --git a/test/litest-device-dell-canvas-totem.c b/test/litest-device-dell-canvas-totem.c new file mode 100644 index 00000000..19f00c58 --- /dev/null +++ b/test/litest-device-dell-canvas-totem.c @@ -0,0 +1,129 @@ +/* + * Copyright © 2018 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include "litest.h" +#include "litest-int.h" + +/* We don't expect anything but slot 0 to be used, ever */ +#define TOTEM_SLOT 0 + +static struct input_event down[] = { + { .type = EV_ABS, .code = ABS_MT_SLOT, .value = TOTEM_SLOT }, + { .type = EV_ABS, .code = ABS_MT_TOOL_TYPE, .value = MT_TOOL_DIAL }, /* fixed value in device */ + { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_ORIENTATION, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_TOUCH_MAJOR, .value = 718 }, /* fixed value in device */ + { .type = EV_ABS, .code = ABS_MT_TOUCH_MINOR, .value = 718 }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct input_event move[] = { + { .type = EV_ABS, .code = ABS_MT_SLOT, .value = TOTEM_SLOT }, + { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_ORIENTATION, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_TOUCH_MAJOR, .value = 718 }, /* fixed value in device */ + { .type = EV_ABS, .code = ABS_MT_TOUCH_MINOR, .value = 718 }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct input_event up[] = { + { .type = EV_ABS, .code = ABS_MT_SLOT, .value = TOTEM_SLOT }, + { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = -1 }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static int +get_axis_default(struct litest_device *d, unsigned int evcode, int32_t *value) +{ + switch (evcode) { + case ABS_MT_ORIENTATION: + *value = 0; + return 0; + } + return 1; +} + +static struct litest_device_interface interface = { + .tablet_proximity_in_events = down, + .tablet_proximity_out_events = up, + .tablet_motion_events = move, + + .get_axis_default = get_axis_default, +}; + +static struct input_absinfo absinfo[] = { + { ABS_MT_SLOT, 0, 4, 0, 0, 0 }, + { ABS_MT_TOUCH_MAJOR, 0, 32767, 0, 0, 10 }, + { ABS_MT_TOUCH_MINOR, 0, 32767, 0, 0, 10 }, + { ABS_MT_ORIENTATION, -89, 89, 0, 0, 0 }, + { ABS_MT_POSITION_X, 0, 32767, 0, 0, 55 }, + { ABS_MT_POSITION_Y, 0, 32767, 0, 0, 98 }, + /* The real device has a min/max of 10/10 but uinput didn't allow + * this */ + { ABS_MT_TOOL_TYPE, 9, 10, 0, 0, 0 }, + { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 }, + { .value = -1 }, +}; + +static struct input_id input_id = { + .bustype = 0x3, + .vendor = 0x2575, + .product = 0x0204, + .version = 0x111, +}; + +static int events[] = { + EV_KEY, BTN_0, + EV_MSC, MSC_TIMESTAMP, + INPUT_PROP_MAX, INPUT_PROP_DIRECT, + -1, -1, +}; + +static const char udev_rule[] = +"ACTION==\"remove\", GOTO=\"totem_end\"\n" +"KERNEL!=\"event*\", GOTO=\"totem_end\"\n" +"\n" +"ATTRS{name}==\"litest Advanced Silicon S.A. CoolTouch® System System Multi Axis*\",\\\n" +" ENV{LIBINPUT_DEVICE_GROUP}=\"dell-canvas-totem-group\"\n" +"\n" +"LABEL=\"totem_end\""; + +TEST_DEVICE("dell-canvas-totem", + .type = LITEST_DELL_CANVAS_TOTEM, + .features = LITEST_TOTEM | LITEST_TABLET, + .interface = &interface, + + .name = "Advanced Silicon S.A. CoolTouch® System System Multi Axis", + .id = &input_id, + .events = events, + .absinfo = absinfo, + .udev_rule = udev_rule, +) diff --git a/test/litest.c b/test/litest.c index 6941ab9e..3befc717 100644 --- a/test/litest.c +++ b/test/litest.c @@ -2164,16 +2164,22 @@ auto_assign_tablet_value(struct litest_device *d, int x, int y, struct axis_replacement *axes) { + static int tracking_id; int value = ev->value; if (value != LITEST_AUTO_ASSIGN || ev->type != EV_ABS) return value; switch (ev->code) { + case ABS_MT_TRACKING_ID: + value = ++tracking_id; + break; case ABS_X: + case ABS_MT_POSITION_X: value = litest_scale(d, ABS_X, x); break; case ABS_Y: + case ABS_MT_POSITION_Y: value = litest_scale(d, ABS_Y, y); break; default: @@ -3376,6 +3382,24 @@ void litest_assert_tablet_proximity_event(struct libinput *li, libinput_event_destroy(event); } +void litest_assert_tablet_tip_event(struct libinput *li, + enum libinput_tablet_tool_tip_state state) +{ + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + enum libinput_event_type type = LIBINPUT_EVENT_TABLET_TOOL_TIP; + + litest_wait_for_event(li); + event = libinput_get_event(li); + + litest_assert_notnull(event); + litest_assert_event_type(event, type); + tev = libinput_event_get_tablet_tool_event(event); + litest_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tev), + state); + libinput_event_destroy(event); +} + struct libinput_event_tablet_pad * litest_is_pad_button_event(struct libinput_event *event, unsigned int button, diff --git a/test/litest.h b/test/litest.h index 0074c869..203f12ec 100644 --- a/test/litest.h +++ b/test/litest.h @@ -277,6 +277,8 @@ enum litest_device_type { LITEST_AIPTEK, LITEST_TOUCHSCREEN_INVALID_RANGE, LITEST_TOUCHSCREEN_MT_TOOL_TYPE, + LITEST_DELL_CANVAS_TOTEM, + LITEST_DELL_CANVAS_TOTEM_TOUCH, }; enum litest_device_feature { @@ -314,6 +316,7 @@ enum litest_device_feature { LITEST_NO_DEBOUNCE = bit(28), LITEST_TOOL_MOUSE = bit(29), LITEST_DIRECT = bit(30), + LITEST_TOTEM = bit(31), _LITEST_DEVICE_FEATURE__FORCE_SIZE = LONG_MAX, }; @@ -763,6 +766,10 @@ litest_assert_tablet_proximity_event(struct libinput *li, enum libinput_tablet_tool_proximity_state state); void +litest_assert_tablet_tip_event(struct libinput *li, + enum libinput_tablet_tool_tip_state state); + +void litest_assert_pad_button_event(struct libinput *li, unsigned int button, enum libinput_button_state state); diff --git a/test/test-device.c b/test/test-device.c index 77f4d7bf..3f79201a 100644 --- a/test/test-device.c +++ b/test/test-device.c @@ -1612,7 +1612,7 @@ TEST_COLLECTION(device) litest_add("device:wheel", device_wheel_only, LITEST_WHEEL, LITEST_RELATIVE|LITEST_ABSOLUTE|LITEST_TABLET); litest_add_no_device("device:accelerometer", device_accelerometer); - litest_add("device:udev tags", device_udev_tag_wacom_tablet, LITEST_TABLET, LITEST_ANY); + litest_add("device:udev tags", device_udev_tag_wacom_tablet, LITEST_TABLET, LITEST_TOTEM); litest_add_no_device("device:invalid rel events", device_nonpointer_rel); litest_add_no_device("device:invalid rel events", device_touchpad_rel); @@ -1630,7 +1630,7 @@ TEST_COLLECTION(device) litest_add("device:size", device_has_size, LITEST_TOUCHPAD, LITEST_ANY); litest_add("device:size", device_has_size, LITEST_TABLET, LITEST_ANY); litest_add("device:size", device_has_no_size, LITEST_ANY, - LITEST_TOUCHPAD|LITEST_TABLET|LITEST_TOUCH|LITEST_ABSOLUTE|LITEST_SINGLE_TOUCH); + LITEST_TOUCHPAD|LITEST_TABLET|LITEST_TOUCH|LITEST_ABSOLUTE|LITEST_SINGLE_TOUCH|LITEST_TOTEM); litest_add_for_device("device:output", device_get_output, LITEST_CALIBRATED_TOUCHSCREEN); litest_add("device:output", device_no_output, LITEST_RELATIVE, LITEST_ANY); diff --git a/test/test-tablet.c b/test/test-tablet.c index 0f074aca..2b650035 100644 --- a/test/test-tablet.c +++ b/test/test-tablet.c @@ -36,6 +36,18 @@ #include "evdev-tablet.h" #include "litest.h" +static inline unsigned int +pick_stylus_or_btn0(struct litest_device *dev) +{ + if (libevdev_has_event_code(dev->evdev, EV_KEY, BTN_STYLUS)) + return BTN_STYLUS; + + if (libevdev_has_event_code(dev->evdev, EV_KEY, BTN_0)) + return BTN_0; /* totem */ + + abort(); +} + START_TEST(button_down_up) { struct litest_device *dev = litest_current_device(); @@ -47,39 +59,34 @@ START_TEST(button_down_up) { ABS_PRESSURE, 0 }, { -1, -1 } }; - - if (!libevdev_has_event_code(dev->evdev, EV_KEY, BTN_STYLUS)) - return; + unsigned int button = pick_stylus_or_btn0(dev); litest_tablet_proximity_in(dev, 10, 10, axes); litest_drain_events(li); - litest_event(dev, EV_KEY, BTN_STYLUS, 1); - litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_button_click(dev, button, true); libinput_dispatch(li); event = libinput_get_event(li); tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_BUTTON); ck_assert_int_eq(libinput_event_tablet_tool_get_button(tev), - BTN_STYLUS); + button); ck_assert_int_eq(libinput_event_tablet_tool_get_button_state(tev), LIBINPUT_BUTTON_STATE_PRESSED); libinput_event_destroy(event); litest_assert_empty_queue(li); - litest_event(dev, EV_KEY, BTN_STYLUS, 0); - litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_button_click(dev, button, false); libinput_dispatch(li); event = libinput_get_event(li); tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_BUTTON); ck_assert_int_eq(libinput_event_tablet_tool_get_button(tev), - BTN_STYLUS); + button); ck_assert_int_eq(libinput_event_tablet_tool_get_button_state(tev), LIBINPUT_BUTTON_STATE_RELEASED); libinput_event_destroy(event); litest_assert_empty_queue(li); - } END_TEST @@ -95,25 +102,28 @@ START_TEST(button_seat_count) { ABS_PRESSURE, 0 }, { -1, -1 } }; + unsigned int button = pick_stylus_or_btn0(dev); - if (!libevdev_has_event_code(dev->evdev, EV_KEY, BTN_STYLUS)) - return; + switch (button) { + case BTN_STYLUS: + dev2 = litest_add_device(li, LITEST_WACOM_CINTIQ_13HDT_PEN); + break; + case BTN_0: + dev2 = litest_add_device(li, LITEST_DELL_CANVAS_TOTEM); + break; + } - dev2 = litest_add_device(li, LITEST_WACOM_CINTIQ_13HDT_PEN); litest_tablet_proximity_in(dev, 10, 10, axes); litest_tablet_proximity_in(dev2, 10, 10, axes); litest_drain_events(li); - litest_event(dev, EV_KEY, BTN_STYLUS, 1); - litest_event(dev, EV_SYN, SYN_REPORT, 0); - litest_event(dev2, EV_KEY, BTN_STYLUS, 1); - litest_event(dev2, EV_SYN, SYN_REPORT, 0); + litest_button_click(dev, button, true); + litest_button_click(dev2, button, true); libinput_dispatch(li); event = libinput_get_event(li); tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_BUTTON); - ck_assert_int_eq(libinput_event_tablet_tool_get_button(tev), - BTN_STYLUS); + ck_assert_int_eq(libinput_event_tablet_tool_get_button(tev), button); ck_assert_int_eq(libinput_event_tablet_tool_get_button_state(tev), LIBINPUT_BUTTON_STATE_PRESSED); ck_assert_int_eq(libinput_event_tablet_tool_get_seat_button_count(tev), 1); @@ -121,8 +131,7 @@ START_TEST(button_seat_count) event = libinput_get_event(li); tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_BUTTON); - ck_assert_int_eq(libinput_event_tablet_tool_get_button(tev), - BTN_STYLUS); + ck_assert_int_eq(libinput_event_tablet_tool_get_button(tev), button); ck_assert_int_eq(libinput_event_tablet_tool_get_button_state(tev), LIBINPUT_BUTTON_STATE_PRESSED); ck_assert_int_eq(libinput_event_tablet_tool_get_seat_button_count(tev), 2); @@ -130,18 +139,15 @@ START_TEST(button_seat_count) litest_assert_empty_queue(li); - litest_event(dev2, EV_KEY, BTN_STYLUS, 0); - litest_event(dev2, EV_SYN, SYN_REPORT, 0); - litest_event(dev, EV_KEY, BTN_STYLUS, 0); - litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_button_click(dev2, button, false); + litest_button_click(dev, button, false); libinput_dispatch(li); event = libinput_get_event(li); tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_BUTTON); ck_assert_int_eq(libinput_event_tablet_tool_get_button_state(tev), LIBINPUT_BUTTON_STATE_RELEASED); - ck_assert_int_eq(libinput_event_tablet_tool_get_button(tev), - BTN_STYLUS); + ck_assert_int_eq(libinput_event_tablet_tool_get_button(tev), button); ck_assert_int_eq(libinput_event_tablet_tool_get_seat_button_count(tev), 1); libinput_event_destroy(event); @@ -149,8 +155,7 @@ START_TEST(button_seat_count) tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_BUTTON); ck_assert_int_eq(libinput_event_tablet_tool_get_button_state(tev), LIBINPUT_BUTTON_STATE_RELEASED); - ck_assert_int_eq(libinput_event_tablet_tool_get_button(tev), - BTN_STYLUS); + ck_assert_int_eq(libinput_event_tablet_tool_get_button(tev), button); ck_assert_int_eq(libinput_event_tablet_tool_get_seat_button_count(tev), 0); libinput_event_destroy(event); litest_assert_empty_queue(li); @@ -818,12 +823,12 @@ START_TEST(tip_state_button) { ABS_PRESSURE, 0 }, { -1, -1 } }; + unsigned int button = pick_stylus_or_btn0(dev); litest_tablet_proximity_in(dev, 10, 10, axes); litest_drain_events(li); - litest_event(dev, EV_KEY, BTN_STYLUS, 1); - litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_button_click(dev, button, true); libinput_dispatch(li); event = libinput_get_event(li); @@ -841,8 +846,7 @@ START_TEST(tip_state_button) litest_pop_event_frame(dev); litest_drain_events(li); - litest_event(dev, EV_KEY, BTN_STYLUS, 0); - litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_button_click(dev, button, false); libinput_dispatch(li); event = libinput_get_event(li); @@ -860,8 +864,7 @@ START_TEST(tip_state_button) 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_button_click(dev, button, true); libinput_dispatch(li); event = libinput_get_event(li); @@ -871,8 +874,7 @@ START_TEST(tip_state_button) LIBINPUT_TABLET_TOOL_TIP_UP); libinput_event_destroy(event); - litest_event(dev, EV_KEY, BTN_STYLUS, 0); - litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_button_click(dev, button, false); libinput_dispatch(li); event = libinput_get_event(li); @@ -929,6 +931,7 @@ START_TEST(proximity_in_out) struct libinput *li = dev->libinput; struct libinput_event_tablet_tool *tablet_event; struct libinput_event *event; + enum libinput_tablet_tool_type type; bool have_tool_update = false, have_proximity_out = false; @@ -940,6 +943,15 @@ START_TEST(proximity_in_out) litest_drain_events(li); + switch (dev->which) { + case LITEST_DELL_CANVAS_TOTEM: + type = LIBINPUT_TABLET_TOOL_TYPE_TOTEM; + break; + default: + type = LIBINPUT_TABLET_TOOL_TYPE_PEN; + break; + } + litest_tablet_proximity_in(dev, 10, 10, axes); libinput_dispatch(li); @@ -952,8 +964,7 @@ START_TEST(proximity_in_out) have_tool_update = true; tablet_event = libinput_event_get_tablet_tool_event(event); tool = libinput_event_tablet_tool_get_tool(tablet_event); - ck_assert_int_eq(libinput_tablet_tool_get_type(tool), - LIBINPUT_TABLET_TOOL_TYPE_PEN); + ck_assert_int_eq(libinput_tablet_tool_get_type(tool), type); } libinput_event_destroy(event); } @@ -994,19 +1005,21 @@ START_TEST(proximity_in_button_down) { ABS_PRESSURE, 0 }, { -1, -1 } }; + unsigned int button = pick_stylus_or_btn0(dev); litest_drain_events(li); litest_push_event_frame(dev); litest_tablet_proximity_in(dev, 10, 10, axes); - litest_event(dev, EV_KEY, BTN_STYLUS, 1); + litest_event(dev, EV_KEY, button, 1); litest_pop_event_frame(dev); libinput_dispatch(li); litest_assert_tablet_proximity_event(li, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN); + litest_drain_events_of_type(li, LIBINPUT_EVENT_TABLET_TOOL_TIP, -1); litest_assert_tablet_button_event(li, - BTN_STYLUS, + button, LIBINPUT_BUTTON_STATE_PRESSED); litest_assert_empty_queue(li); } @@ -1021,16 +1034,16 @@ START_TEST(proximity_out_button_up) { ABS_PRESSURE, 0 }, { -1, -1 } }; + unsigned int button = pick_stylus_or_btn0(dev); litest_tablet_proximity_in(dev, 10, 10, axes); - litest_event(dev, EV_KEY, BTN_STYLUS, 1); - litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_button_click(dev, button, true); litest_drain_events(li); litest_push_event_frame(dev); litest_tablet_proximity_out(dev); - litest_event(dev, EV_KEY, BTN_STYLUS, 0); + litest_event(dev, EV_KEY, button, 0); litest_pop_event_frame(dev); libinput_dispatch(li); @@ -1038,8 +1051,9 @@ START_TEST(proximity_out_button_up) libinput_dispatch(li); litest_assert_tablet_button_event(li, - BTN_STYLUS, + button, LIBINPUT_BUTTON_STATE_RELEASED); + litest_drain_events_of_type(li, LIBINPUT_EVENT_TABLET_TOOL_TIP, -1); litest_assert_tablet_proximity_event(li, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); litest_assert_empty_queue(li); @@ -1182,6 +1196,8 @@ START_TEST(proximity_has_axes) litest_assert_double_ne(y, 0); } + litest_drain_events_of_type(li, LIBINPUT_EVENT_TABLET_TOOL_TIP, -1); + litest_assert_empty_queue(li); libinput_event_destroy(event); @@ -1219,6 +1235,8 @@ START_TEST(proximity_has_axes) litest_timeout_tablet_proxout(); libinput_dispatch(li); + litest_drain_events_of_type(li, LIBINPUT_EVENT_TABLET_TOOL_TIP, -1); + event = libinput_get_event(li); tablet_event = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); @@ -1917,12 +1935,12 @@ START_TEST(motion_event_state) struct libinput_event_tablet_tool *tablet_event; int test_x, test_y; double last_x, last_y; - struct axis_replacement axes[] = { { ABS_DISTANCE, 10 }, { ABS_PRESSURE, 0 }, { -1, -1 } }; + unsigned int button = pick_stylus_or_btn0(dev); litest_drain_events(li); litest_tablet_proximity_in(dev, 5, 100, axes); @@ -1941,8 +1959,7 @@ START_TEST(motion_event_state) last_y = libinput_event_tablet_tool_get_y(tablet_event); /* mark with a button event, then go back to bottom/left */ - litest_event(dev, EV_KEY, BTN_STYLUS, 1); - litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_button_click(dev, button, true); for (test_x = 100, test_y = 0; test_x > 0; test_x -= 10, test_y += 10) litest_tablet_motion(dev, test_x, test_y, axes); @@ -2692,6 +2709,8 @@ START_TEST(tool_in_prox_before_start) serial = libinput_tablet_tool_get_serial(tool); libinput_event_destroy(event); + litest_drain_events_of_type(li, LIBINPUT_EVENT_TABLET_TOOL_TIP, -1); + litest_tablet_motion(dev, 30, 40, axes); libinput_dispatch(li); event = libinput_get_event(li); @@ -5606,9 +5625,13 @@ TEST_COLLECTION(tablet) litest_add("tablet:tool", tool_capability, LITEST_TABLET, LITEST_ANY); litest_add_no_device("tablet:tool", tool_capabilities); litest_add("tablet:tool", tool_type, LITEST_TABLET, LITEST_ANY); - litest_add("tablet:tool", tool_in_prox_before_start, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:tool", tool_in_prox_before_start, LITEST_TABLET, LITEST_TOTEM); litest_add("tablet:tool", tool_direct_switch_warning, LITEST_TABLET, LITEST_ANY); litest_add("tablet:tool", tool_direct_switch_skip_tool_update, LITEST_TABLET, LITEST_ANY); + + /* Tablets hold back the proximity until the first event from the + * kernel, the totem sends it immediately */ + litest_add("tablet:tool", tool_in_prox_before_start, LITEST_TABLET, LITEST_TOTEM); litest_add("tablet:tool_serial", tool_unique, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY); litest_add("tablet:tool_serial", tool_serial, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY); litest_add("tablet:tool_serial", tool_id, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY); @@ -5656,7 +5679,7 @@ TEST_COLLECTION(tablet) litest_add_for_device("tablet:left_handed", left_handed_mouse_rotation, LITEST_WACOM_INTUOS); litest_add_for_device("tablet:left_handed", left_handed_artpen_rotation, LITEST_WACOM_INTUOS); litest_add_for_device("tablet:left_handed", no_left_handed, LITEST_WACOM_CINTIQ); - litest_add("tablet:pad", pad_buttons_ignored, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:pad", pad_buttons_ignored, LITEST_TABLET, LITEST_TOTEM); litest_add("tablet:mouse", mouse_tool, LITEST_TABLET | LITEST_TOOL_MOUSE, LITEST_ANY); litest_add("tablet:mouse", mouse_buttons, LITEST_TABLET | LITEST_TOOL_MOUSE, LITEST_ANY); litest_add("tablet:mouse", mouse_rotation, LITEST_TABLET | LITEST_TOOL_MOUSE, LITEST_ANY); @@ -5669,9 +5692,10 @@ TEST_COLLECTION(tablet) litest_add("tablet:time", tablet_time_usec, LITEST_TABLET, LITEST_ANY); litest_add("tablet:pressure", tablet_pressure_distance_exclusive, LITEST_TABLET | LITEST_DISTANCE, LITEST_ANY); - litest_add("tablet:calibration", tablet_calibration_has_matrix, LITEST_TABLET, LITEST_ANY); - litest_add("tablet:calibration", tablet_calibration_set_matrix, LITEST_TABLET, LITEST_ANY); - litest_add("tablet:calibration", tablet_calibration_set_matrix_delta, LITEST_TABLET, LITEST_ANY); + /* The totem doesn't need calibration */ + litest_add("tablet:calibration", tablet_calibration_has_matrix, LITEST_TABLET, LITEST_TOTEM); + litest_add("tablet:calibration", tablet_calibration_set_matrix, LITEST_TABLET, LITEST_TOTEM); + litest_add("tablet:calibration", tablet_calibration_set_matrix_delta, LITEST_TABLET, LITEST_TOTEM); litest_add("tablet:pressure", tablet_pressure_min_max, LITEST_TABLET, LITEST_ANY); litest_add_for_device("tablet:pressure", tablet_pressure_range, LITEST_WACOM_INTUOS); diff --git a/test/test-totem.c b/test/test-totem.c new file mode 100644 index 00000000..bafa8d23 --- /dev/null +++ b/test/test-totem.c @@ -0,0 +1,605 @@ +/* + * Copyright © 2018 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <config.h> + +#include <check.h> +#include <errno.h> +#include <fcntl.h> +#include <libinput.h> +#include <unistd.h> +#include <stdbool.h> +#include <stdarg.h> + +#include "libinput-util.h" +#include "evdev-tablet.h" +#include "litest.h" + +START_TEST(totem_type) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *t; + struct libinput_tablet_tool *tool; + + litest_drain_events(li); + + litest_tablet_proximity_in(dev, 50, 50, NULL); + libinput_dispatch(li); + + event = libinput_get_event(li); + t = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + tool = libinput_event_tablet_tool_get_tool(t); + + ck_assert_int_eq(libinput_tablet_tool_get_type(tool), + LIBINPUT_TABLET_TOOL_TYPE_TOTEM); + libinput_event_destroy(event); +} +END_TEST + +START_TEST(totem_axes) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *t; + struct libinput_tablet_tool *tool; + + litest_drain_events(li); + + litest_tablet_proximity_in(dev, 50, 50, NULL); + libinput_dispatch(li); + + event = libinput_get_event(li); + t = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + tool = libinput_event_tablet_tool_get_tool(t); + + ck_assert(libinput_tablet_tool_has_rotation(tool)); + ck_assert(libinput_tablet_tool_has_size(tool)); + ck_assert(libinput_tablet_tool_has_button(tool, BTN_0)); + + libinput_event_destroy(event); +} +END_TEST + +START_TEST(totem_proximity_in_out) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *t; + + litest_drain_events(li); + + litest_tablet_proximity_in(dev, 50, 50, NULL); + libinput_dispatch(li); + + event = libinput_get_event(li); + t = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + ck_assert_int_eq(libinput_event_tablet_tool_get_proximity_state(t), + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN); + libinput_event_destroy(event); + + event = libinput_get_event(li); + t = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_TIP); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(t), + LIBINPUT_TABLET_TOOL_TIP_DOWN); + libinput_event_destroy(event); + + litest_assert_empty_queue(li); + litest_tablet_proximity_out(dev); + libinput_dispatch(li); + + event = libinput_get_event(li); + t = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_TIP); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(t), + LIBINPUT_TABLET_TOOL_TIP_UP); + libinput_event_destroy(event); + + event = libinput_get_event(li); + t = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + ck_assert_int_eq(libinput_event_tablet_tool_get_proximity_state(t), + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); + libinput_event_destroy(event); +} +END_TEST + +START_TEST(totem_proximity_in_on_init) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li; + struct libinput_event *event; + struct libinput_event_tablet_tool *t; + const char *devnode; + double x, y; + double w, h; + const struct input_absinfo *abs; + + abs = libevdev_get_abs_info(dev->evdev, ABS_MT_POSITION_X); + w = (abs->maximum - abs->minimum + 1)/abs->resolution; + abs = libevdev_get_abs_info(dev->evdev, ABS_MT_POSITION_Y); + h = (abs->maximum - abs->minimum + 1)/abs->resolution; + + litest_tablet_proximity_in(dev, 50, 50, NULL); + + /* for simplicity, we create a new litest context */ + devnode = libevdev_uinput_get_devnode(dev->uinput); + li = litest_create_context(); + libinput_path_add_device(li, devnode); + libinput_dispatch(li); + + litest_wait_for_event_of_type(li, + LIBINPUT_EVENT_DEVICE_ADDED, + -1); + event = libinput_get_event(li); + libinput_event_destroy(event); + + event = libinput_get_event(li); + t = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + ck_assert_int_eq(libinput_event_tablet_tool_get_proximity_state(t), + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN); + x = libinput_event_tablet_tool_get_x(t); + y = libinput_event_tablet_tool_get_y(t); + + ck_assert_double_gt(x, w/2 - 1); + ck_assert_double_lt(x, w/2 + 1); + ck_assert_double_gt(y, h/2 - 1); + ck_assert_double_lt(y, h/2 + 1); + + libinput_event_destroy(event); + + event = libinput_get_event(li); + t = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_TIP); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(t), + LIBINPUT_TABLET_TOOL_TIP_DOWN); + x = libinput_event_tablet_tool_get_x(t); + y = libinput_event_tablet_tool_get_y(t); + + ck_assert_double_gt(x, w/2 - 1); + ck_assert_double_lt(x, w/2 + 1); + ck_assert_double_gt(y, h/2 - 1); + ck_assert_double_lt(y, h/2 + 1); + + libinput_event_destroy(event); + + litest_assert_empty_queue(li); + + libinput_unref(li); +} +END_TEST + +START_TEST(totem_proximity_out_on_suspend) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li; + struct libinput_event *event; + struct libinput_event_tablet_tool *t; + const char *devnode; + + /* for simplicity, we create a new litest context */ + devnode = libevdev_uinput_get_devnode(dev->uinput); + li = litest_create_context(); + libinput_path_add_device(li, devnode); + + litest_tablet_proximity_in(dev, 50, 50, NULL); + litest_drain_events(li); + + libinput_suspend(li); + + libinput_dispatch(li); + event = libinput_get_event(li); + t = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_TIP); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(t), + LIBINPUT_TABLET_TOOL_TIP_UP); + libinput_event_destroy(event); + + event = libinput_get_event(li); + t = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + ck_assert_int_eq(libinput_event_tablet_tool_get_proximity_state(t), + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); + libinput_event_destroy(event); + + litest_assert_only_typed_events(li, LIBINPUT_EVENT_DEVICE_REMOVED); + libinput_unref(li); +} +END_TEST + +START_TEST(totem_motion) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + double x = 50, y = 50; + double current_x, current_y, old_x, old_y; + + litest_tablet_proximity_in(dev, x, y, NULL); + litest_drain_events(li); + + for (int i = 0; i < 30; i++, x++, y--) { + struct libinput_event_tablet_tool *t; + + litest_tablet_motion(dev, x + 1, y + 1, NULL); + libinput_dispatch(li); + + event = libinput_get_event(li); + t = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS); + + ck_assert(libinput_event_tablet_tool_x_has_changed(t)); + ck_assert(libinput_event_tablet_tool_y_has_changed(t)); + + current_x = libinput_event_tablet_tool_get_x(t); + current_y = libinput_event_tablet_tool_get_y(t); + if (i != 0) { + ck_assert_double_gt(current_x, old_x); + ck_assert_double_lt(current_y, old_y); + } + old_x = current_x; + old_y = current_y; + + libinput_event_destroy(event); + } +} +END_TEST + +START_TEST(totem_rotation) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + double r, old_r; + struct axis_replacement axes[] = { + { ABS_MT_ORIENTATION, 50 }, /* mid-point is 0 */ + { -1, -1 } + }; + + litest_tablet_proximity_in(dev, 50, 50, axes); + litest_drain_events(li); + + old_r = 360; + + for (int i = 1; i < 30; i++) { + struct libinput_event_tablet_tool *t; + + + litest_axis_set_value(axes, ABS_MT_ORIENTATION, 50 + i); + litest_tablet_motion(dev, 50, 50, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + t = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS); + + ck_assert(!libinput_event_tablet_tool_x_has_changed(t)); + ck_assert(!libinput_event_tablet_tool_y_has_changed(t)); + ck_assert(libinput_event_tablet_tool_rotation_has_changed(t)); + + r = libinput_event_tablet_tool_get_rotation(t); + ck_assert_double_lt(r, old_r); + old_r = r; + + libinput_event_destroy(event); + } + + old_r = 0; + + for (int i = 1; i < 30; i++) { + struct libinput_event_tablet_tool *t; + + + litest_axis_set_value(axes, ABS_MT_ORIENTATION, 50 - i); + litest_tablet_motion(dev, 50, 50, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + t = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS); + + ck_assert(!libinput_event_tablet_tool_x_has_changed(t)); + ck_assert(!libinput_event_tablet_tool_y_has_changed(t)); + ck_assert(libinput_event_tablet_tool_rotation_has_changed(t)); + + r = libinput_event_tablet_tool_get_rotation(t); + ck_assert_double_gt(r, old_r); + old_r = r; + + libinput_event_destroy(event); + } +} +END_TEST + +START_TEST(totem_size) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *t; + double smin, smaj; + + litest_drain_events(li); + + litest_tablet_proximity_in(dev, 50, 50, NULL); + libinput_dispatch(li); + + event = libinput_get_event(li); + t = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + ck_assert(libinput_event_tablet_tool_size_major_has_changed(t)); + ck_assert(libinput_event_tablet_tool_size_minor_has_changed(t)); + smaj = libinput_event_tablet_tool_get_size_major(t); + smin = libinput_event_tablet_tool_get_size_minor(t); + libinput_event_destroy(event); + + ck_assert_double_eq(smaj, 71.8); + ck_assert_double_eq(smin, 71.8); + + litest_drain_events(li); +} +END_TEST + +START_TEST(totem_button) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *t; + + litest_tablet_proximity_in(dev, 30, 40, NULL); + litest_drain_events(li); + + litest_button_click(dev, BTN_0, true); + libinput_dispatch(li); + event = libinput_get_event(li); + t = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + ck_assert_int_eq(libinput_event_tablet_tool_get_button(t), BTN_0); + ck_assert_int_eq(libinput_event_tablet_tool_get_button_state(t), + LIBINPUT_BUTTON_STATE_PRESSED); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(t), + LIBINPUT_TABLET_TOOL_TIP_DOWN); + libinput_event_destroy(event); + + litest_button_click(dev, BTN_0, false); + libinput_dispatch(li); + + event = libinput_get_event(li); + t = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + ck_assert_int_eq(libinput_event_tablet_tool_get_button(t), BTN_0); + ck_assert_int_eq(libinput_event_tablet_tool_get_button_state(t), + LIBINPUT_BUTTON_STATE_RELEASED); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(t), + LIBINPUT_TABLET_TOOL_TIP_DOWN); + libinput_event_destroy(event); +} +END_TEST + +START_TEST(totem_button_down_on_init) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li; + struct libinput_event *event; + struct libinput_event_tablet_tool *t; + const char *devnode; + + litest_tablet_proximity_in(dev, 50, 50, NULL); + litest_button_click(dev, BTN_0, true); + + /* for simplicity, we create a new litest context */ + devnode = libevdev_uinput_get_devnode(dev->uinput); + li = litest_create_context(); + libinput_path_add_device(li, devnode); + libinput_dispatch(li); + + litest_wait_for_event_of_type(li, + LIBINPUT_EVENT_DEVICE_ADDED, + -1); + event = libinput_get_event(li); + libinput_event_destroy(event); + + event = libinput_get_event(li); + t = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + ck_assert_int_eq(libinput_event_tablet_tool_get_proximity_state(t), + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN); + + libinput_event_destroy(event); + + event = libinput_get_event(li); + t = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_TIP); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(t), + LIBINPUT_TABLET_TOOL_TIP_DOWN); + + libinput_event_destroy(event); + + /* The button is down on init but we don't expect an event */ + litest_assert_empty_queue(li); + + litest_button_click(dev, BTN_0, false); + libinput_dispatch(li); + litest_assert_empty_queue(li); + + /* but buttons after this should be sent */ + litest_button_click(dev, BTN_0, true); + libinput_dispatch(li); + litest_assert_tablet_button_event(li, BTN_0, LIBINPUT_BUTTON_STATE_PRESSED); + litest_button_click(dev, BTN_0, false); + libinput_dispatch(li); + litest_assert_tablet_button_event(li, BTN_0, LIBINPUT_BUTTON_STATE_RELEASED); + + libinput_unref(li); +} +END_TEST + +START_TEST(totem_button_up_on_delete) +{ + struct libinput *li = litest_create_context(); + struct litest_device *dev = litest_add_device(li, LITEST_DELL_CANVAS_TOTEM); + struct libevdev *evdev = libevdev_new(); + + litest_tablet_proximity_in(dev, 10, 10, NULL); + litest_drain_events(li); + + litest_button_click(dev, BTN_0, true); + litest_drain_events(li); + + litest_delete_device(dev); + libinput_dispatch(li); + + litest_assert_tablet_button_event(li, + BTN_0, + LIBINPUT_BUTTON_STATE_RELEASED); + + litest_assert_tablet_tip_event(li, LIBINPUT_TABLET_TOOL_TIP_UP); + litest_assert_tablet_proximity_event(li, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); + libevdev_free(evdev); + libinput_unref(li); +} +END_TEST + +START_TEST(totem_arbitration_below) +{ + struct litest_device *totem = litest_current_device(); + struct litest_device *touch; + struct libinput *li = totem->libinput; + + touch = litest_add_device(li, LITEST_DELL_CANVAS_TOTEM_TOUCH); + litest_drain_events(li); + + /* touches below the totem, cancelled once the totem is down */ + litest_touch_down(touch, 0, 50, 50); + libinput_dispatch(li); + litest_assert_touch_down_frame(li); + litest_touch_move_to(touch, 0, 50, 50, 50, 70, 10); + libinput_dispatch(li); + while (libinput_next_event_type(li)) { + litest_assert_touch_motion_frame(li); + } + + litest_tablet_proximity_in(totem, 50, 70, NULL); + libinput_dispatch(li); + + litest_assert_tablet_proximity_event(li, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN); + litest_assert_tablet_tip_event(li, LIBINPUT_TABLET_TOOL_TIP_DOWN); + litest_assert_touch_cancel(li); + + litest_touch_move_to(touch, 0, 50, 70, 20, 50, 10); + litest_assert_empty_queue(li); + + litest_tablet_motion(totem, 20, 50, NULL); + litest_assert_only_typed_events(li, LIBINPUT_EVENT_TABLET_TOOL_AXIS); + + litest_touch_up(touch, 0); + litest_assert_empty_queue(li); + + litest_delete_device(touch); +} +END_TEST + +START_TEST(totem_arbitration_during) +{ + struct litest_device *totem = litest_current_device(); + struct litest_device *touch; + struct libinput *li = totem->libinput; + + touch = litest_add_device(li, LITEST_DELL_CANVAS_TOTEM_TOUCH); + litest_drain_events(li); + + litest_tablet_proximity_in(totem, 50, 50, NULL); + libinput_dispatch(li); + + litest_drain_events(li); + + for (int i = 0; i < 3; i++) { + litest_touch_down(touch, 0, 51, 51); + litest_touch_move_to(touch, 0, 51, 50, 90, 80, 10); + litest_touch_up(touch, 0); + + litest_assert_empty_queue(li); + } + + litest_delete_device(touch); +} +END_TEST + +START_TEST(totem_arbitration_outside_rect) +{ + struct litest_device *totem = litest_current_device(); + struct litest_device *touch; + struct libinput *li = totem->libinput; + + touch = litest_add_device(li, LITEST_DELL_CANVAS_TOTEM_TOUCH); + litest_drain_events(li); + + litest_tablet_proximity_in(totem, 50, 50, NULL); + libinput_dispatch(li); + + litest_drain_events(li); + + for (int i = 0; i < 3; i++) { + litest_touch_down(touch, 0, 81, 51); + litest_touch_move_to(touch, 0, 81, 50, 90, 80, 10); + litest_touch_up(touch, 0); + libinput_dispatch(li); + + litest_assert_touch_sequence(li); + } + + /* moving onto the totem is fine */ + litest_touch_down(touch, 0, 81, 51); + litest_touch_move_to(touch, 0, 81, 50, 50, 50, 10); + litest_touch_up(touch, 0); + libinput_dispatch(li); + + litest_assert_touch_sequence(li); + + litest_delete_device(touch); +} +END_TEST + +TEST_COLLECTION(totem) +{ + litest_add("totem:tool", totem_type, LITEST_TOTEM, LITEST_ANY); + litest_add("totem:tool", totem_axes, LITEST_TOTEM, LITEST_ANY); + litest_add("totem:proximity", totem_proximity_in_out, LITEST_TOTEM, LITEST_ANY); + litest_add("totem:proximity", totem_proximity_in_on_init, LITEST_TOTEM, LITEST_ANY); + litest_add("totem:proximity", totem_proximity_out_on_suspend, LITEST_TOTEM, LITEST_ANY); + + litest_add("totem:axes", totem_motion, LITEST_TOTEM, LITEST_ANY); + litest_add("totem:axes", totem_rotation, LITEST_TOTEM, LITEST_ANY); + litest_add("totem:axes", totem_size, LITEST_TOTEM, LITEST_ANY); + litest_add("totem:button", totem_button, LITEST_TOTEM, LITEST_ANY); + litest_add("totem:button", totem_button_down_on_init, LITEST_TOTEM, LITEST_ANY); + litest_add_no_device("totem:button", totem_button_up_on_delete); + + litest_add("totem:arbitration", totem_arbitration_below, LITEST_TOTEM, LITEST_ANY); + litest_add("totem:arbitration", totem_arbitration_during, LITEST_TOTEM, LITEST_ANY); + litest_add("totem:arbitration", totem_arbitration_outside_rect, LITEST_TOTEM, LITEST_ANY); +} |