summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPeter Hutterer <peter.hutterer@who-t.net>2018-09-14 14:03:09 +1000
committerPeter Hutterer <peter.hutterer@who-t.net>2019-06-07 01:03:21 +0000
commitbf4277623f7c24286b9f1e4b3ace51fbb5bb6a98 (patch)
tree2165eb35a34a6d15b03015c4d6e9420c661cb3f5 /src
parent17d792445edd689edc5b83608dbca51a1ab9e6fa (diff)
Add a new dispatch interface for the Dell Canvas Totem
This device looks similar to a MT device on the kernel side, but it's not a MT device and it's not quite a tablet either. It uses slots to track up to 4 totems off the same device and the only hint that it's not a MT device is that it sends ABS_MT_TOOL_TYPE / MT_TOOL_DIAL. udev thinks it's a touchscreen and a tablet but we currently init those devices as touchscreen (because all wacom tablet touch devices are udev tablets+tochscreens). So we need a quirk to hook onto this device. And we use a completely separate dispatch implementation, because adding the behavior to the tablet interface requires so many exceptions that it's easier to just add a separate dispatch interface. Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Diffstat (limited to 'src')
-rw-r--r--src/evdev-totem.c829
-rw-r--r--src/evdev.c8
-rw-r--r--src/evdev.h5
-rw-r--r--src/quirks.c1
-rw-r--r--src/quirks.h1
5 files changed, 843 insertions, 1 deletions
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 */