/* * 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.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 { 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.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; 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) { touchpad->device->rel.dx = wl_fixed_from_double(dx); touchpad->device->rel.dy = wl_fixed_from_double(dy); touchpad->device->pending_events |= EVDEV_RELATIVE_MOTION | EVDEV_SYN; } 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) { 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: notify_button(device->seat, time, e->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 int touchpad_init(struct touchpad_dispatch *touchpad, struct evdev_device *device) { struct weston_motion_filter *accel; struct wl_event_loop *loop; struct input_absinfo absinfo; unsigned long abs_bits[NBITS(ABS_MAX)]; double width; double height; double diagonal; touchpad->base.interface = &touchpad_interface; touchpad->device = device; /* 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); 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; } 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; }