diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..30a27dc1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +*.o +*.pc +*.la +*.lo +Makefile +Makefile.in +aclocal.m4 +autom4te.cache/ +config.guess +config.h +config.h.in +config.log +config.status +config.sub +configure +depcomp +install-sh +libtool +ltmain.sh +missing +stamp-h1 +src/.libs/ +src/.deps/ +src/Makefile +src/Makefile.in +src/libinput-version.h diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000..3a7ce7e8 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = src + +ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} diff --git a/README b/README new file mode 100644 index 00000000..06c30923 --- /dev/null +++ b/README @@ -0,0 +1,9 @@ +This library does processing on input device events while providing an API +to the the user used for delegating more useful input events. + +Input event processing includes scaling touch coordinates, generating +pointer events from touchpads, pointer acceleration, etc. + +It is based on the input code from the weston Wayland reference compositor. + +It has no other dependencies than libmtdev and supports only evdev devices. diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 00000000..b08bc831 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +test -n "$srcdir" || srcdir=`dirname "$0"` +test -n "$srcdir" || srcdir=. +( + cd "$srcdir" && + autoreconf --force -v --install +) || exit +test -n "$NOCONFIGURE" || "$srcdir/configure" "$@" diff --git a/configure.ac b/configure.ac new file mode 100644 index 00000000..08472cf0 --- /dev/null +++ b/configure.ac @@ -0,0 +1,56 @@ +AC_PREREQ([2.64]) + +m4_define([libinput_major_version], [0]) +m4_define([libinput_minor_version], [0]) +m4_define([libinput_micro_version], [90]) +m4_define([libinput_version], + [libinput_major_version.libinput_minor_version.libinput_micro_version]) + +AC_INIT([libinput], + [libinput_version], + [http://nobugtracker], + [libinput], + [http://nohomepage]) + +AC_SUBST([LIBINPUT_VERSION_MAJOR], [libinput_major_version]) +AC_SUBST([LIBINPUT_VERSION_MINOR], [libinput_minor_version]) +AC_SUBST([LIBINPUT_VERSION_MICRO], [libinput_micro_version]) +AC_SUBST([LIBINPUT_VERSION], [libinput_version]) + +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_MACRO_DIR([m4]) + +AM_INIT_AUTOMAKE([1.11 foreign no-dist-gzip dist-xz]) + +AM_SILENT_RULES([yes]) + +# Check for programs +AC_PROG_CC + +# Initialize libtool +LT_PREREQ([2.2]) +LT_INIT + +AC_CHECK_DECL(EPOLL_CLOEXEC, [], + [AC_MSG_ERROR("EPOLL_CLOEXEC is needed to compile libinput")], + [[#include ]]) +AC_CHECK_DECL(TFD_CLOEXEC,[], + [AC_MSG_ERROR("TFD_CLOEXEC is needed to compile libinput")], + [[#include ]]) +AC_CHECK_DECL(CLOCK_MONOTONIC,[], + [AC_MSG_ERROR("CLOCK_MONOTONIC is needed to compile libinput")], + [[#include ]]) + +PKG_PROG_PKG_CONFIG() +PKG_CHECK_MODULES(MTDEV, [mtdev >= 1.1.0]) + +if test "x$GCC" = "xyes"; then + GCC_CFLAGS="-Wall -Wextra -Wno-unused-parameter -g -Wstrict-prototypes -Wmissing-prototypes -fvisibility=hidden" +fi +AC_SUBST(GCC_CFLAGS) + +AC_CONFIG_FILES([Makefile + src/Makefile + src/libinput.pc + src/libinput-version.h]) +AC_OUTPUT diff --git a/m4/.gitignore b/m4/.gitignore new file mode 100644 index 00000000..38066ddf --- /dev/null +++ b/m4/.gitignore @@ -0,0 +1,5 @@ +libtool.m4 +ltoptions.m4 +ltsugar.m4 +ltversion.m4 +lt~obsolete.m4 diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 00000000..c1e31499 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,28 @@ +lib_LTLIBRARIES = libinput.la + +include_HEADERS = \ + libinput.h + +libinput_la_SOURCES = \ + libinput.c \ + libinput.h \ + libinput-util.c \ + libinput-util.h \ + evdev.c \ + evdev.h \ + evdev-touchpad.c \ + filter.c \ + filter.h + +libinput_la_LIBADD = $(MTDEV_LIBS) +libinput_la_CFLAGS = $(MTDEV_CFLAGS) \ + $(GCC_CFLAGS) + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libinput.pc + +AM_CPPFLAGS = $(FFI_CFLAGS) +AM_CFLAGS = $(GCC_CFLAGS) + +DISTCLEANFILES = libinput-version.h +EXTRA_DIST = libinput-version.h.in diff --git a/src/evdev-touchpad.c b/src/evdev-touchpad.c new file mode 100644 index 00000000..a673a7b7 --- /dev/null +++ b/src/evdev-touchpad.c @@ -0,0 +1,833 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "evdev.h" +#include "filter.h" +#include "libinput-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 + +#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; + + + enum fsm_event *events; + size_t events_len; + size_t events_count; + enum fsm_state state; + struct { + int fd; + struct libinput_source *source; + } timer; + } 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 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 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 motion_params motion; + + motion.dx = *dx; + motion.dy = *dy; + + 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) +{ + pointer_notify_button( + &touchpad->device->base, + time, + DEFAULT_TOUCHPAD_SINGLE_TAP_BUTTON, + LIBINPUT_POINTER_BUTTON_STATE_PRESSED); +} + +static void +notify_button_released(struct touchpad_dispatch *touchpad, uint32_t time) +{ + pointer_notify_button( + &touchpad->device->base, + time, + DEFAULT_TOUCHPAD_SINGLE_TAP_BUTTON, + LIBINPUT_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 event; + unsigned int i; + + if (!touchpad->fsm.enable) + return; + + if (touchpad->fsm.events_count == 0) + return; + + for (i = 0; i < touchpad->fsm.events_count; ++i) { + event = touchpad->fsm.events[i]; + 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: + touchpad->fsm.state = FSM_IDLE; + break; + } + } + + if (timeout != UINT32_MAX) { + struct itimerspec its; + + its.it_interval.tv_sec = 0; + its.it_interval.tv_nsec = 0; + its.it_value.tv_sec = timeout / 1000; + its.it_value.tv_nsec = (timeout % 1000) * 1000 * 1000; + timerfd_settime(touchpad->fsm.timer.fd, 0, &its, NULL); + } + + touchpad->fsm.events_count = 0; +} + +static void +push_fsm_event(struct touchpad_dispatch *touchpad, + enum fsm_event event) +{ + enum fsm_event *events; + size_t new_len = touchpad->fsm.events_len; + + if (!touchpad->fsm.enable) + return; + + if (touchpad->fsm.events_count + 1 >= touchpad->fsm.events_len) { + if (new_len == 0) + new_len = 4; + else + new_len *= 2; + events = realloc(touchpad->fsm.events, + sizeof(enum fsm_event) * new_len); + if (!events) { + touchpad->fsm.state = FSM_IDLE; + return; + } + + touchpad->fsm.events = events; + touchpad->fsm.events_len = new_len; + } + + touchpad->fsm.events[touchpad->fsm.events_count++] = event; +} + +static void +fsm_timeout_handler(void *data) +{ + struct touchpad_dispatch *touchpad = data; + uint64_t expires; + int len; + struct timespec ts; + uint32_t now; + + len = read(touchpad->fsm.timer.fd, &expires, sizeof expires); + if (len != sizeof expires) + /* This will only happen if the application made the fd + * non-blocking, but this function should only be called + * upon the timeout, so lets continue anyway. */ + fprintf(stderr, "timerfd read error: %m\n"); + + if (touchpad->fsm.events_count == 0) { + clock_gettime(CLOCK_MONOTONIC, &ts); + now = ts.tv_sec * 1000 + ts.tv_nsec / 1000000; + + push_fsm_event(touchpad, FSM_EVENT_TIMEOUT); + process_fsm_events(touchpad, now); + } +} + +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; + struct libinput_device *base = &touchpad->device->base; + + 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) { + pointer_notify_motion( + base, + time, + li_fixed_from_double(dx), + li_fixed_from_double(dy)); + } else if (touchpad->finger_state == TOUCHPAD_FINGERS_TWO) { + if (dx != 0.0) + pointer_notify_axis( + base, + time, + LIBINPUT_POINTER_AXIS_HORIZONTAL_SCROLL, + li_fixed_from_double(dx)); + if (dy != 0.0) + pointer_notify_axis( + base, + time, + LIBINPUT_POINTER_AXIS_VERTICAL_SCROLL, + li_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; + pointer_notify_button( + &touchpad->device->base, + time, + code, + e->value ? LIBINPUT_POINTER_BUTTON_STATE_PRESSED : + LIBINPUT_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; + struct libinput *libinput = touchpad->device->base.libinput; + + touchpad->filter->interface->destroy(touchpad->filter); + libinput_remove_source(libinput, touchpad->fsm.timer.source); + free(touchpad->fsm.events); + 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 motion_filter *accel; + + 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); + + /* Set default parameters */ + 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; + + touchpad->fsm.events = NULL; + touchpad->fsm.events_count = 0; + touchpad->fsm.events_len = 0; + touchpad->fsm.state = FSM_IDLE; + + touchpad->fsm.timer.fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); + touchpad->fsm.timer.source = + libinput_add_fd(touchpad->device->base.libinput, + touchpad->fsm.timer.fd, + fsm_timeout_handler, + touchpad); + + if (touchpad->fsm.timer.source == NULL) { + close(touchpad->fsm.timer.fd); + 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; +} diff --git a/src/evdev.c b/src/evdev.c new file mode 100644 index 00000000..55d8a155 --- /dev/null +++ b/src/evdev.c @@ -0,0 +1,714 @@ +/* + * Copyright © 2010 Intel Corporation + * Copyright © 2013 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 +#include +#include +#include +#include +#include +#include +#include + +#include "libinput.h" +#include "evdev.h" +#include "libinput-private.h" + +#define DEFAULT_AXIS_STEP_DISTANCE li_fixed_from_int(10) + +void +evdev_device_led_update(struct evdev_device *device, enum libinput_led leds) +{ + static const struct { + enum libinput_led weston; + int evdev; + } map[] = { + { LIBINPUT_LED_NUM_LOCK, LED_NUML }, + { LIBINPUT_LED_CAPS_LOCK, LED_CAPSL }, + { LIBINPUT_LED_SCROLL_LOCK, LED_SCROLLL }, + }; + struct input_event ev[ARRAY_LENGTH(map) + 1]; + unsigned int i; + + if (!device->caps & EVDEV_KEYBOARD) + return; + + memset(ev, 0, sizeof(ev)); + for (i = 0; i < ARRAY_LENGTH(map); i++) { + ev[i].type = EV_LED; + ev[i].code = map[i].evdev; + ev[i].value = !!(leds & map[i].weston); + } + ev[i].type = EV_SYN; + ev[i].code = SYN_REPORT; + + i = write(device->fd, ev, sizeof ev); + (void)i; /* no, we really don't care about the return value */ +} + +static void +transform_absolute(struct evdev_device *device, int32_t *x, int32_t *y) +{ + if (!device->abs.apply_calibration) { + *x = device->abs.x; + *y = device->abs.y; + return; + } else { + *x = device->abs.x * device->abs.calibration[0] + + device->abs.y * device->abs.calibration[1] + + device->abs.calibration[2]; + + *y = device->abs.x * device->abs.calibration[3] + + device->abs.y * device->abs.calibration[4] + + device->abs.calibration[5]; + } +} + +static void +evdev_flush_pending_event(struct evdev_device *device, uint32_t time) +{ + int32_t cx, cy; + int slot; + struct libinput_device *base = &device->base; + + slot = device->mt.slot; + + switch (device->pending_event) { + case EVDEV_NONE: + return; + case EVDEV_RELATIVE_MOTION: + pointer_notify_motion(base, + time, + device->rel.dx, + device->rel.dy); + device->rel.dx = 0; + device->rel.dy = 0; + goto handled; + case EVDEV_ABSOLUTE_MT_DOWN: + touch_notify_touch(base, + time, + slot, + li_fixed_from_int(device->mt.slots[slot].x), + li_fixed_from_int(device->mt.slots[slot].y), + LIBINPUT_TOUCH_TYPE_DOWN); + goto handled; + case EVDEV_ABSOLUTE_MT_MOTION: + touch_notify_touch(base, + time, + slot, + li_fixed_from_int(device->mt.slots[slot].x), + li_fixed_from_int(device->mt.slots[slot].y), + LIBINPUT_TOUCH_TYPE_MOTION); + goto handled; + case EVDEV_ABSOLUTE_MT_UP: + touch_notify_touch(base, + time, + slot, + 0, 0, + LIBINPUT_TOUCH_TYPE_UP); + goto handled; + case EVDEV_ABSOLUTE_TOUCH_DOWN: + transform_absolute(device, &cx, &cy); + touch_notify_touch(base, + time, + slot, + li_fixed_from_int(cx), + li_fixed_from_int(cy), + LIBINPUT_TOUCH_TYPE_DOWN); + goto handled; + case EVDEV_ABSOLUTE_MOTION: + transform_absolute(device, &cx, &cy); + if (device->caps & EVDEV_TOUCH) { + touch_notify_touch(base, + time, + slot, + li_fixed_from_int(cx), + li_fixed_from_int(cy), + LIBINPUT_TOUCH_TYPE_DOWN); + } else { + pointer_notify_motion_absolute(base, + time, + li_fixed_from_int(cx), + li_fixed_from_int(cy)); + } + goto handled; + case EVDEV_ABSOLUTE_TOUCH_UP: + touch_notify_touch(base, + time, + 0, 0, 0, LIBINPUT_TOUCH_TYPE_UP); + goto handled; + } + + assert(0 && "Unknown pending event type"); + +handled: + device->pending_event = EVDEV_NONE; +} + +static void +evdev_process_touch_button(struct evdev_device *device, int time, int value) +{ + if (device->pending_event != EVDEV_NONE && + device->pending_event != EVDEV_ABSOLUTE_MOTION) + evdev_flush_pending_event(device, time); + + device->pending_event = (value ? + EVDEV_ABSOLUTE_TOUCH_DOWN : + EVDEV_ABSOLUTE_TOUCH_UP); +} + +static inline void +evdev_process_key(struct evdev_device *device, struct input_event *e, int time) +{ + /* ignore kernel key repeat */ + if (e->value == 2) + return; + + if (e->code == BTN_TOUCH) { + if (!device->is_mt) + evdev_process_touch_button(device, time, e->value); + return; + } + + evdev_flush_pending_event(device, time); + + switch (e->code) { + case BTN_LEFT: + case BTN_RIGHT: + case BTN_MIDDLE: + case BTN_SIDE: + case BTN_EXTRA: + case BTN_FORWARD: + case BTN_BACK: + case BTN_TASK: + pointer_notify_button( + &device->base, + time, + e->code, + e->value ? LIBINPUT_POINTER_BUTTON_STATE_PRESSED : + LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + break; + + default: + keyboard_notify_key( + &device->base, + time, + e->code, + e->value ? LIBINPUT_KEYBOARD_KEY_STATE_PRESSED : + LIBINPUT_KEYBOARD_KEY_STATE_RELEASED); + break; + } +} + +static void +evdev_process_touch(struct evdev_device *device, + struct input_event *e, + uint32_t time) +{ + struct libinput *libinput = device->base.libinput; + int screen_width; + int screen_height; + + libinput->interface->get_current_screen_dimensions( + &device->base, + &screen_width, + &screen_height, + libinput->user_data); + + switch (e->code) { + case ABS_MT_SLOT: + evdev_flush_pending_event(device, time); + device->mt.slot = e->value; + break; + case ABS_MT_TRACKING_ID: + if (device->pending_event != EVDEV_NONE && + device->pending_event != EVDEV_ABSOLUTE_MT_MOTION) + evdev_flush_pending_event(device, time); + if (e->value >= 0) + device->pending_event = EVDEV_ABSOLUTE_MT_DOWN; + else + device->pending_event = EVDEV_ABSOLUTE_MT_UP; + break; + case ABS_MT_POSITION_X: + device->mt.slots[device->mt.slot].x = + (e->value - device->abs.min_x) * screen_width / + (device->abs.max_x - device->abs.min_x); + if (device->pending_event == EVDEV_NONE) + device->pending_event = EVDEV_ABSOLUTE_MT_MOTION; + break; + case ABS_MT_POSITION_Y: + device->mt.slots[device->mt.slot].y = + (e->value - device->abs.min_y) * screen_height / + (device->abs.max_y - device->abs.min_y); + if (device->pending_event == EVDEV_NONE) + device->pending_event = EVDEV_ABSOLUTE_MT_MOTION; + break; + } +} + +static inline void +evdev_process_absolute_motion(struct evdev_device *device, + struct input_event *e) +{ + struct libinput *libinput = device->base.libinput; + int screen_width; + int screen_height; + + libinput->interface->get_current_screen_dimensions( + &device->base, + &screen_width, + &screen_height, + libinput->user_data); + + switch (e->code) { + case ABS_X: + device->abs.x = + (e->value - device->abs.min_x) * screen_width / + (device->abs.max_x - device->abs.min_x); + if (device->pending_event == EVDEV_NONE) + device->pending_event = EVDEV_ABSOLUTE_MOTION; + break; + case ABS_Y: + device->abs.y = + (e->value - device->abs.min_y) * screen_height / + (device->abs.max_y - device->abs.min_y); + if (device->pending_event == EVDEV_NONE) + device->pending_event = EVDEV_ABSOLUTE_MOTION; + break; + } +} + +static inline void +evdev_process_relative(struct evdev_device *device, + struct input_event *e, uint32_t time) +{ + struct libinput_device *base = &device->base; + + switch (e->code) { + case REL_X: + if (device->pending_event != EVDEV_RELATIVE_MOTION) + evdev_flush_pending_event(device, time); + device->rel.dx += li_fixed_from_int(e->value); + device->pending_event = EVDEV_RELATIVE_MOTION; + break; + case REL_Y: + if (device->pending_event != EVDEV_RELATIVE_MOTION) + evdev_flush_pending_event(device, time); + device->rel.dy += li_fixed_from_int(e->value); + device->pending_event = EVDEV_RELATIVE_MOTION; + break; + case REL_WHEEL: + evdev_flush_pending_event(device, time); + switch (e->value) { + case -1: + /* Scroll down */ + case 1: + /* Scroll up */ + pointer_notify_axis( + base, + time, + LIBINPUT_POINTER_AXIS_VERTICAL_SCROLL, + -1 * e->value * DEFAULT_AXIS_STEP_DISTANCE); + break; + default: + break; + } + break; + case REL_HWHEEL: + evdev_flush_pending_event(device, time); + switch (e->value) { + case -1: + /* Scroll left */ + case 1: + /* Scroll right */ + pointer_notify_axis( + base, + time, + LIBINPUT_POINTER_AXIS_HORIZONTAL_SCROLL, + e->value * DEFAULT_AXIS_STEP_DISTANCE); + break; + default: + break; + + } + } +} + +static inline void +evdev_process_absolute(struct evdev_device *device, + struct input_event *e, + uint32_t time) +{ + if (device->is_mt) { + evdev_process_touch(device, e, time); + } else { + evdev_process_absolute_motion(device, e); + } +} + +static void +fallback_process(struct evdev_dispatch *dispatch, + struct evdev_device *device, + struct input_event *event, + uint32_t time) +{ + switch (event->type) { + case EV_REL: + evdev_process_relative(device, event, time); + break; + case EV_ABS: + evdev_process_absolute(device, event, time); + break; + case EV_KEY: + evdev_process_key(device, event, time); + break; + case EV_SYN: + evdev_flush_pending_event(device, time); + break; + } +} + +static void +fallback_destroy(struct evdev_dispatch *dispatch) +{ + free(dispatch); +} + +struct evdev_dispatch_interface fallback_interface = { + fallback_process, + fallback_destroy +}; + +static struct evdev_dispatch * +fallback_dispatch_create(void) +{ + struct evdev_dispatch *dispatch = malloc(sizeof *dispatch); + if (dispatch == NULL) + return NULL; + + dispatch->interface = &fallback_interface; + + return dispatch; +} + +static void +evdev_process_events(struct evdev_device *device, + struct input_event *ev, int count) +{ + struct evdev_dispatch *dispatch = device->dispatch; + struct input_event *e, *end; + uint32_t time = 0; + + e = ev; + end = e + count; + for (e = ev; e < end; e++) { + time = e->time.tv_sec * 1000 + e->time.tv_usec / 1000; + + dispatch->interface->process(dispatch, device, e, time); + } +} + +static void +evdev_device_dispatch(void *data) +{ + struct evdev_device *device = data; + int fd = device->fd; + struct input_event ev[32]; + int len; + + /* If the compositor is repainting, this function is called only once + * per frame and we have to process all the events available on the + * fd, otherwise there will be input lag. */ + do { + if (device->mtdev) + len = mtdev_get(device->mtdev, fd, ev, + ARRAY_LENGTH(ev)) * + sizeof (struct input_event); + else + len = read(fd, &ev, sizeof ev); + + if (len < 0 || len % sizeof ev[0] != 0) { + if (len < 0 && errno != EAGAIN && errno != EINTR) { + libinput_remove_source(device->base.libinput, + device->source); + device->source = NULL; + } + + return; + } + + evdev_process_events(device, ev, len / sizeof ev[0]); + + } while (len > 0); +} + +static int +evdev_handle_device(struct evdev_device *device) +{ + struct input_absinfo absinfo; + unsigned long ev_bits[NBITS(EV_MAX)]; + unsigned long abs_bits[NBITS(ABS_MAX)]; + unsigned long rel_bits[NBITS(REL_MAX)]; + unsigned long key_bits[NBITS(KEY_MAX)]; + int has_key, has_abs; + unsigned int i; + + has_key = 0; + has_abs = 0; + device->caps = 0; + + ioctl(device->fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits); + if (TEST_BIT(ev_bits, EV_ABS)) { + has_abs = 1; + + ioctl(device->fd, EVIOCGBIT(EV_ABS, sizeof(abs_bits)), + abs_bits); + + if (TEST_BIT(abs_bits, ABS_WHEEL) || + TEST_BIT(abs_bits, ABS_GAS) || + TEST_BIT(abs_bits, ABS_BRAKE) || + TEST_BIT(abs_bits, ABS_HAT0X)) { + /* Device %s is a joystick, ignoring. */ + return 0; + } + + if (TEST_BIT(abs_bits, ABS_X)) { + ioctl(device->fd, EVIOCGABS(ABS_X), &absinfo); + device->abs.min_x = absinfo.minimum; + device->abs.max_x = absinfo.maximum; + device->caps |= EVDEV_MOTION_ABS; + } + if (TEST_BIT(abs_bits, ABS_Y)) { + ioctl(device->fd, EVIOCGABS(ABS_Y), &absinfo); + device->abs.min_y = absinfo.minimum; + device->abs.max_y = absinfo.maximum; + device->caps |= EVDEV_MOTION_ABS; + } + /* We only handle the slotted Protocol B in weston. + Devices with ABS_MT_POSITION_* but not ABS_MT_SLOT + require mtdev for conversion. */ + if (TEST_BIT(abs_bits, ABS_MT_POSITION_X) && + TEST_BIT(abs_bits, ABS_MT_POSITION_Y)) { + ioctl(device->fd, EVIOCGABS(ABS_MT_POSITION_X), + &absinfo); + device->abs.min_x = absinfo.minimum; + device->abs.max_x = absinfo.maximum; + ioctl(device->fd, EVIOCGABS(ABS_MT_POSITION_Y), + &absinfo); + device->abs.min_y = absinfo.minimum; + device->abs.max_y = absinfo.maximum; + device->is_mt = 1; + device->caps |= EVDEV_TOUCH; + + if (!TEST_BIT(abs_bits, ABS_MT_SLOT)) { + device->mtdev = mtdev_new_open(device->fd); + if (!device->mtdev) { + /* mtdev required but failed to open. */ + return 0; + } + device->mt.slot = device->mtdev->caps.slot.value; + } else { + ioctl(device->fd, EVIOCGABS(ABS_MT_SLOT), + &absinfo); + device->mt.slot = absinfo.value; + } + } + } + if (TEST_BIT(ev_bits, EV_REL)) { + ioctl(device->fd, EVIOCGBIT(EV_REL, sizeof(rel_bits)), + rel_bits); + if (TEST_BIT(rel_bits, REL_X) || TEST_BIT(rel_bits, REL_Y)) + device->caps |= EVDEV_MOTION_REL; + } + if (TEST_BIT(ev_bits, EV_KEY)) { + has_key = 1; + 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) && + has_abs) { + device->dispatch = evdev_touchpad_create(device); + } + for (i = KEY_ESC; i < KEY_MAX; i++) { + if (i >= BTN_MISC && i < KEY_OK) + continue; + if (TEST_BIT(key_bits, i)) { + device->caps |= EVDEV_KEYBOARD; + break; + } + } + if (TEST_BIT(key_bits, BTN_TOUCH)) { + device->caps |= EVDEV_TOUCH; + } + for (i = BTN_MISC; i < BTN_JOYSTICK; i++) { + if (TEST_BIT(key_bits, i)) { + device->caps |= EVDEV_BUTTON; + device->caps &= ~EVDEV_TOUCH; + break; + } + } + } + if (TEST_BIT(ev_bits, EV_LED)) { + device->caps |= EVDEV_KEYBOARD; + } + + /* This rule tries to catch accelerometer devices and opt out. We may + * want to adjust the protocol later adding a proper event for dealing + * with accelerometers and implement here accordingly */ + if (has_abs && !has_key && !device->is_mt) { + return 0; + } + + return 1; +} + +static int +evdev_configure_device(struct evdev_device *device) +{ + if ((device->caps & (EVDEV_MOTION_ABS | EVDEV_MOTION_REL)) && + (device->caps & EVDEV_BUTTON)) { + device_register_capability(&device->base, + LIBINPUT_DEVICE_CAP_POINTER); + device->seat_caps |= EVDEV_DEVICE_POINTER; + } + if ((device->caps & EVDEV_KEYBOARD)) { + device_register_capability(&device->base, + LIBINPUT_DEVICE_CAP_KEYBOARD); + device->seat_caps |= EVDEV_DEVICE_KEYBOARD; + } + if ((device->caps & EVDEV_TOUCH)) { + device_register_capability(&device->base, + LIBINPUT_DEVICE_CAP_TOUCH); + device->seat_caps |= EVDEV_DEVICE_TOUCH; + } + + return 0; +} + +LIBINPUT_EXPORT struct libinput_device * +libinput_device_create_evdev( + struct libinput *libinput, + const char *devnode, + int fd, + void *user_data) +{ + struct evdev_device *device; + char devname[256] = "unknown"; + + device = zalloc(sizeof *device); + if (device == NULL) + return NULL; + + device->base.libinput = libinput; + device->base.user_data = user_data; + + device->seat_caps = 0; + device->is_mt = 0; + device->mtdev = NULL; + device->devnode = strdup(devnode); + device->mt.slot = -1; + device->rel.dx = 0; + device->rel.dy = 0; + device->dispatch = NULL; + device->fd = fd; + device->pending_event = EVDEV_NONE; + + ioctl(device->fd, EVIOCGNAME(sizeof(devname)), devname); + devname[sizeof(devname) - 1] = '\0'; + device->devname = strdup(devname); + + if (!evdev_handle_device(device)) { + evdev_device_destroy(device); + return NULL; + } + + if (evdev_configure_device(device) == -1) + goto err; + + /* If the dispatch was not set up use the fallback. */ + if (device->dispatch == NULL) + device->dispatch = fallback_dispatch_create(); + if (device->dispatch == NULL) + goto err; + + device->source = + libinput_add_fd(libinput, fd, evdev_device_dispatch, device); + if (!device->source) + goto err; + + return &device->base; + +err: + evdev_device_destroy(device); + return NULL; +} + +int +evdev_device_get_keys(struct evdev_device *device, char *keys, size_t size) +{ + memset(keys, 0, size); + return ioctl(device->fd, EVIOCGKEY(size), keys); +} + +void +evdev_device_calibrate(struct evdev_device *device, float calibration[6]) +{ + device->abs.apply_calibration = 1; + memcpy(device->abs.calibration, calibration, sizeof calibration); +} + +void +evdev_device_terminate(struct evdev_device *device) +{ + if (device->seat_caps & EVDEV_DEVICE_POINTER) { + device_unregister_capability(&device->base, + LIBINPUT_DEVICE_CAP_POINTER); + } + if (device->seat_caps & EVDEV_DEVICE_KEYBOARD) { + device_unregister_capability(&device->base, + LIBINPUT_DEVICE_CAP_KEYBOARD); + } + if (device->seat_caps & EVDEV_DEVICE_TOUCH) { + device_unregister_capability(&device->base, + LIBINPUT_DEVICE_CAP_TOUCH); + } +} + +void +evdev_device_destroy(struct evdev_device *device) +{ + struct evdev_dispatch *dispatch; + + dispatch = device->dispatch; + if (dispatch) + dispatch->interface->destroy(dispatch); + + if (device->mtdev) + mtdev_close_delete(device->mtdev); + close(device->fd); + free(device->devname); + free(device->devnode); + free(device); +} diff --git a/src/evdev.h b/src/evdev.h new file mode 100644 index 00000000..c8cb4054 --- /dev/null +++ b/src/evdev.h @@ -0,0 +1,153 @@ +/* + * Copyright © 2011, 2012 Intel Corporation + * Copyright © 2013 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 EVDEV_H +#define EVDEV_H + +#include "config.h" + +#include + +#include "evdev.h" +#include "libinput-private.h" + +static inline void * +zalloc(size_t size) +{ + return calloc(1, size); +} + +#define MAX_SLOTS 16 + +enum evdev_event_type { + EVDEV_NONE, + EVDEV_ABSOLUTE_TOUCH_DOWN, + EVDEV_ABSOLUTE_MOTION, + EVDEV_ABSOLUTE_TOUCH_UP, + EVDEV_ABSOLUTE_MT_DOWN, + EVDEV_ABSOLUTE_MT_MOTION, + EVDEV_ABSOLUTE_MT_UP, + EVDEV_RELATIVE_MOTION, +}; + +enum evdev_device_capability { + EVDEV_KEYBOARD = (1 << 0), + EVDEV_BUTTON = (1 << 1), + EVDEV_MOTION_ABS = (1 << 2), + EVDEV_MOTION_REL = (1 << 3), + EVDEV_TOUCH = (1 << 4), +}; + +enum evdev_device_seat_capability { + EVDEV_DEVICE_POINTER = (1 << 0), + EVDEV_DEVICE_KEYBOARD = (1 << 1), + EVDEV_DEVICE_TOUCH = (1 << 2) +}; + +struct evdev_device { + struct libinput_device base; + + struct libinput_source *source; + + struct evdev_dispatch *dispatch; + char *devnode; + char *devname; + int fd; + struct { + int min_x, max_x, min_y, max_y; + int32_t x, y; + + int apply_calibration; + float calibration[6]; + } abs; + + struct { + int slot; + struct { + int32_t x, y; + } slots[MAX_SLOTS]; + } mt; + struct mtdev *mtdev; + + struct { + li_fixed_t dx, dy; + } rel; + + enum evdev_event_type pending_event; + enum evdev_device_capability caps; + enum evdev_device_seat_capability seat_caps; + + int is_mt; +}; + +/* copied from udev/extras/input_id/input_id.c */ +/* we must use this kernel-compatible implementation */ +#define BITS_PER_LONG (sizeof(unsigned long) * 8) +#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1) +#define OFF(x) ((x)%BITS_PER_LONG) +#define BIT(x) (1UL<> OFF(bit)) & 1) +/* end copied */ + +#define EVDEV_UNHANDLED_DEVICE ((struct evdev_device *) 1) + +struct evdev_dispatch; + +struct evdev_dispatch_interface { + /* Process an evdev input event. */ + void (*process)(struct evdev_dispatch *dispatch, + struct evdev_device *device, + struct input_event *event, + uint32_t time); + + /* Destroy an event dispatch handler and free all its resources. */ + void (*destroy)(struct evdev_dispatch *dispatch); +}; + +struct evdev_dispatch { + struct evdev_dispatch_interface *interface; +}; + +struct evdev_dispatch * +evdev_touchpad_create(struct evdev_device *device); + +void +evdev_device_proces_event(struct libinput_event *event); + +void +evdev_device_led_update(struct evdev_device *device, enum libinput_led leds); + +int +evdev_device_get_keys(struct evdev_device *device, char *keys, size_t size); + +void +evdev_device_calibrate(struct evdev_device *device, float calibration[6]); + +void +evdev_device_terminate(struct evdev_device *terminate); + +void +evdev_device_destroy(struct evdev_device *device); + +#endif /* EVDEV_H */ diff --git a/src/filter.c b/src/filter.c new file mode 100644 index 00000000..397e0f63 --- /dev/null +++ b/src/filter.c @@ -0,0 +1,334 @@ +/* + * 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 +#include +#include +#include + +#include "filter.h" + +void +filter_dispatch(struct motion_filter *filter, + struct 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 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 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 motion_filter *filter, + struct 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 motion_filter *filter) +{ + struct pointer_accelerator *accel = + (struct pointer_accelerator *) filter; + + free(accel->trackers); + free(accel); +} + +struct motion_filter_interface accelerator_interface = { + accelerator_filter, + accelerator_destroy +}; + +struct 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; + + 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..6b2a1d20 --- /dev/null +++ b/src/filter.h @@ -0,0 +1,62 @@ +/* + * 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 "config.h" + +struct motion_params { + double dx, dy; +}; + +struct motion_filter; + +void +filter_dispatch(struct motion_filter *filter, + struct motion_params *motion, + void *data, uint32_t time); + + +struct motion_filter_interface { + void (*filter)(struct motion_filter *filter, + struct motion_params *motion, + void *data, uint32_t time); + void (*destroy)(struct motion_filter *filter); +}; + +struct motion_filter { + struct motion_filter_interface *interface; +}; + +struct motion_filter * +create_linear_acceleration_filter(double speed); + +typedef double (*accel_profile_func_t)(struct motion_filter *filter, + void *data, + double velocity, + uint32_t time); + +struct motion_filter * +create_pointer_accelator_filter(accel_profile_func_t filter); + +#endif /* FILTER_H */ diff --git a/src/libinput-private.h b/src/libinput-private.h new file mode 100644 index 00000000..f49badce --- /dev/null +++ b/src/libinput-private.h @@ -0,0 +1,113 @@ +/* + * Copyright © 2013 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 LIBINPUT_PRIVATE_H +#define LIBINPUT_PRIVATE_H + +#include "libinput.h" +#include "libinput-util.h" + +struct libinput { + int epoll_fd; + struct list source_destroy_list; + + struct libinput_event **events; + size_t events_count; + size_t events_len; + size_t events_in; + size_t events_out; + + const struct libinput_interface *interface; + void *user_data; +}; + +struct libinput_device { + struct libinput *libinput; + void *user_data; + int terminated; +}; + +typedef void (*libinput_source_dispatch_t)(void *data); + +struct libinput_source; + +struct libinput_source * +libinput_add_fd(struct libinput *libinput, + int fd, + libinput_source_dispatch_t dispatch, + void *data); + +void +libinput_remove_source(struct libinput *libinput, + struct libinput_source *source); + +void +libinput_post_event(struct libinput *libinput, + struct libinput_event *event); + +void +device_register_capability(struct libinput_device *device, + enum libinput_device_capability capability); + +void +device_unregister_capability(struct libinput_device *device, + enum libinput_device_capability capability); + +void +keyboard_notify_key(struct libinput_device *device, + uint32_t time, + uint32_t key, + enum libinput_keyboard_key_state state); + +void +pointer_notify_motion(struct libinput_device *device, + uint32_t time, + li_fixed_t dx, + li_fixed_t dy); + +void +pointer_notify_motion_absolute(struct libinput_device *device, + uint32_t time, + li_fixed_t x, + li_fixed_t y); + +void +pointer_notify_button(struct libinput_device *device, + uint32_t time, + int32_t button, + enum libinput_pointer_button_state state); + +void +pointer_notify_axis(struct libinput_device *device, + uint32_t time, + enum libinput_pointer_axis axis, + li_fixed_t value); + +void +touch_notify_touch(struct libinput_device *device, + uint32_t time, + int32_t slot, + li_fixed_t x, + li_fixed_t y, + enum libinput_touch_type touch_type); + +#endif /* LIBINPUT_PRIVATE_H */ diff --git a/src/libinput-util.c b/src/libinput-util.c new file mode 100644 index 00000000..9d9d4dd8 --- /dev/null +++ b/src/libinput-util.c @@ -0,0 +1,62 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * + * 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. + */ + +/* + * This list data structure is verbatim copy from wayland-util.h from the + * Wayland project; except that wl_ prefix has been removed. + */ + +#include "config.h" + +#include "libinput-private.h" + +void +list_init(struct list *list) +{ + list->prev = list; + list->next = list; +} + +void +list_insert(struct list *list, struct list *elm) +{ + elm->prev = list; + elm->next = list->next; + list->next = elm; + elm->next->prev = elm; +} + +void +list_remove(struct list *elm) +{ + elm->prev->next = elm->next; + elm->next->prev = elm->prev; + elm->next = NULL; + elm->prev = NULL; +} + +int +list_empty(const struct list *list) +{ + return list->next == list; +} diff --git a/src/libinput-util.h b/src/libinput-util.h new file mode 100644 index 00000000..1395106e --- /dev/null +++ b/src/libinput-util.h @@ -0,0 +1,91 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * 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 LIBINPUT_UTIL_H +#define LIBINPUT_UTIL_H + +/* + * This list data structure is a verbatim copy from wayland-util.h from the + * Wayland project; except that wl_ prefix has been removed. + */ + +struct list { + struct list *prev; + struct list *next; +}; + +void list_init(struct list *list); +void list_insert(struct list *list, struct list *elm); +void list_remove(struct list *elm); +int list_empty(const struct list *list); + +#ifdef __GNUC__ +#define container_of(ptr, sample, member) \ + (__typeof__(sample))((char *)(ptr) - \ + ((char *)&(sample)->member - (char *)(sample))) +#else +#define container_of(ptr, sample, member) \ + (void *)((char *)(ptr) - \ + ((char *)&(sample)->member - (char *)(sample))) +#endif + +#define list_for_each(pos, head, member) \ + for (pos = 0, pos = container_of((head)->next, pos, member); \ + &pos->member != (head); \ + pos = container_of(pos->member.next, pos, member)) + +#define list_for_each_safe(pos, tmp, head, member) \ + for (pos = 0, tmp = 0, \ + pos = container_of((head)->next, pos, member), \ + tmp = container_of((pos)->member.next, tmp, member); \ + &pos->member != (head); \ + pos = tmp, \ + tmp = container_of(pos->member.next, tmp, member)) + +#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0]) + +/* + * This fixed point implementation is a verbatim copy from wayland-util.h from + * the Wayland project, with the wl_ prefix renamed li_. + */ + +static inline li_fixed_t li_fixed_from_int(int i) +{ + return i * 256; +} + +static inline li_fixed_t +li_fixed_from_double(double d) +{ + union { + double d; + int64_t i; + } u; + + u.d = d + (3LL << (51 - 8)); + + return u.i; +} + +#define LIBINPUT_EXPORT __attribute__ ((visibility("default"))) + +#endif /* LIBINPUT_UTIL_H */ diff --git a/src/libinput-version.h.in b/src/libinput-version.h.in new file mode 100644 index 00000000..2b1c4ea2 --- /dev/null +++ b/src/libinput-version.h.in @@ -0,0 +1,31 @@ +/* + * Copyright © 2013 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 LIBINPUT_VERSION_H +#define LIBINPUT_VERSION_H + +#define LIBINPUT_VERSION_MAJOR @LIBINPUT_VERSION_MAJOR@ +#define LIBINPUT_VERSION_MINOR @LIBINPUT_VERSION_MINOR@ +#define LIBINPUT_VERSION_MICRO @LIBINPUT_VERSION_MICRO@ +#define LIBINPUT_VERSION "@LIBINPUT_VERSION@" + +#endif diff --git a/src/libinput.c b/src/libinput.c new file mode 100644 index 00000000..10d6fc13 --- /dev/null +++ b/src/libinput.c @@ -0,0 +1,444 @@ +/* + * Copyright © 2013 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 +#include +#include +#include +#include +#include + +#include "libinput.h" +#include "evdev.h" +#include "libinput-private.h" + +struct libinput_source { + libinput_source_dispatch_t dispatch; + void *user_data; + int fd; + struct list link; +}; + +struct libinput_source * +libinput_add_fd(struct libinput *libinput, + int fd, + libinput_source_dispatch_t dispatch, + void *user_data) +{ + struct libinput_source *source; + struct epoll_event ep; + + source = malloc(sizeof *source); + if (!source) + return NULL; + + source->dispatch = dispatch; + source->user_data = user_data; + source->fd = fd; + + memset(&ep, 0, sizeof ep); + ep.events = EPOLLIN; + ep.data.ptr = source; + + if (epoll_ctl(libinput->epoll_fd, EPOLL_CTL_ADD, fd, &ep) < 0) { + close(source->fd); + free(source); + return NULL; + } + + return source; +} + +void +libinput_remove_source(struct libinput *libinput, + struct libinput_source *source) +{ + epoll_ctl(libinput->epoll_fd, EPOLL_CTL_DEL, source->fd, NULL); + close(source->fd); + source->fd = -1; + list_insert(&libinput->source_destroy_list, &source->link); +} + +LIBINPUT_EXPORT struct libinput * +libinput_create(const struct libinput_interface *interface, void *user_data) +{ + struct libinput *libinput; + + libinput = zalloc(sizeof *libinput); + if (!libinput) + return NULL; + + list_init(&libinput->source_destroy_list); + + libinput->interface = interface; + libinput->user_data = user_data; + + libinput->epoll_fd = epoll_create1(EPOLL_CLOEXEC);; + if (libinput->epoll_fd < 0) + return NULL; + + return libinput; +} + +LIBINPUT_EXPORT void +libinput_destroy(struct libinput *libinput) +{ + struct libinput_event *event; + + while ((event = libinput_get_event(libinput))) + free(event); + free(libinput->events); + + close(libinput->epoll_fd); + free(libinput); +} + +LIBINPUT_EXPORT int +libinput_get_fd(struct libinput *libinput) +{ + return libinput->epoll_fd; +} + +LIBINPUT_EXPORT int +libinput_dispatch(struct libinput *libinput) +{ + struct libinput_source *source, *next; + struct epoll_event ep[32]; + int i, count; + + count = epoll_wait(libinput->epoll_fd, ep, ARRAY_LENGTH(ep), 0); + if (count < 0) + return -1; + + for (i = 0; i < count; ++i) { + source = ep[i].data.ptr; + if (source->fd == -1) + continue; + + source->dispatch(source->user_data); + } + + list_for_each_safe(source, next, &libinput->source_destroy_list, link) + free(source); + list_init(&libinput->source_destroy_list); + + return 0; +} + +static void +init_event_base(struct libinput_event *event, + enum libinput_event_type type, + struct libinput_device *device) +{ + event->type = type; + event->device = device; +} + +static void +post_device_event(struct libinput_device *device, + enum libinput_event_type type, + struct libinput_event *event) +{ + init_event_base(event, type, device); + libinput_post_event(device->libinput, event); +} + +void +device_register_capability(struct libinput_device *device, + enum libinput_device_capability capability) +{ + struct libinput_event_device_register_capability *capability_event; + + capability_event = malloc(sizeof *capability_event); + + *capability_event = (struct libinput_event_device_register_capability) { + .capability = capability, + }; + + post_device_event(device, + LIBINPUT_EVENT_DEVICE_REGISTER_CAPABILITY, + &capability_event->base); +} + +void +device_unregister_capability(struct libinput_device *device, + enum libinput_device_capability capability) +{ + struct libinput_event_device_unregister_capability *capability_event; + + capability_event = malloc(sizeof *capability_event); + + *capability_event = (struct libinput_event_device_unregister_capability) { + .capability = capability, + }; + + post_device_event(device, + LIBINPUT_EVENT_DEVICE_UNREGISTER_CAPABILITY, + &capability_event->base); +} + +void +keyboard_notify_key(struct libinput_device *device, + uint32_t time, + uint32_t key, + enum libinput_keyboard_key_state state) +{ + struct libinput_event_keyboard_key *key_event; + + key_event = malloc(sizeof *key_event); + if (!key_event) + return; + + *key_event = (struct libinput_event_keyboard_key) { + .time = time, + .key = key, + .state = state, + }; + + post_device_event(device, + LIBINPUT_EVENT_KEYBOARD_KEY, + &key_event->base); +} + +void +pointer_notify_motion(struct libinput_device *device, + uint32_t time, + li_fixed_t dx, + li_fixed_t dy) +{ + struct libinput_event_pointer_motion *motion_event; + + motion_event = malloc(sizeof *motion_event); + if (!motion_event) + return; + + *motion_event = (struct libinput_event_pointer_motion) { + .time = time, + .dx = dx, + .dy = dy, + }; + + post_device_event(device, + LIBINPUT_EVENT_POINTER_MOTION, + &motion_event->base); +} + +void +pointer_notify_motion_absolute(struct libinput_device *device, + uint32_t time, + li_fixed_t x, + li_fixed_t y) +{ + struct libinput_event_pointer_motion_absolute *motion_absolute_event; + + motion_absolute_event = malloc(sizeof *motion_absolute_event); + if (!motion_absolute_event) + return; + + *motion_absolute_event = (struct libinput_event_pointer_motion_absolute) { + .time = time, + .x = x, + .y = y, + }; + + post_device_event(device, + LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE, + &motion_absolute_event->base); +} + +void +pointer_notify_button(struct libinput_device *device, + uint32_t time, + int32_t button, + enum libinput_pointer_button_state state) +{ + struct libinput_event_pointer_button *button_event; + + button_event = malloc(sizeof *button_event); + if (!button_event) + return; + + *button_event = (struct libinput_event_pointer_button) { + .time = time, + .button = button, + .state = state, + }; + + post_device_event(device, + LIBINPUT_EVENT_POINTER_BUTTON, + &button_event->base); +} + +void +pointer_notify_axis(struct libinput_device *device, + uint32_t time, + enum libinput_pointer_axis axis, + li_fixed_t value) +{ + struct libinput_event_pointer_axis *axis_event; + + axis_event = malloc(sizeof *axis_event); + if (!axis_event) + return; + + *axis_event = (struct libinput_event_pointer_axis) { + .time = time, + .axis = axis, + .value = value, + }; + + post_device_event(device, + LIBINPUT_EVENT_POINTER_AXIS, + &axis_event->base); +} + +void +touch_notify_touch(struct libinput_device *device, + uint32_t time, + int32_t slot, + li_fixed_t x, + li_fixed_t y, + enum libinput_touch_type touch_type) +{ + struct libinput_event_touch_touch *touch_event; + + touch_event = malloc(sizeof *touch_event); + if (!touch_event) + return; + + *touch_event = (struct libinput_event_touch_touch) { + .time = time, + .slot = slot, + .x = x, + .y = y, + .touch_type = touch_type, + }; + + post_device_event(device, + LIBINPUT_EVENT_TOUCH_TOUCH, + &touch_event->base); +} + +void +libinput_post_event(struct libinput *libinput, + struct libinput_event *event) +{ + struct libinput_event **events = libinput->events; + size_t events_len = libinput->events_len; + size_t events_count = libinput->events_count; + size_t move_len; + size_t new_out; + + events_count++; + if (events_count > events_len) { + if (events_len == 0) + events_len = 4; + else + events_len *= 2; + events = realloc(events, events_len * sizeof *events); + if (!events) { + fprintf(stderr, "Failed to reallocate event ring " + "buffer"); + return; + } + + if (libinput->events_count > 0 && libinput->events_in == 0) { + libinput->events_in = libinput->events_len; + } else if (libinput->events_count > 0 && + libinput->events_out >= libinput->events_in) { + move_len = libinput->events_len - libinput->events_out; + new_out = events_len - move_len; + memmove(events + new_out, + libinput->events + libinput->events_out, + move_len * sizeof *events); + libinput->events_out = new_out; + } + + libinput->events = events; + libinput->events_len = events_len; + } + + libinput->events_count = events_count; + events[libinput->events_in] = event; + libinput->events_in = (libinput->events_in + 1) % libinput->events_len; +} + +LIBINPUT_EXPORT struct libinput_event * +libinput_get_event(struct libinput *libinput) +{ + struct libinput_event *event; + + if (libinput->events_count == 0) + return NULL; + + event = libinput->events[libinput->events_out]; + libinput->events_out = + (libinput->events_out + 1) % libinput->events_len; + libinput->events_count--; + + return event; +} + +LIBINPUT_EXPORT void +libinput_device_terminate(struct libinput_device *device) +{ + evdev_device_terminate((struct evdev_device *) device); + device->terminated = 1; +} + +LIBINPUT_EXPORT void +libinput_device_destroy(struct libinput_device *device) +{ + assert(device->terminated); + evdev_device_destroy((struct evdev_device *) device); +} + +LIBINPUT_EXPORT void * +libinput_device_get_user_data(struct libinput_device *device) +{ + return device->user_data; +} + +LIBINPUT_EXPORT void +libinput_device_led_update(struct libinput_device *device, + enum libinput_led leds) +{ + evdev_device_led_update((struct evdev_device *) device, leds); +} + +LIBINPUT_EXPORT int +libinput_device_get_keys(struct libinput_device *device, + char *keys, size_t size) +{ + return evdev_device_get_keys((struct evdev_device *) device, + keys, + size); +} + +LIBINPUT_EXPORT void +libinput_device_calibrate(struct libinput_device *device, + float calibration[6]) +{ + evdev_device_calibrate((struct evdev_device *) device, calibration); +} diff --git a/src/libinput.h b/src/libinput.h new file mode 100644 index 00000000..a051e4ac --- /dev/null +++ b/src/libinput.h @@ -0,0 +1,195 @@ +/* + * Copyright © 2013 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 LIBINPUT_H +#define LIBINPUT_H + +#include +#include + +typedef int32_t li_fixed_t; + +enum libinput_device_capability { + LIBINPUT_DEVICE_CAP_KEYBOARD = 0, + LIBINPUT_DEVICE_CAP_POINTER = 1, + LIBINPUT_DEVICE_CAP_TOUCH = 2, +}; + +enum libinput_keyboard_key_state { + LIBINPUT_KEYBOARD_KEY_STATE_RELEASED = 0, + LIBINPUT_KEYBOARD_KEY_STATE_PRESSED = 1, +}; + +enum libinput_led { + LIBINPUT_LED_NUM_LOCK = (1 << 0), + LIBINPUT_LED_CAPS_LOCK = (1 << 1), + LIBINPUT_LED_SCROLL_LOCK = (1 << 2), +}; + +enum libinput_pointer_button_state { + LIBINPUT_POINTER_BUTTON_STATE_RELEASED = 0, + LIBINPUT_POINTER_BUTTON_STATE_PRESSED = 1, +}; + +enum libinput_pointer_axis { + LIBINPUT_POINTER_AXIS_VERTICAL_SCROLL = 0, + LIBINPUT_POINTER_AXIS_HORIZONTAL_SCROLL = 1, +}; + +enum libinput_touch_type { + LIBINPUT_TOUCH_TYPE_DOWN = 0, + LIBINPUT_TOUCH_TYPE_UP = 1, + LIBINPUT_TOUCH_TYPE_MOTION = 2, + LIBINPUT_TOUCH_TYPE_FRAME = 3, + LIBINPUT_TOUCH_TYPE_CANCEL = 4, +}; + +enum libinput_event_type { + LIBINPUT_EVENT_DEVICE_REGISTER_CAPABILITY = 200, + LIBINPUT_EVENT_DEVICE_UNREGISTER_CAPABILITY, + + LIBINPUT_EVENT_KEYBOARD_KEY = 300, + + LIBINPUT_EVENT_POINTER_MOTION = 400, + LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE, + LIBINPUT_EVENT_POINTER_BUTTON, + LIBINPUT_EVENT_POINTER_AXIS, + + LIBINPUT_EVENT_TOUCH_TOUCH = 500, +}; + +struct libinput_event { + enum libinput_event_type type; + struct libinput_device *device; +}; + +struct libinput_event_device_register_capability { + struct libinput_event base; + enum libinput_device_capability capability; +}; + +struct libinput_event_device_unregister_capability { + struct libinput_event base; + enum libinput_device_capability capability; +}; + +struct libinput_event_keyboard_key { + struct libinput_event base; + uint32_t time; + uint32_t key; + enum libinput_keyboard_key_state state; +}; + +struct libinput_event_pointer_motion { + struct libinput_event base; + uint32_t time; + li_fixed_t dx; + li_fixed_t dy; +}; + +struct libinput_event_pointer_motion_absolute { + struct libinput_event base; + uint32_t time; + li_fixed_t x; + li_fixed_t y; +}; + +struct libinput_event_pointer_button { + struct libinput_event base; + uint32_t time; + int32_t button; + enum libinput_pointer_button_state state; +}; + +struct libinput_event_pointer_axis { + struct libinput_event base; + uint32_t time; + enum libinput_pointer_axis axis; + li_fixed_t value; +}; + +struct libinput_event_touch_touch { + struct libinput_event base; + uint32_t time; + int32_t slot; + li_fixed_t x; + li_fixed_t y; + enum libinput_touch_type touch_type; +}; + +struct libinput_fd_handle; + +typedef void (*libinput_fd_callback)(int fd, void *data); + +struct libinput_interface { + void (*get_current_screen_dimensions)(struct libinput_device *device, + int *width, + int *height, + void *user_data); +}; + +struct libinput; +struct libinput_device; + +struct libinput * +libinput_create(const struct libinput_interface *interface, void *user_data); + +int +libinput_get_fd(struct libinput *libinput); + +int +libinput_dispatch(struct libinput *libinput); + +struct libinput_event * +libinput_get_event(struct libinput *libinput); + +void +libinput_destroy(struct libinput *libinput); + +struct libinput_device * +libinput_device_create_evdev(struct libinput *libinput, + const char *devnode, + int fd, + void *user_data); + +void +libinput_device_terminate(struct libinput_device *device); + +void +libinput_device_destroy(struct libinput_device *device); + +void * +libinput_device_get_user_data(struct libinput_device *device); + +void +libinput_device_led_update(struct libinput_device *device, + enum libinput_led leds); + +int +libinput_device_get_keys(struct libinput_device *device, + char *keys, size_t size); + +void +libinput_device_calibrate(struct libinput_device *device, + float calibration[6]); + +#endif /* LIBINPUT_H */ diff --git a/src/libinput.pc.in b/src/libinput.pc.in new file mode 100644 index 00000000..ed51acc9 --- /dev/null +++ b/src/libinput.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +datarootdir=@datarootdir@ +pkgdatadir=@datadir@/@PACKAGE@ +libdir=@libdir@ +includedir=@includedir@ + +Name: Libinput +Description: Input device library +Version: @LIBINPUT_VERSION@ +Cflags: -I${includedir} +Libs: -L${libdir} -linput