summaryrefslogtreecommitdiff
path: root/src/evdev-touchpad.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/evdev-touchpad.c')
-rw-r--r--src/evdev-touchpad.c800
1 files changed, 800 insertions, 0 deletions
diff --git a/src/evdev-touchpad.c b/src/evdev-touchpad.c
new file mode 100644
index 00000000..69f913ac
--- /dev/null
+++ b/src/evdev-touchpad.c
@@ -0,0 +1,800 @@
+/*
+ * Copyright © 2012 Jonas Ådahl
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission. The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <linux/input.h>
+
+#include "filter.h"
+#include "evdev.h"
+#include "../shared/config-parser.h"
+
+/* Default values */
+#define DEFAULT_CONSTANT_ACCEL_NUMERATOR 50
+#define DEFAULT_MIN_ACCEL_FACTOR 0.16
+#define DEFAULT_MAX_ACCEL_FACTOR 1.0
+#define DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR 700.0
+
+#define DEFAULT_TOUCHPAD_SINGLE_TAP_BUTTON BTN_LEFT
+#define DEFAULT_TOUCHPAD_SINGLE_TAP_TIMEOUT 100
+
+enum touchpad_model {
+ TOUCHPAD_MODEL_UNKNOWN = 0,
+ TOUCHPAD_MODEL_SYNAPTICS,
+ TOUCHPAD_MODEL_ALPS,
+ TOUCHPAD_MODEL_APPLETOUCH,
+ TOUCHPAD_MODEL_ELANTECH
+};
+
+enum touchpad_event {
+ TOUCHPAD_EVENT_NONE = 0,
+ TOUCHPAD_EVENT_ABSOLUTE_ANY = (1 << 0),
+ TOUCHPAD_EVENT_ABSOLUTE_X = (1 << 1),
+ TOUCHPAD_EVENT_ABSOLUTE_Y = (1 << 2),
+ TOUCHPAD_EVENT_REPORT = (1 << 3)
+};
+
+struct touchpad_model_spec {
+ short vendor;
+ short product;
+ enum touchpad_model model;
+};
+
+static struct touchpad_model_spec touchpad_spec_table[] = {
+ {0x0002, 0x0007, TOUCHPAD_MODEL_SYNAPTICS},
+ {0x0002, 0x0008, TOUCHPAD_MODEL_ALPS},
+ {0x05ac, 0x0000, TOUCHPAD_MODEL_APPLETOUCH},
+ {0x0002, 0x000e, TOUCHPAD_MODEL_ELANTECH},
+ {0x0000, 0x0000, TOUCHPAD_MODEL_UNKNOWN}
+};
+
+enum touchpad_state {
+ TOUCHPAD_STATE_NONE = 0,
+ TOUCHPAD_STATE_TOUCH = (1 << 0),
+ TOUCHPAD_STATE_MOVE = (1 << 1)
+};
+
+#define TOUCHPAD_HISTORY_LENGTH 4
+
+struct touchpad_motion {
+ int32_t x;
+ int32_t y;
+};
+
+enum touchpad_fingers_state {
+ TOUCHPAD_FINGERS_ONE = (1 << 0),
+ TOUCHPAD_FINGERS_TWO = (1 << 1),
+ TOUCHPAD_FINGERS_THREE = (1 << 2)
+};
+
+enum fsm_event {
+ FSM_EVENT_TOUCH,
+ FSM_EVENT_RELEASE,
+ FSM_EVENT_MOTION,
+ FSM_EVENT_TIMEOUT
+};
+
+enum fsm_state {
+ FSM_IDLE,
+ FSM_TOUCH,
+ FSM_TAP,
+ FSM_TAP_2,
+ FSM_DRAG
+};
+
+struct touchpad_dispatch {
+ struct evdev_dispatch base;
+ struct evdev_device *device;
+
+ enum touchpad_model model;
+ unsigned int state;
+ int finger_state;
+ int last_finger_state;
+
+ double constant_accel_factor;
+ double min_accel_factor;
+ double max_accel_factor;
+
+ unsigned int event_mask;
+ unsigned int event_mask_filter;
+
+ int reset;
+
+ struct {
+ bool enable;
+
+ struct wl_array events;
+ enum fsm_state state;
+ struct wl_event_source *timer_source;
+ } fsm;
+
+ struct {
+ int32_t x;
+ int32_t y;
+ } hw_abs;
+
+ int has_pressure;
+ struct {
+ int32_t touch_low;
+ int32_t touch_high;
+ } pressure;
+
+ struct {
+ int32_t margin_x;
+ int32_t margin_y;
+ int32_t center_x;
+ int32_t center_y;
+ } hysteresis;
+
+ struct touchpad_motion motion_history[TOUCHPAD_HISTORY_LENGTH];
+ int motion_index;
+ unsigned int motion_count;
+
+ struct weston_motion_filter *filter;
+};
+
+static enum touchpad_model
+get_touchpad_model(struct evdev_device *device)
+{
+ struct input_id id;
+ unsigned int i;
+
+ if (ioctl(device->fd, EVIOCGID, &id) < 0)
+ return TOUCHPAD_MODEL_UNKNOWN;
+
+ for (i = 0; i < ARRAY_LENGTH(touchpad_spec_table); i++)
+ if (touchpad_spec_table[i].vendor == id.vendor &&
+ (!touchpad_spec_table[i].product ||
+ touchpad_spec_table[i].product == id.product))
+ return touchpad_spec_table[i].model;
+
+ return TOUCHPAD_MODEL_UNKNOWN;
+}
+
+static void
+configure_touchpad_pressure(struct touchpad_dispatch *touchpad,
+ int32_t pressure_min, int32_t pressure_max)
+{
+ int32_t range = pressure_max - pressure_min + 1;
+
+ touchpad->has_pressure = 1;
+
+ /* Magic numbers from xf86-input-synaptics */
+ switch (touchpad->model) {
+ case TOUCHPAD_MODEL_ELANTECH:
+ touchpad->pressure.touch_low = pressure_min + 1;
+ touchpad->pressure.touch_high = pressure_min + 1;
+ break;
+ default:
+ touchpad->pressure.touch_low =
+ pressure_min + range * (25.0/256.0);
+ touchpad->pressure.touch_high =
+ pressure_min + range * (30.0/256.0);
+ }
+}
+
+static double
+touchpad_profile(struct weston_motion_filter *filter,
+ void *data,
+ double velocity,
+ uint32_t time)
+{
+ struct touchpad_dispatch *touchpad =
+ (struct touchpad_dispatch *) data;
+
+ double accel_factor;
+
+ accel_factor = velocity * touchpad->constant_accel_factor;
+
+ if (accel_factor > touchpad->max_accel_factor)
+ accel_factor = touchpad->max_accel_factor;
+ else if (accel_factor < touchpad->min_accel_factor)
+ accel_factor = touchpad->min_accel_factor;
+
+ return accel_factor;
+}
+
+static inline struct touchpad_motion *
+motion_history_offset(struct touchpad_dispatch *touchpad, int offset)
+{
+ int offset_index =
+ (touchpad->motion_index - offset + TOUCHPAD_HISTORY_LENGTH) %
+ TOUCHPAD_HISTORY_LENGTH;
+
+ return &touchpad->motion_history[offset_index];
+}
+
+static double
+estimate_delta(int x0, int x1, int x2, int x3)
+{
+ return (x0 + x1 - x2 - x3) / 4;
+}
+
+static int
+hysteresis(int in, int center, int margin)
+{
+ int diff = in - center;
+ if (abs(diff) <= margin)
+ return center;
+
+ if (diff > margin)
+ return center + diff - margin;
+ else if (diff < -margin)
+ return center + diff + margin;
+ return center + diff;
+}
+
+static void
+touchpad_get_delta(struct touchpad_dispatch *touchpad, double *dx, double *dy)
+{
+ *dx = estimate_delta(motion_history_offset(touchpad, 0)->x,
+ motion_history_offset(touchpad, 1)->x,
+ motion_history_offset(touchpad, 2)->x,
+ motion_history_offset(touchpad, 3)->x);
+ *dy = estimate_delta(motion_history_offset(touchpad, 0)->y,
+ motion_history_offset(touchpad, 1)->y,
+ motion_history_offset(touchpad, 2)->y,
+ motion_history_offset(touchpad, 3)->y);
+}
+
+static void
+filter_motion(struct touchpad_dispatch *touchpad,
+ double *dx, double *dy, uint32_t time)
+{
+ struct weston_motion_params motion;
+
+ motion.dx = *dx;
+ motion.dy = *dy;
+
+ weston_filter_dispatch(touchpad->filter, &motion, touchpad, time);
+
+ *dx = motion.dx;
+ *dy = motion.dy;
+}
+
+static void
+notify_button_pressed(struct touchpad_dispatch *touchpad, uint32_t time)
+{
+ notify_button(touchpad->device->seat, time,
+ DEFAULT_TOUCHPAD_SINGLE_TAP_BUTTON,
+ WL_POINTER_BUTTON_STATE_PRESSED);
+}
+
+static void
+notify_button_released(struct touchpad_dispatch *touchpad, uint32_t time)
+{
+ notify_button(touchpad->device->seat, time,
+ DEFAULT_TOUCHPAD_SINGLE_TAP_BUTTON,
+ WL_POINTER_BUTTON_STATE_RELEASED);
+}
+
+static void
+notify_tap(struct touchpad_dispatch *touchpad, uint32_t time)
+{
+ notify_button_pressed(touchpad, time);
+ notify_button_released(touchpad, time);
+}
+
+static void
+process_fsm_events(struct touchpad_dispatch *touchpad, uint32_t time)
+{
+ uint32_t timeout = UINT32_MAX;
+ enum fsm_event *pevent;
+ enum fsm_event event;
+
+ if (!touchpad->fsm.enable)
+ return;
+
+ if (touchpad->fsm.events.size == 0)
+ return;
+
+ wl_array_for_each(pevent, &touchpad->fsm.events) {
+ event = *pevent;
+ timeout = 0;
+
+ switch (touchpad->fsm.state) {
+ case FSM_IDLE:
+ switch (event) {
+ case FSM_EVENT_TOUCH:
+ touchpad->fsm.state = FSM_TOUCH;
+ break;
+ default:
+ break;
+ }
+ break;
+ case FSM_TOUCH:
+ switch (event) {
+ case FSM_EVENT_RELEASE:
+ timeout = DEFAULT_TOUCHPAD_SINGLE_TAP_TIMEOUT;
+ touchpad->fsm.state = FSM_TAP;
+ break;
+ default:
+ touchpad->fsm.state = FSM_IDLE;
+ break;
+ }
+ break;
+ case FSM_TAP:
+ switch (event) {
+ case FSM_EVENT_TIMEOUT:
+ notify_tap(touchpad, time);
+ touchpad->fsm.state = FSM_IDLE;
+ break;
+ case FSM_EVENT_TOUCH:
+ notify_button_pressed(touchpad, time);
+ touchpad->fsm.state = FSM_TAP_2;
+ break;
+ default:
+ touchpad->fsm.state = FSM_IDLE;
+ break;
+ }
+ break;
+ case FSM_TAP_2:
+ switch (event) {
+ case FSM_EVENT_MOTION:
+ touchpad->fsm.state = FSM_DRAG;
+ break;
+ case FSM_EVENT_RELEASE:
+ notify_button_released(touchpad, time);
+ notify_tap(touchpad, time);
+ touchpad->fsm.state = FSM_IDLE;
+ break;
+ default:
+ touchpad->fsm.state = FSM_IDLE;
+ break;
+ }
+ break;
+ case FSM_DRAG:
+ switch (event) {
+ case FSM_EVENT_RELEASE:
+ notify_button_released(touchpad, time);
+ touchpad->fsm.state = FSM_IDLE;
+ break;
+ default:
+ touchpad->fsm.state = FSM_IDLE;
+ break;
+ }
+ break;
+ default:
+ weston_log("evdev-touchpad: Unknown state %d",
+ touchpad->fsm.state);
+ touchpad->fsm.state = FSM_IDLE;
+ break;
+ }
+ }
+
+ if (timeout != UINT32_MAX)
+ wl_event_source_timer_update(touchpad->fsm.timer_source,
+ timeout);
+
+ wl_array_release(&touchpad->fsm.events);
+ wl_array_init(&touchpad->fsm.events);
+}
+
+static void
+push_fsm_event(struct touchpad_dispatch *touchpad,
+ enum fsm_event event)
+{
+ enum fsm_event *pevent;
+
+ if (!touchpad->fsm.enable)
+ return;
+
+ pevent = wl_array_add(&touchpad->fsm.events, sizeof event);
+ if (pevent)
+ *pevent = event;
+ else
+ touchpad->fsm.state = FSM_IDLE;
+}
+
+static int
+fsm_timout_handler(void *data)
+{
+ struct touchpad_dispatch *touchpad = data;
+
+ if (touchpad->fsm.events.size == 0) {
+ push_fsm_event(touchpad, FSM_EVENT_TIMEOUT);
+ process_fsm_events(touchpad, weston_compositor_get_time());
+ }
+
+ return 1;
+}
+
+static void
+touchpad_update_state(struct touchpad_dispatch *touchpad, uint32_t time)
+{
+ int motion_index;
+ int center_x, center_y;
+ double dx = 0.0, dy = 0.0;
+
+ if (touchpad->reset ||
+ touchpad->last_finger_state != touchpad->finger_state) {
+ touchpad->reset = 0;
+ touchpad->motion_count = 0;
+ touchpad->event_mask = TOUCHPAD_EVENT_NONE;
+ touchpad->event_mask_filter =
+ TOUCHPAD_EVENT_ABSOLUTE_X | TOUCHPAD_EVENT_ABSOLUTE_Y;
+
+ touchpad->last_finger_state = touchpad->finger_state;
+
+ process_fsm_events(touchpad, time);
+
+ return;
+ }
+ touchpad->last_finger_state = touchpad->finger_state;
+
+ if (!(touchpad->event_mask & TOUCHPAD_EVENT_REPORT))
+ return;
+ else
+ touchpad->event_mask &= ~TOUCHPAD_EVENT_REPORT;
+
+ if ((touchpad->event_mask & touchpad->event_mask_filter) !=
+ touchpad->event_mask_filter)
+ return;
+
+ touchpad->event_mask_filter = TOUCHPAD_EVENT_ABSOLUTE_ANY;
+ touchpad->event_mask = 0;
+
+ /* Avoid noice by moving center only when delta reaches a threshold
+ * distance from the old center. */
+ if (touchpad->motion_count > 0) {
+ center_x = hysteresis(touchpad->hw_abs.x,
+ touchpad->hysteresis.center_x,
+ touchpad->hysteresis.margin_x);
+ center_y = hysteresis(touchpad->hw_abs.y,
+ touchpad->hysteresis.center_y,
+ touchpad->hysteresis.margin_y);
+ } else {
+ center_x = touchpad->hw_abs.x;
+ center_y = touchpad->hw_abs.y;
+ }
+ touchpad->hysteresis.center_x = center_x;
+ touchpad->hysteresis.center_y = center_y;
+ touchpad->hw_abs.x = center_x;
+ touchpad->hw_abs.y = center_y;
+
+ /* Update motion history tracker */
+ motion_index = (touchpad->motion_index + 1) % TOUCHPAD_HISTORY_LENGTH;
+ touchpad->motion_index = motion_index;
+ touchpad->motion_history[motion_index].x = touchpad->hw_abs.x;
+ touchpad->motion_history[motion_index].y = touchpad->hw_abs.y;
+ if (touchpad->motion_count < 4)
+ touchpad->motion_count++;
+
+ if (touchpad->motion_count >= 4) {
+ touchpad_get_delta(touchpad, &dx, &dy);
+
+ filter_motion(touchpad, &dx, &dy, time);
+
+ if (touchpad->finger_state == TOUCHPAD_FINGERS_ONE) {
+ notify_motion(touchpad->device->seat, time,
+ wl_fixed_from_double(dx),
+ wl_fixed_from_double(dy));
+ } else if (touchpad->finger_state == TOUCHPAD_FINGERS_TWO) {
+ if (dx != 0.0)
+ notify_axis(touchpad->device->seat,
+ time,
+ WL_POINTER_AXIS_HORIZONTAL_SCROLL,
+ wl_fixed_from_double(dx));
+ if (dy != 0.0)
+ notify_axis(touchpad->device->seat,
+ time,
+ WL_POINTER_AXIS_VERTICAL_SCROLL,
+ wl_fixed_from_double(dy));
+ }
+ }
+
+ if (!(touchpad->state & TOUCHPAD_STATE_MOVE) &&
+ ((int)dx || (int)dy)) {
+ touchpad->state |= TOUCHPAD_STATE_MOVE;
+ push_fsm_event(touchpad, FSM_EVENT_MOTION);
+ }
+
+ process_fsm_events(touchpad, time);
+}
+
+static void
+on_touch(struct touchpad_dispatch *touchpad)
+{
+ touchpad->state |= TOUCHPAD_STATE_TOUCH;
+
+ push_fsm_event(touchpad, FSM_EVENT_TOUCH);
+}
+
+static void
+on_release(struct touchpad_dispatch *touchpad)
+{
+
+ touchpad->reset = 1;
+ touchpad->state &= ~(TOUCHPAD_STATE_MOVE | TOUCHPAD_STATE_TOUCH);
+
+ push_fsm_event(touchpad, FSM_EVENT_RELEASE);
+}
+
+static inline void
+process_absolute(struct touchpad_dispatch *touchpad,
+ struct evdev_device *device,
+ struct input_event *e)
+{
+ switch (e->code) {
+ case ABS_PRESSURE:
+ if (e->value > touchpad->pressure.touch_high &&
+ !(touchpad->state & TOUCHPAD_STATE_TOUCH))
+ on_touch(touchpad);
+ else if (e->value < touchpad->pressure.touch_low &&
+ touchpad->state & TOUCHPAD_STATE_TOUCH)
+ on_release(touchpad);
+
+ break;
+ case ABS_X:
+ if (touchpad->state & TOUCHPAD_STATE_TOUCH) {
+ touchpad->hw_abs.x = e->value;
+ touchpad->event_mask |= TOUCHPAD_EVENT_ABSOLUTE_ANY;
+ touchpad->event_mask |= TOUCHPAD_EVENT_ABSOLUTE_X;
+ }
+ break;
+ case ABS_Y:
+ if (touchpad->state & TOUCHPAD_STATE_TOUCH) {
+ touchpad->hw_abs.y = e->value;
+ touchpad->event_mask |= TOUCHPAD_EVENT_ABSOLUTE_ANY;
+ touchpad->event_mask |= TOUCHPAD_EVENT_ABSOLUTE_Y;
+ }
+ break;
+ }
+}
+
+static inline void
+process_key(struct touchpad_dispatch *touchpad,
+ struct evdev_device *device,
+ struct input_event *e,
+ uint32_t time)
+{
+ uint32_t code;
+
+ switch (e->code) {
+ case BTN_TOUCH:
+ if (!touchpad->has_pressure) {
+ if (e->value && !(touchpad->state & TOUCHPAD_STATE_TOUCH))
+ on_touch(touchpad);
+ else if (!e->value)
+ on_release(touchpad);
+ }
+ break;
+ case BTN_LEFT:
+ case BTN_RIGHT:
+ case BTN_MIDDLE:
+ case BTN_SIDE:
+ case BTN_EXTRA:
+ case BTN_FORWARD:
+ case BTN_BACK:
+ case BTN_TASK:
+ if (!touchpad->fsm.enable && e->code == BTN_LEFT &&
+ touchpad->finger_state == TOUCHPAD_FINGERS_TWO)
+ code = BTN_RIGHT;
+ else
+ code = e->code;
+ notify_button(device->seat, time, code,
+ e->value ? WL_POINTER_BUTTON_STATE_PRESSED :
+ WL_POINTER_BUTTON_STATE_RELEASED);
+ break;
+ case BTN_TOOL_PEN:
+ case BTN_TOOL_RUBBER:
+ case BTN_TOOL_BRUSH:
+ case BTN_TOOL_PENCIL:
+ case BTN_TOOL_AIRBRUSH:
+ case BTN_TOOL_MOUSE:
+ case BTN_TOOL_LENS:
+ touchpad->reset = 1;
+ break;
+ case BTN_TOOL_FINGER:
+ if (e->value)
+ touchpad->finger_state |= TOUCHPAD_FINGERS_ONE;
+ else
+ touchpad->finger_state &= ~TOUCHPAD_FINGERS_ONE;
+ break;
+ case BTN_TOOL_DOUBLETAP:
+ if (e->value)
+ touchpad->finger_state |= TOUCHPAD_FINGERS_TWO;
+ else
+ touchpad->finger_state &= ~TOUCHPAD_FINGERS_TWO;
+ break;
+ case BTN_TOOL_TRIPLETAP:
+ if (e->value)
+ touchpad->finger_state |= TOUCHPAD_FINGERS_THREE;
+ else
+ touchpad->finger_state &= ~TOUCHPAD_FINGERS_THREE;
+ break;
+ }
+}
+
+static void
+touchpad_process(struct evdev_dispatch *dispatch,
+ struct evdev_device *device,
+ struct input_event *e,
+ uint32_t time)
+{
+ struct touchpad_dispatch *touchpad =
+ (struct touchpad_dispatch *) dispatch;
+
+ switch (e->type) {
+ case EV_SYN:
+ if (e->code == SYN_REPORT)
+ touchpad->event_mask |= TOUCHPAD_EVENT_REPORT;
+ break;
+ case EV_ABS:
+ process_absolute(touchpad, device, e);
+ break;
+ case EV_KEY:
+ process_key(touchpad, device, e, time);
+ break;
+ }
+
+ touchpad_update_state(touchpad, time);
+}
+
+static void
+touchpad_destroy(struct evdev_dispatch *dispatch)
+{
+ struct touchpad_dispatch *touchpad =
+ (struct touchpad_dispatch *) dispatch;
+
+ touchpad->filter->interface->destroy(touchpad->filter);
+ wl_event_source_remove(touchpad->fsm.timer_source);
+ free(dispatch);
+}
+
+struct evdev_dispatch_interface touchpad_interface = {
+ touchpad_process,
+ touchpad_destroy
+};
+
+static void
+touchpad_parse_config(struct touchpad_dispatch *touchpad, double diagonal)
+{
+ struct weston_compositor *compositor =
+ touchpad->device->seat->compositor;
+ struct weston_config_section *s;
+ double constant_accel_factor;
+ double min_accel_factor;
+ double max_accel_factor;
+
+ s = weston_config_get_section(compositor->config,
+ "touchpad", NULL, NULL);
+ weston_config_section_get_double(s, "constant_accel_factor",
+ &constant_accel_factor,
+ DEFAULT_CONSTANT_ACCEL_NUMERATOR);
+ weston_config_section_get_double(s, "min_accel_factor",
+ &min_accel_factor,
+ DEFAULT_MIN_ACCEL_FACTOR);
+ weston_config_section_get_double(s, "max_accel_factor",
+ &max_accel_factor,
+ DEFAULT_MAX_ACCEL_FACTOR);
+
+ touchpad->constant_accel_factor =
+ constant_accel_factor / diagonal;
+ touchpad->min_accel_factor = min_accel_factor;
+ touchpad->max_accel_factor = max_accel_factor;
+}
+
+static int
+touchpad_init(struct touchpad_dispatch *touchpad,
+ struct evdev_device *device)
+{
+ struct weston_motion_filter *accel;
+ struct wl_event_loop *loop;
+
+ unsigned long prop_bits[INPUT_PROP_MAX];
+ struct input_absinfo absinfo;
+ unsigned long abs_bits[NBITS(ABS_MAX)];
+
+ bool has_buttonpad;
+
+ double width;
+ double height;
+ double diagonal;
+
+ touchpad->base.interface = &touchpad_interface;
+ touchpad->device = device;
+
+ /* Detect model */
+ touchpad->model = get_touchpad_model(device);
+
+ ioctl(device->fd, EVIOCGPROP(sizeof(prop_bits)), prop_bits);
+ has_buttonpad = TEST_BIT(prop_bits, INPUT_PROP_BUTTONPAD);
+
+ /* Configure pressure */
+ ioctl(device->fd, EVIOCGBIT(EV_ABS, sizeof(abs_bits)), abs_bits);
+ if (TEST_BIT(abs_bits, ABS_PRESSURE)) {
+ ioctl(device->fd, EVIOCGABS(ABS_PRESSURE), &absinfo);
+ configure_touchpad_pressure(touchpad,
+ absinfo.minimum,
+ absinfo.maximum);
+ }
+
+ /* Configure acceleration factor */
+ width = abs(device->abs.max_x - device->abs.min_x);
+ height = abs(device->abs.max_y - device->abs.min_y);
+ diagonal = sqrt(width*width + height*height);
+
+ touchpad_parse_config(touchpad, diagonal);
+
+ touchpad->hysteresis.margin_x =
+ diagonal / DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR;
+ touchpad->hysteresis.margin_y =
+ diagonal / DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR;
+ touchpad->hysteresis.center_x = 0;
+ touchpad->hysteresis.center_y = 0;
+
+ /* Configure acceleration profile */
+ accel = create_pointer_accelator_filter(touchpad_profile);
+ if (accel == NULL)
+ return -1;
+ touchpad->filter = accel;
+
+ /* Setup initial state */
+ touchpad->reset = 1;
+
+ memset(touchpad->motion_history, 0, sizeof touchpad->motion_history);
+ touchpad->motion_index = 0;
+ touchpad->motion_count = 0;
+
+ touchpad->state = TOUCHPAD_STATE_NONE;
+ touchpad->last_finger_state = 0;
+ touchpad->finger_state = 0;
+
+ wl_array_init(&touchpad->fsm.events);
+ touchpad->fsm.state = FSM_IDLE;
+
+ loop = wl_display_get_event_loop(device->seat->compositor->wl_display);
+ touchpad->fsm.timer_source =
+ wl_event_loop_add_timer(loop, fsm_timout_handler, touchpad);
+ if (touchpad->fsm.timer_source == NULL) {
+ accel->interface->destroy(accel);
+ return -1;
+ }
+
+ /* Configure */
+ touchpad->fsm.enable = !has_buttonpad;
+
+ return 0;
+}
+
+struct evdev_dispatch *
+evdev_touchpad_create(struct evdev_device *device)
+{
+ struct touchpad_dispatch *touchpad;
+
+ touchpad = malloc(sizeof *touchpad);
+ if (touchpad == NULL)
+ return NULL;
+
+ if (touchpad_init(touchpad, device) != 0) {
+ free(touchpad);
+ return NULL;
+ }
+
+ return &touchpad->base;
+}