diff --git a/src/Makefile.am b/src/Makefile.am index 8ea5d9e0..f7e3b97d 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 dd724306..c11c9b68 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 00000000..dd7ab1c9 --- /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 +#include +#include +#include +#include + +#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 d020187d..73b012b3 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: @@ -145,50 +119,9 @@ 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 00000000..72ef9be9 --- /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 +#include +#include +#include +#include + +#include + +#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 00000000..ff8e579f --- /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 + +#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_