summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Makefile.am3
-rw-r--r--src/evdev-private.h6
-rw-r--r--src/evdev-touchpad.c537
-rw-r--r--src/evdev.c79
-rw-r--r--src/filter.c337
-rw-r--r--src/filter.h65
6 files changed, 951 insertions, 76 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 8ea5d9e..f7e3b97 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -14,6 +14,8 @@ weston_LDADD = $(COMPOSITOR_LIBS) $(DLOPEN_LIBS) -lm ../shared/libshared.la
weston_SOURCES = \
compositor.c \
compositor.h \
+ filter.c \
+ filter.h \
screenshooter.c \
screenshooter-protocol.c \
screenshooter-server-protocol.h \
@@ -84,6 +86,7 @@ drm_backend_la_SOURCES = \
evdev.c \
evdev.h \
evdev-private.h \
+ evdev-touchpad.c \
launcher-util.c \
launcher-util.h \
libbacklight.c \
diff --git a/src/evdev-private.h b/src/evdev-private.h
index dd72430..c11c9b6 100644
--- a/src/evdev-private.h
+++ b/src/evdev-private.h
@@ -46,7 +46,6 @@ struct evdev_input_device {
int fd;
struct {
int min_x, max_x, min_y, max_y;
- int old_x, old_y, reset_x, reset_y;
int32_t x, y;
} abs;
@@ -63,7 +62,7 @@ struct evdev_input_device {
int type; /* event type flags */
- int is_touchpad, is_mt;
+ int is_mt;
};
/* event type flags */
@@ -100,4 +99,7 @@ struct evdev_dispatch {
struct evdev_dispatch_interface *interface;
};
+struct evdev_dispatch *
+evdev_touchpad_create(struct evdev_input_device *device);
+
#endif /* EVDEV_PRIVATE_H */
diff --git a/src/evdev-touchpad.c b/src/evdev-touchpad.c
new file mode 100644
index 0000000..dd7ab1c
--- /dev/null
+++ b/src/evdev-touchpad.c
@@ -0,0 +1,537 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <linux/input.h>
+
+#include "filter.h"
+#include "evdev-private.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
+
+enum touchpad_model {
+ TOUCHPAD_MODEL_UNKNOWN = 0,
+ TOUCHPAD_MODEL_SYNAPTICS,
+ TOUCHPAD_MODEL_ALPS,
+ TOUCHPAD_MODEL_APPLETOUCH,
+ TOUCHPAD_MODEL_ELANTECH
+};
+
+#define TOUCHPAD_EVENT_NONE 0
+#define TOUCHPAD_EVENT_ABSOLUTE_ANY (1 << 0)
+#define TOUCHPAD_EVENT_ABSOLUTE_X (1 << 1)
+#define TOUCHPAD_EVENT_ABSOLUTE_Y (1 << 2)
+#define 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,
+ TOUCHPAD_STATE_PRESS
+};
+
+#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)
+};
+
+struct touchpad_dispatch {
+ struct evdev_dispatch base;
+ struct evdev_input_device *device;
+
+ enum touchpad_model model;
+ enum touchpad_state 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 {
+ int32_t x;
+ int32_t y;
+ } hw_abs;
+
+ int has_pressure;
+ struct {
+ int32_t touch_low;
+ int32_t touch_high;
+ int32_t press;
+ } 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 wl_list motion_filters;
+};
+
+static enum touchpad_model
+get_touchpad_model(struct evdev_input_device *device)
+{
+ struct input_id id;
+ unsigned int i;
+
+ if (ioctl(device->fd, EVIOCGID, &id) < 0)
+ return TOUCHPAD_MODEL_UNKNOWN;
+
+ for (i = 0; i < sizeof 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);
+ }
+
+ touchpad->pressure.press = pressure_min + range;
+}
+
+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 void
+configure_touchpad(struct touchpad_dispatch *touchpad,
+ struct evdev_input_device *device)
+{
+ struct weston_motion_filter *accel;
+
+ struct input_absinfo absinfo;
+ unsigned long abs_bits[NBITS(ABS_MAX)];
+
+ double width;
+ double height;
+ double diagonal;
+
+ /* Detect model */
+ touchpad->model = get_touchpad_model(device);
+
+ /* 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->constant_accel_factor =
+ DEFAULT_CONSTANT_ACCEL_NUMERATOR / diagonal;
+
+ touchpad->min_accel_factor = DEFAULT_MIN_ACCEL_FACTOR;
+ touchpad->max_accel_factor = DEFAULT_MAX_ACCEL_FACTOR;
+
+ 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);
+ wl_list_insert(&touchpad->motion_filters, &accel->link);
+
+ /* 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;
+}
+
+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_filter *filter;
+ struct weston_motion_params motion;
+
+ motion.dx = *dx;
+ motion.dy = *dy;
+
+ wl_list_for_each(filter, &touchpad->motion_filters, link)
+ weston_filter_dispatch(filter, &motion, touchpad, time);
+
+ *dx = motion.dx;
+ *dy = motion.dy;
+}
+
+static void
+touchpad_update_state(struct touchpad_dispatch *touchpad, uint32_t time)
+{
+ int motion_index;
+ int center_x, center_y;
+ double dx, dy;
+
+ 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;
+
+ 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);
+
+ touchpad->device->rel.dx = wl_fixed_from_double(dx);
+ touchpad->device->rel.dy = wl_fixed_from_double(dy);
+ touchpad->device->type |= EVDEV_RELATIVE_MOTION;
+ }
+}
+
+static inline void
+process_absolute(struct touchpad_dispatch *touchpad,
+ struct evdev_input_device *device,
+ struct input_event *e)
+{
+ switch (e->code) {
+ case ABS_PRESSURE:
+ if (e->value > touchpad->pressure.press)
+ touchpad->state = TOUCHPAD_STATE_PRESS;
+ else if (e->value > touchpad->pressure.touch_high)
+ touchpad->state = TOUCHPAD_STATE_TOUCH;
+ else if (e->value < touchpad->pressure.touch_low) {
+ if (touchpad->state > TOUCHPAD_STATE_NONE)
+ touchpad->reset = 1;
+
+ touchpad->state = TOUCHPAD_STATE_NONE;
+ }
+
+ 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_input_device *device,
+ struct input_event *e,
+ uint32_t time)
+{
+ switch (e->code) {
+ case BTN_TOUCH:
+ if (!touchpad->has_pressure) {
+ if (!e->value) {
+ touchpad->state = TOUCHPAD_STATE_NONE;
+ touchpad->reset = 1;
+ } else {
+ touchpad->state =
+ e->value ?
+ TOUCHPAD_STATE_TOUCH :
+ TOUCHPAD_STATE_NONE;
+ }
+ }
+ 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:
+ notify_button(&device->master->base.seat,
+ time, e->code, e->value);
+ 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:
+ touchpad->finger_state =
+ ~TOUCHPAD_FINGERS_ONE | e->value ?
+ TOUCHPAD_FINGERS_ONE : 0;
+ break;
+ case BTN_TOOL_DOUBLETAP:
+ touchpad->finger_state =
+ ~TOUCHPAD_FINGERS_TWO | e->value ?
+ TOUCHPAD_FINGERS_TWO : 0;
+ break;
+ case BTN_TOOL_TRIPLETAP:
+ touchpad->finger_state =
+ ~TOUCHPAD_FINGERS_THREE | e->value ?
+ TOUCHPAD_FINGERS_THREE : 0;
+ break;
+ }
+}
+
+static void
+touchpad_process(struct evdev_dispatch *dispatch,
+ struct evdev_input_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;
+ struct weston_motion_filter *filter;
+ struct weston_motion_filter *next;
+
+ wl_list_for_each_safe(filter, next, &touchpad->motion_filters, link)
+ filter->interface->destroy(filter);
+
+ free(dispatch);
+}
+
+struct evdev_dispatch_interface touchpad_interface = {
+ touchpad_process,
+ touchpad_destroy
+};
+
+struct evdev_dispatch *
+evdev_touchpad_create(struct evdev_input_device *device)
+{
+ struct touchpad_dispatch *touchpad;
+
+ touchpad = malloc(sizeof *touchpad);
+ if (touchpad == NULL)
+ return NULL;
+
+ touchpad->base.interface = &touchpad_interface;
+
+ touchpad->device = device;
+ wl_list_init(&touchpad->motion_filters);
+
+ configure_touchpad(touchpad, device);
+
+ return &touchpad->base;
+}
diff --git a/src/evdev.c b/src/evdev.c
index d020187..73b012b 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -41,32 +41,6 @@ evdev_process_key(struct evdev_input_device *device,
return;
switch (e->code) {
- case BTN_TOOL_PEN:
- case BTN_TOOL_RUBBER:
- case BTN_TOOL_BRUSH:
- case BTN_TOOL_PENCIL:
- case BTN_TOOL_AIRBRUSH:
- case BTN_TOOL_FINGER:
- case BTN_TOOL_MOUSE:
- case BTN_TOOL_LENS:
- if (device->is_touchpad)
- {
- device->abs.reset_x = 1;
- device->abs.reset_y = 1;
- }
- break;
- case BTN_TOUCH:
- /* Multitouch touchscreen devices might not send individually
- * button events each time a new finger is down. So we don't
- * send notification for such devices and we solve the button
- * case emulating on compositor side. */
- if (device->is_mt)
- break;
-
- /* Treat BTN_TOUCH from devices that only have BTN_TOUCH as
- * BTN_LEFT */
- e->code = BTN_LEFT;
- /* Intentional fallthrough! */
case BTN_LEFT:
case BTN_RIGHT:
case BTN_MIDDLE:
@@ -146,49 +120,8 @@ evdev_process_absolute_motion(struct evdev_input_device *device,
}
static inline void
-evdev_process_absolute_motion_touchpad(struct evdev_input_device *device,
- struct input_event *e)
-{
- /* FIXME: Make this configurable somehow. */
- const int touchpad_speed = 700;
- int dx, dy;
-
- switch (e->code) {
- case ABS_X:
- e->value -= device->abs.min_x;
- if (device->abs.reset_x)
- device->abs.reset_x = 0;
- else {
- dx =
- (e->value - device->abs.old_x) *
- touchpad_speed /
- (device->abs.max_x - device->abs.min_x);
- device->rel.dx = wl_fixed_from_int(dx);
- }
- device->abs.old_x = e->value;
- device->type |= EVDEV_RELATIVE_MOTION;
- break;
- case ABS_Y:
- e->value -= device->abs.min_y;
- if (device->abs.reset_y)
- device->abs.reset_y = 0;
- else {
- dy =
- (e->value - device->abs.old_y) *
- touchpad_speed /
- /* maybe use x size here to have the same scale? */
- (device->abs.max_y - device->abs.min_y);
- device->rel.dy = wl_fixed_from_int(dy);
- }
- device->abs.old_y = e->value;
- device->type |= EVDEV_RELATIVE_MOTION;
- break;
- }
-}
-
-static inline void
evdev_process_relative(struct evdev_input_device *device,
- struct input_event *e, uint32_t time)
+ struct input_event *e, uint32_t time)
{
switch (e->code) {
case REL_X:
@@ -216,9 +149,7 @@ static inline void
evdev_process_absolute(struct evdev_input_device *device,
struct input_event *e)
{
- if (device->is_touchpad) {
- evdev_process_absolute_motion_touchpad(device, e);
- } else if (device->is_mt) {
+ if (device->is_mt) {
evdev_process_touch(device, e);
} else {
evdev_process_absolute_motion(device, e);
@@ -437,8 +368,9 @@ evdev_configure_device(struct evdev_input_device *device)
ioctl(device->fd, EVIOCGBIT(EV_KEY, sizeof(key_bits)),
key_bits);
if (TEST_BIT(key_bits, BTN_TOOL_FINGER) &&
- !TEST_BIT(key_bits, BTN_TOOL_PEN))
- device->is_touchpad = 1;
+ !TEST_BIT(key_bits, BTN_TOOL_PEN) &&
+ has_abs)
+ device->dispatch = evdev_touchpad_create(device);
}
/* This rule tries to catch accelerometer devices and opt out. We may
@@ -466,7 +398,6 @@ evdev_input_device_create(struct evdev_seat *master,
container_of(ec->output_list.next, struct weston_output, link);
device->master = master;
- device->is_touchpad = 0;
device->is_mt = 0;
device->mtdev = NULL;
device->devnode = strdup(path);
diff --git a/src/filter.c b/src/filter.c
new file mode 100644
index 0000000..72ef9be
--- /dev/null
+++ b/src/filter.c
@@ -0,0 +1,337 @@
+/*
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <limits.h>
+#include <math.h>
+
+#include <wayland-util.h>
+
+#include "compositor.h"
+#include "filter.h"
+
+void
+weston_filter_dispatch(struct weston_motion_filter *filter,
+ struct weston_motion_params *motion,
+ void *data, uint32_t time)
+{
+ filter->interface->filter(filter, motion, data, time);
+}
+
+/*
+ * Pointer acceleration filter
+ */
+
+#define MAX_VELOCITY_DIFF 1.0
+#define MOTION_TIMEOUT 300 /* (ms) */
+#define NUM_POINTER_TRACKERS 16
+
+struct pointer_tracker {
+ double dx;
+ double dy;
+ uint32_t time;
+ int dir;
+};
+
+struct pointer_accelerator;
+struct pointer_accelerator {
+ struct weston_motion_filter base;
+
+ accel_profile_func_t profile;
+
+ double velocity;
+ double last_velocity;
+ int last_dx;
+ int last_dy;
+
+ struct pointer_tracker *trackers;
+ int cur_tracker;
+};
+
+enum directions {
+ N = 1 << 0,
+ NE = 1 << 1,
+ E = 1 << 2,
+ SE = 1 << 3,
+ S = 1 << 4,
+ SW = 1 << 5,
+ W = 1 << 6,
+ NW = 1 << 7,
+ UNDEFINED_DIRECTION = 0xff
+};
+
+static int
+get_direction(int dx, int dy)
+{
+ int dir = UNDEFINED_DIRECTION;
+ int d1, d2;
+ double r;
+
+ if (abs(dx) < 2 && abs(dy) < 2) {
+ if (dx > 0 && dy > 0)
+ dir = S | SE | E;
+ else if (dx > 0 && dy < 0)
+ dir = N | NE | E;
+ else if (dx < 0 && dy > 0)
+ dir = S | SW | W;
+ else if (dx < 0 && dy < 0)
+ dir = N | NW | W;
+ else if (dx > 0)
+ dir = NW | W | SW;
+ else if (dx < 0)
+ dir = NE | E | SE;
+ else if (dy > 0)
+ dir = SE | S | SW;
+ else if (dy < 0)
+ dir = NE | N | NW;
+ }
+ else {
+ /* Calculate r within the interval [0 to 8)
+ *
+ * r = [0 .. 2π] where 0 is North
+ * d_f = r / 2π ([0 .. 1))
+ * d_8 = 8 * d_f
+ */
+ r = atan2(dy, dx);
+ r = fmod(r + 2.5*M_PI, 2*M_PI);
+ r *= 4*M_1_PI;
+
+ /* Mark one or two close enough octants */
+ d1 = (int)(r + 0.9) % 8;
+ d2 = (int)(r + 0.1) % 8;
+
+ dir = (1 << d1) | (1 << d2);
+ }
+
+ return dir;
+}
+
+static void
+feed_trackers(struct pointer_accelerator *accel,
+ double dx, double dy,
+ uint32_t time)
+{
+ int i, current;
+ struct pointer_tracker *trackers = accel->trackers;
+
+ for (i = 0; i < NUM_POINTER_TRACKERS; i++) {
+ trackers[i].dx += dx;
+ trackers[i].dy += dy;
+ }
+
+ current = (accel->cur_tracker + 1) % NUM_POINTER_TRACKERS;
+ accel->cur_tracker = current;
+
+ trackers[current].dx = 0.0;
+ trackers[current].dy = 0.0;
+ trackers[current].time = time;
+ trackers[current].dir = get_direction(dx, dy);
+}
+
+static struct pointer_tracker *
+tracker_by_offset(struct pointer_accelerator *accel, unsigned int offset)
+{
+ unsigned int index =
+ (accel->cur_tracker + NUM_POINTER_TRACKERS - offset)
+ % NUM_POINTER_TRACKERS;
+ return &accel->trackers[index];
+}
+
+static double
+calculate_tracker_velocity(struct pointer_tracker *tracker, uint32_t time)
+{
+ int dx;
+ int dy;
+ double distance;
+
+ dx = tracker->dx;
+ dy = tracker->dy;
+ distance = sqrt(dx*dx + dy*dy);
+ return distance / (double)(time - tracker->time);
+}
+
+static double
+calculate_velocity(struct pointer_accelerator *accel, uint32_t time)
+{
+ struct pointer_tracker *tracker;
+ double velocity;
+ double result = 0.0;
+ double initial_velocity;
+ double velocity_diff;
+ unsigned int offset;
+
+ unsigned int dir = tracker_by_offset(accel, 0)->dir;
+
+ /* Find first velocity */
+ for (offset = 1; offset < NUM_POINTER_TRACKERS; offset++) {
+ tracker = tracker_by_offset(accel, offset);
+
+ if (time <= tracker->time)
+ continue;
+
+ result = initial_velocity =
+ calculate_tracker_velocity(tracker, time);
+ if (initial_velocity > 0.0)
+ break;
+ }
+
+ /* Find least recent vector within a timelimit, maximum velocity diff
+ * and direction threshold. */
+ for (; offset < NUM_POINTER_TRACKERS; offset++) {
+ tracker = tracker_by_offset(accel, offset);
+
+ /* Stop if too far away in time */
+ if (time - tracker->time > MOTION_TIMEOUT ||
+ tracker->time > time)
+ break;
+
+ /* Stop if direction changed */
+ dir &= tracker->dir;
+ if (dir == 0)
+ break;
+
+ velocity = calculate_tracker_velocity(tracker, time);
+
+ /* Stop if velocity differs too much from initial */
+ velocity_diff = fabs(initial_velocity - velocity);
+ if (velocity_diff > MAX_VELOCITY_DIFF)
+ break;
+
+ result = velocity;
+ }
+
+ return result;
+}
+
+static double
+acceleration_profile(struct pointer_accelerator *accel,
+ void *data, double velocity, uint32_t time)
+{
+ return accel->profile(&accel->base, data, velocity, time);
+}
+
+static double
+calculate_acceleration(struct pointer_accelerator *accel,
+ void *data, double velocity, uint32_t time)
+{
+ double factor;
+
+ /* Use Simpson's rule to calculate the avarage acceleration between
+ * the previous motion and the most recent. */
+ factor = acceleration_profile(accel, data, velocity, time);
+ factor += acceleration_profile(accel, data, accel->last_velocity, time);
+ factor += 4.0 *
+ acceleration_profile(accel, data,
+ (accel->last_velocity + velocity) / 2,
+ time);
+
+ factor = factor / 6.0;
+
+ return factor;
+}
+
+static double
+soften_delta(double last_delta, double delta)
+{
+ if (delta < -1.0 || delta > 1.0) {
+ if (delta > last_delta)
+ return delta - 0.5;
+ else if (delta < last_delta)
+ return delta + 0.5;
+ }
+
+ return delta;
+}
+
+static void
+apply_softening(struct pointer_accelerator *accel,
+ struct weston_motion_params *motion)
+{
+ motion->dx = soften_delta(accel->last_dx, motion->dx);
+ motion->dy = soften_delta(accel->last_dy, motion->dy);
+}
+
+static void
+accelerator_filter(struct weston_motion_filter *filter,
+ struct weston_motion_params *motion,
+ void *data, uint32_t time)
+{
+ struct pointer_accelerator *accel =
+ (struct pointer_accelerator *) filter;
+ double velocity;
+ double accel_value;
+
+ feed_trackers(accel, motion->dx, motion->dy, time);
+ velocity = calculate_velocity(accel, time);
+ accel_value = calculate_acceleration(accel, data, velocity, time);
+
+ motion->dx = accel_value * motion->dx;
+ motion->dy = accel_value * motion->dy;
+
+ apply_softening(accel, motion);
+
+ accel->last_dx = motion->dx;
+ accel->last_dy = motion->dy;
+
+ accel->last_velocity = velocity;
+}
+
+static void
+accelerator_destroy(struct weston_motion_filter *filter)
+{
+ struct pointer_accelerator *accel =
+ (struct pointer_accelerator *) filter;
+
+ free(accel->trackers);
+ free(accel);
+}
+
+struct weston_motion_filter_interface accelerator_interface = {
+ accelerator_filter,
+ accelerator_destroy
+};
+
+struct weston_motion_filter *
+create_pointer_accelator_filter(accel_profile_func_t profile)
+{
+ struct pointer_accelerator *filter;
+
+ filter = malloc(sizeof *filter);
+ if (filter == NULL)
+ return NULL;
+
+ filter->base.interface = &accelerator_interface;
+ wl_list_init(&filter->base.link);
+
+ filter->profile = profile;
+ filter->last_velocity = 0.0;
+ filter->last_dx = 0;
+ filter->last_dy = 0;
+
+ filter->trackers =
+ calloc(NUM_POINTER_TRACKERS, sizeof *filter->trackers);
+ filter->cur_tracker = 0;
+
+ return &filter->base;
+}
diff --git a/src/filter.h b/src/filter.h
new file mode 100644
index 0000000..ff8e579
--- /dev/null
+++ b/src/filter.h
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+#ifndef _FILTER_H_
+#define _FILTER_H_
+
+#include <wayland-util.h>
+
+#include "compositor.h"
+
+struct weston_motion_params {
+ double dx, dy;
+};
+
+struct weston_motion_filter;
+
+WL_EXPORT void
+weston_filter_dispatch(struct weston_motion_filter *filter,
+ struct weston_motion_params *motion,
+ void *data, uint32_t time);
+
+
+struct weston_motion_filter_interface {
+ void (*filter)(struct weston_motion_filter *filter,
+ struct weston_motion_params *motion,
+ void *data, uint32_t time);
+ void (*destroy)(struct weston_motion_filter *filter);
+};
+
+struct weston_motion_filter {
+ struct weston_motion_filter_interface *interface;
+ struct wl_list link;
+};
+
+WL_EXPORT struct weston_motion_filter *
+create_linear_acceleration_filter(double speed);
+
+typedef double (*accel_profile_func_t)(struct weston_motion_filter *filter,
+ void *data,
+ double velocity,
+ uint32_t time);
+
+WL_EXPORT struct weston_motion_filter *
+create_pointer_accelator_filter(accel_profile_func_t filter);
+
+#endif // _FILTER_H_