diff --git a/README.txt b/README.txt index fabe304b..c5dc61e5 100644 --- a/README.txt +++ b/README.txt @@ -22,7 +22,7 @@ For more information, visit: http://www.freedesktop.org/wiki/Software/libinput/ Bugs can be filed in the libinput component of Wayland: -https://bugs.freedesktop.org/enter_bug.cgi?product=Wayland +https://bugs.freedesktop.org/enter_bug.cgi?product=Wayland&component=libinput Online API documentation: http://wayland.freedesktop.org/libinput/doc/latest/modules.html diff --git a/configure.ac b/configure.ac index 39e42d0f..f47c5a49 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ AC_PREREQ([2.64]) m4_define([libinput_major_version], [0]) -m4_define([libinput_minor_version], [9]) +m4_define([libinput_minor_version], [10]) m4_define([libinput_micro_version], [0]) m4_define([libinput_version], [libinput_major_version.libinput_minor_version.libinput_micro_version]) @@ -30,10 +30,11 @@ AM_INIT_AUTOMAKE([1.11 foreign no-dist-gzip dist-xz]) # - If binary compatibility has been broken (eg removed or changed interfaces) # change to C+1:0:0 # - If the interface is the same as the previous version, change to C:R+1:A -LIBINPUT_LT_VERSION=8:0:1 +LIBINPUT_LT_VERSION=8:1:1 AC_SUBST(LIBINPUT_LT_VERSION) AM_SILENT_RULES([yes]) +AC_USE_SYSTEM_EXTENSIONS # Check for programs AC_PROG_CC_C99 diff --git a/doc/clickpad-softbuttons.dox b/doc/clickpad-softbuttons.dox index d9ebfbbc..8d919369 100644 --- a/doc/clickpad-softbuttons.dox +++ b/doc/clickpad-softbuttons.dox @@ -54,6 +54,13 @@ The movement of a finger can alter the button area behavior: - once a finger has moved out of the button area, it cannot move back in and trigger a right or middle button event +On some touchpads, notably the 2015 Lenovo X1 Carbon 3rd series, the very +bottom end of the touchpad is outside of the sensor range but it is possible +to trigger a physical click there. To libinput, the click merely shows up as +a left button click without any positional finger data and it is +impossible to determine whether it is a left or a right click. libinput +ignores such button clicks, this behavior is intentional. + @section clickfinger Clickfinger behavior This is the default behavior on Apple touchpads. diff --git a/src/evdev-mt-touchpad-buttons.c b/src/evdev-mt-touchpad-buttons.c index ca7495f8..9dbb5137 100644 --- a/src/evdev-mt-touchpad-buttons.c +++ b/src/evdev-mt-touchpad-buttons.c @@ -823,6 +823,7 @@ tp_post_clickpadbutton_buttons(struct tp_dispatch *tp, uint64_t time) break; case BUTTON_EVENT_IN_TOP_L: is_top = 1; + /* fallthrough */ case BUTTON_EVENT_IN_BOTTOM_L: button |= LEFT; break; @@ -832,6 +833,7 @@ tp_post_clickpadbutton_buttons(struct tp_dispatch *tp, uint64_t time) break; case BUTTON_EVENT_IN_TOP_R: is_top = 1; + /* fallthrough */ case BUTTON_EVENT_IN_BOTTOM_R: button |= RIGHT; break; diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 94cbc136..ae37ab17 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -1107,12 +1107,6 @@ tp_tag_device(struct evdev_device *device, if (udev_device_get_property_value(udev_device, "TOUCHPAD_HAS_TRACKPOINT_BUTTONS")) device->tags |= EVDEV_TAG_TOUCHPAD_TRACKPOINT; - - /* Magic version tag: used by the litest device. Should never be set - * in real life but allows us to test for these features without - * requiring custom udev rules during make check */ - if (libevdev_get_id_version(device->evdev) == 0xfffa) - device->tags |= EVDEV_TAG_TOUCHPAD_TRACKPOINT; } static struct evdev_dispatch_interface tp_interface = { diff --git a/src/evdev.c b/src/evdev.c index a9765df3..eb963ab1 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -49,6 +49,37 @@ enum evdev_key_type { EVDEV_KEY_TYPE_BUTTON, }; +enum evdev_device_udev_tags { + EVDEV_UDEV_TAG_INPUT = (1 << 0), + EVDEV_UDEV_TAG_KEYBOARD = (1 << 1), + EVDEV_UDEV_TAG_MOUSE = (1 << 2), + EVDEV_UDEV_TAG_TOUCHPAD = (1 << 3), + EVDEV_UDEV_TAG_TOUCHSCREEN = (1 << 4), + EVDEV_UDEV_TAG_TABLET = (1 << 5), + EVDEV_UDEV_TAG_JOYSTICK = (1 << 6), + EVDEV_UDEV_TAG_ACCELEROMETER = (1 << 7), +}; + +struct evdev_udev_tag_match { + const char *name; + enum evdev_device_udev_tags tag; +}; + +static const struct evdev_udev_tag_match evdev_udev_tag_matches[] = { + {"ID_INPUT", EVDEV_UDEV_TAG_INPUT}, + {"ID_INPUT_KEYBOARD", EVDEV_UDEV_TAG_KEYBOARD}, + {"ID_INPUT_KEY", EVDEV_UDEV_TAG_KEYBOARD}, + {"ID_INPUT_MOUSE", EVDEV_UDEV_TAG_MOUSE}, + {"ID_INPUT_TOUCHPAD", EVDEV_UDEV_TAG_TOUCHPAD}, + {"ID_INPUT_TOUCHSCREEN", EVDEV_UDEV_TAG_TOUCHSCREEN}, + {"ID_INPUT_TABLET", EVDEV_UDEV_TAG_TABLET}, + {"ID_INPUT_JOYSTICK", EVDEV_UDEV_TAG_JOYSTICK}, + {"ID_INPUT_ACCELEROMETER", EVDEV_UDEV_TAG_ACCELEROMETER}, + + /* sentinel value */ + { 0 }, +}; + static void hw_set_key_down(struct evdev_device *device, int code, int pressed) { @@ -1229,10 +1260,12 @@ evdev_device_init_pointer_acceleration(struct evdev_device *device) device->pointer.config.get_default_speed = evdev_accel_config_get_default_speed; device->base.config.accel = &device->pointer.config; + evdev_accel_config_set_speed(&device->base, + evdev_accel_config_get_default_speed(&device->base)); + return 0; } - static inline int evdev_need_mtdev(struct evdev_device *device) { @@ -1317,38 +1350,62 @@ evdev_fix_abs_resolution(struct libevdev *evdev, } } +static enum evdev_device_udev_tags +evdev_device_get_udev_tags(struct evdev_device *device, + struct udev_device *udev_device) +{ + const char *prop; + enum evdev_device_udev_tags tags = 0; + const struct evdev_udev_tag_match *match = evdev_udev_tag_matches; + + while (match->name) { + prop = udev_device_get_property_value(device->udev_device, + match->name); + if (prop) + tags |= match->tag; + + match++; + } + return tags; +} + static int evdev_configure_device(struct evdev_device *device) { struct libinput *libinput = device->base.seat->libinput; struct libevdev *evdev = device->evdev; const struct input_absinfo *absinfo; - int has_abs, has_rel, has_mt; - int has_button, has_keyboard, has_touch, has_joystick_button; struct mt_slot *slots; int num_slots; int active_slot; int slot; - unsigned int i; const char *devnode = udev_device_get_devnode(device->udev_device); + enum evdev_device_udev_tags udev_tags; - has_rel = 0; - has_abs = 0; - has_mt = 0; - has_button = 0; - has_joystick_button = 0; - has_keyboard = 0; - has_touch = 0; + udev_tags = evdev_device_get_udev_tags(device, device->udev_device); - for (i = BTN_JOYSTICK; i <= BTN_PINKIE; i++) - if (libevdev_has_event_code(evdev, EV_KEY, i)) - has_joystick_button = 1; + if ((udev_tags & EVDEV_UDEV_TAG_INPUT) == 0 || + (udev_tags & ~EVDEV_UDEV_TAG_INPUT) == 0) { + log_info(libinput, + "input device '%s', %s not tagged as input device\n", + device->devname, devnode); + return -1; + } - for (i = BTN_GAMEPAD; i <= BTN_TR2; i++) - if (libevdev_has_event_code(evdev, EV_KEY, i)) - has_joystick_button = 1; + log_info(libinput, + "input device '%s', %s is tagged by udev as:%s%s%s%s%s%s\n", + device->devname, devnode, + udev_tags & EVDEV_UDEV_TAG_KEYBOARD ? " Keyboard" : "", + udev_tags & EVDEV_UDEV_TAG_MOUSE ? " Mouse" : "", + udev_tags & EVDEV_UDEV_TAG_TOUCHPAD ? " Touchpad" : "", + udev_tags & EVDEV_UDEV_TAG_TOUCHSCREEN ? " Touchscreen" : "", + udev_tags & EVDEV_UDEV_TAG_TABLET ? " Tablet" : "", + udev_tags & EVDEV_UDEV_TAG_JOYSTICK ? " Joystick" : "", + udev_tags & EVDEV_UDEV_TAG_ACCELEROMETER ? " Accelerometer" : ""); - if (has_joystick_button) { + /* libwacom *adds* TABLET, TOUCHPAD but leaves JOYSTICK in place, so + make sure we only ignore real joystick devices */ + if ((udev_tags & EVDEV_UDEV_TAG_JOYSTICK) == udev_tags) { log_info(libinput, "input device '%s', %s is a joystick, ignoring\n", device->devname, devnode); @@ -1363,7 +1420,6 @@ evdev_configure_device(struct evdev_device *device) absinfo)) device->abs.fake_resolution = 1; device->abs.absinfo_x = absinfo; - has_abs = 1; } if ((absinfo = libevdev_get_abs_info(evdev, ABS_Y))) { if (evdev_fix_abs_resolution(evdev, @@ -1371,7 +1427,6 @@ evdev_configure_device(struct evdev_device *device) absinfo)) device->abs.fake_resolution = 1; device->abs.absinfo_y = absinfo; - has_abs = 1; } /* Fake MT devices have the ABS_MT_SLOT bit set because of @@ -1379,8 +1434,7 @@ evdev_configure_device(struct evdev_device *device) just have too many ABS_ axes */ if (libevdev_has_event_code(evdev, EV_ABS, ABS_MT_SLOT) && libevdev_get_num_slots(evdev) == -1) { - has_mt = 0; - has_touch = 0; + udev_tags &= ~EVDEV_UDEV_TAG_TOUCHSCREEN; } else if (libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) && libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y)) { absinfo = libevdev_get_abs_info(evdev, ABS_MT_POSITION_X); @@ -1397,8 +1451,6 @@ evdev_configure_device(struct evdev_device *device) device->abs.fake_resolution = 1; device->abs.absinfo_y = absinfo; device->is_mt = 1; - has_touch = 1; - has_mt = 1; /* We only handle the slotted Protocol B in libinput. Devices with ABS_MT_POSITION_* but not ABS_MT_SLOT @@ -1433,83 +1485,55 @@ evdev_configure_device(struct evdev_device *device) } } - if (libevdev_has_event_code(evdev, EV_REL, REL_X) || - libevdev_has_event_code(evdev, EV_REL, REL_Y)) - has_rel = 1; - - if (libevdev_has_event_type(evdev, EV_KEY)) { - if (!libevdev_has_property(evdev, INPUT_PROP_DIRECT) && - libevdev_has_event_code(evdev, EV_KEY, BTN_TOOL_FINGER) && - !libevdev_has_event_code(evdev, EV_KEY, BTN_TOOL_PEN) && - (has_abs || has_mt)) { - device->dispatch = evdev_mt_touchpad_create(device); - log_info(libinput, - "input device '%s', %s is a touchpad\n", - device->devname, devnode); - return device->dispatch == NULL ? -1 : 0; - } else if (!libevdev_has_event_code(device->evdev, EV_KEY, BTN_TOOL_FINGER) && - libevdev_has_event_code(device->evdev, EV_KEY, BTN_TOOL_PEN) && - has_abs) { - device->dispatch = evdev_tablet_create(device); - device->seat_caps |= EVDEV_DEVICE_TABLET; - log_info(libinput, - "input device '%s', %s is a tablet\n", - device->devname, devnode); - return device->dispatch == NULL ? -1 : 0; - } - - for (i = 0; i < KEY_MAX; i++) { - if (libevdev_has_event_code(evdev, EV_KEY, i)) { - switch (get_key_type(i)) { - case EVDEV_KEY_TYPE_NONE: - break; - case EVDEV_KEY_TYPE_KEY: - has_keyboard = 1; - break; - case EVDEV_KEY_TYPE_BUTTON: - has_button = 1; - break; - } - } - } - - if (libevdev_has_event_code(evdev, EV_KEY, BTN_TOUCH)) - has_touch = 1; + /* libwacom assigns touchpad _and_ tablet to the tablet touch bits, + so make sure we don't initialize the tablet interface for the + touch device */ + if ((udev_tags & (EVDEV_UDEV_TAG_TABLET|EVDEV_UDEV_TAG_TOUCHPAD)) == + EVDEV_UDEV_TAG_TABLET) { + device->dispatch = evdev_tablet_create(device); + device->seat_caps |= EVDEV_DEVICE_TABLET; + log_info(libinput, + "input device '%s', %s is a tablet\n", + device->devname, devnode); + return device->dispatch == NULL ? -1 : 0; } - if (libevdev_has_event_type(evdev, EV_LED)) - has_keyboard = 1; - if ((has_abs || has_rel) && has_button) { - if (evdev_device_init_pointer_acceleration(device) == -1) + if (udev_tags & EVDEV_UDEV_TAG_TOUCHPAD) { + device->dispatch = evdev_mt_touchpad_create(device); + log_info(libinput, + "input device '%s', %s is a touchpad\n", + device->devname, devnode); + return device->dispatch == NULL ? -1 : 0; + } + + if (udev_tags & EVDEV_UDEV_TAG_MOUSE) { + if (!libevdev_has_event_code(evdev, EV_ABS, ABS_X) && + !libevdev_has_event_code(evdev, EV_ABS, ABS_Y) && + evdev_device_init_pointer_acceleration(device) == -1) return -1; device->seat_caps |= EVDEV_DEVICE_POINTER; log_info(libinput, - "input device '%s', %s is a pointer caps =%s%s%s\n", - device->devname, devnode, - has_abs ? " absolute-motion" : "", - has_rel ? " relative-motion": "", - has_button ? " button" : ""); + "input device '%s', %s is a pointer caps\n", + device->devname, devnode); /* want left-handed config option */ device->left_handed.want_enabled = true; /* want natural-scroll config option */ device->scroll.natural_scrolling_enabled = true; - } - - if (has_rel && has_button) { /* want button scrolling config option */ device->scroll.want_button = 1; } - if (has_keyboard) { + if (udev_tags & EVDEV_UDEV_TAG_KEYBOARD) { device->seat_caps |= EVDEV_DEVICE_KEYBOARD; log_info(libinput, "input device '%s', %s is a keyboard\n", device->devname, devnode); } - if (has_touch && !has_button) { + + if (udev_tags & EVDEV_UDEV_TAG_TOUCHSCREEN) { device->seat_caps |= EVDEV_DEVICE_TOUCH; log_info(libinput, "input device '%s', %s is a touch device\n", @@ -1578,6 +1602,7 @@ evdev_device_create(struct libinput_seat *seat, int fd; int unhandled_device = 0; const char *devnode = udev_device_get_devnode(udev_device); + struct libinput_device_group *group; /* Use non-blocking mode so that we can loop on read on * evdev_device_data() until all events on the fd are @@ -1648,6 +1673,12 @@ evdev_device_create(struct libinput_seat *seat, if (!device->source) goto err; + group = libinput_device_group_create(); + if (!group) + goto err; + libinput_device_set_device_group(&device->base, group); + libinput_device_group_unref(group); + list_insert(seat->devices_list.prev, &device->base.link); evdev_tag_device(device); @@ -2131,6 +2162,9 @@ evdev_device_destroy(struct evdev_device *device) if (dispatch) dispatch->interface->destroy(dispatch); + if (device->base.group) + libinput_device_group_unref(device->base.group); + filter_destroy(device->pointer.filter); libinput_seat_unref(device->base.seat); libevdev_free(device->evdev); diff --git a/src/filter.c b/src/filter.c index 89390d12..72ef7609 100644 --- a/src/filter.c +++ b/src/filter.c @@ -286,7 +286,7 @@ create_pointer_accelerator_filter(accel_profile_func_t profile) { struct pointer_accelerator *filter; - filter = malloc(sizeof *filter); + filter = zalloc(sizeof *filter); if (filter == NULL) return NULL; diff --git a/src/libinput-private.h b/src/libinput-private.h index 8f11d73d..7b4a0d6e 100644 --- a/src/libinput-private.h +++ b/src/libinput-private.h @@ -169,8 +169,14 @@ struct libinput_device_config { struct libinput_device_config_click_method *click_method; }; +struct libinput_device_group { + int refcount; + void *user_data; +}; + struct libinput_device { struct libinput_seat *seat; + struct libinput_device_group *group; struct list link; struct list event_listeners; void *user_data; @@ -253,6 +259,13 @@ void libinput_device_init(struct libinput_device *device, struct libinput_seat *seat); +struct libinput_device_group * +libinput_device_group_create(void); + +void +libinput_device_set_device_group(struct libinput_device *device, + struct libinput_device_group *group); + void libinput_device_add_event_listener(struct libinput_device *device, struct libinput_event_listener *listener, diff --git a/src/libinput.c b/src/libinput.c index 95d9c6c8..5e7514b2 100644 --- a/src/libinput.c +++ b/src/libinput.c @@ -715,7 +715,7 @@ libinput_add_fd(struct libinput *libinput, struct libinput_source *source; struct epoll_event ep; - source = malloc(sizeof *source); + source = zalloc(sizeof *source); if (!source) return NULL; @@ -1603,6 +1603,12 @@ libinput_device_get_context(struct libinput_device *device) return libinput_seat_get_context(device->seat); } +LIBINPUT_EXPORT struct libinput_device_group * +libinput_device_get_device_group(struct libinput_device *device) +{ + return device->group; +} + LIBINPUT_EXPORT const char * libinput_device_get_sysname(struct libinput_device *device) { @@ -1713,6 +1719,64 @@ libinput_event_touch_get_base_event(struct libinput_event_touch *event) return &event->base; } +LIBINPUT_EXPORT struct libinput_device_group * +libinput_device_group_ref(struct libinput_device_group *group) +{ + group->refcount++; + return group; +} + +struct libinput_device_group * +libinput_device_group_create(void) +{ + struct libinput_device_group *group; + + group = zalloc(sizeof *group); + if (group) + group->refcount = 1; + return group; +} + +void +libinput_device_set_device_group(struct libinput_device *device, + struct libinput_device_group *group) +{ + device->group = group; + libinput_device_group_ref(group); +} + +static void +libinput_device_group_destroy(struct libinput_device_group *group) +{ + free(group); +} + +LIBINPUT_EXPORT struct libinput_device_group * +libinput_device_group_unref(struct libinput_device_group *group) +{ + assert(group->refcount > 0); + group->refcount--; + if (group->refcount == 0) { + libinput_device_group_destroy(group); + return NULL; + } else { + return group; + } +} + +LIBINPUT_EXPORT void +libinput_device_group_set_user_data(struct libinput_device_group *group, + void *user_data) +{ + group->user_data = user_data; +} + +LIBINPUT_EXPORT void * +libinput_device_group_get_user_data(struct libinput_device_group *group) +{ + return group->user_data; +} + LIBINPUT_EXPORT const char * libinput_config_status_to_str(enum libinput_config_status status) { @@ -1860,7 +1924,8 @@ LIBINPUT_EXPORT enum libinput_config_status libinput_device_config_accel_set_speed(struct libinput_device *device, double speed) { - if (speed < -1.0 || speed > 1.0) + /* Need the negation in case speed is NaN */ + if (!(speed >= -1.0 && speed <= 1.0)) return LIBINPUT_CONFIG_STATUS_INVALID; if (!libinput_device_config_accel_is_available(device)) diff --git a/src/libinput.h b/src/libinput.h index 0cdda00d..ff49bb2f 100644 --- a/src/libinput.h +++ b/src/libinput.h @@ -255,6 +255,16 @@ struct libinput; */ struct libinput_device; +/** + * @ingroup device + * @struct libinput_device_group + * + * A base handle for accessing libinput device groups. This struct is + * refcounted, use libinput_device_group_ref() and + * libinput_device_group_unref(). + */ +struct libinput_device_group; + /** * @ingroup seat * @struct libinput_seat @@ -1686,7 +1696,7 @@ libinput_device_ref(struct libinput_device *device); * the device correctly to avoid dangling pointers. * * @param device A previously obtained device - * @return NULL if device was destroyed, otherwise the passed device + * @return NULL if the device was destroyed, otherwise the passed device */ struct libinput_device * libinput_device_unref(struct libinput_device *device); @@ -1728,6 +1738,66 @@ libinput_device_get_user_data(struct libinput_device *device); struct libinput * libinput_device_get_context(struct libinput_device *device); +/** + * @ingroup device + * + * Get the device group this device is assigned to. Some physical + * devices like graphics tablets are represented by multiple kernel + * devices and thus by multiple struct @ref libinput_device. + * + * libinput assigns these devices to the same @ref libinput_device_group + * allowing the caller to identify such devices and adjust configuration + * settings accordingly. For example, setting a tablet to left-handed often + * means turning it upside down. A touch device on the same tablet would + * need to be turned upside down too to work correctly. + * + * All devices are part of a device group though for most devices the group + * will be a singleton. A device is assigned to a device group on @ref + * LIBINPUT_EVENT_DEVICE_ADDED and removed from that group on @ref + * LIBINPUT_EVENT_DEVICE_REMOVED. It is up to the caller to track how many + * devices are in each device group. + * + * @dot + * digraph groups_libinput { + * rankdir="TB"; + * node [ + * shape="box"; + * ] + * + * mouse [ label="mouse"; URL="\ref libinput_device"]; + * kbd [ label="keyboard"; URL="\ref libinput_device"]; + * + * pen [ label="tablet pen"; URL="\ref libinput_device"]; + * touch [ label="tablet touch"; URL="\ref libinput_device"]; + * pad [ label="tablet pad"; URL="\ref libinput_device"]; + * + * group1 [ label="group 1"; URL="\ref libinput_device_group"]; + * group2 [ label="group 2"; URL="\ref libinput_device_group"]; + * group3 [ label="group 3"; URL="\ref libinput_device_group"]; + * + * mouse -> group1 + * kbd -> group2 + * + * pen -> group3; + * touch -> group3; + * pad -> group3; + * } + * @enddot + * + * Device groups do not get re-used once the last device in the group was + * removed, i.e. unplugging and re-plugging a physical device with grouped + * devices will return a different device group after every unplug. + * + * The returned device group is not refcounted and may become invalid after + * the next call to libinput. Use libinput_device_group_ref() and + * libinput_device_group_unref() to continue using the handle outside of the + * immediate scope. + * + * @return The device group this device belongs to + */ +struct libinput_device_group * +libinput_device_get_device_group(struct libinput_device *device); + /** * @ingroup device * @@ -1837,6 +1907,8 @@ libinput_device_set_seat_logical_name(struct libinput_device *device, const char *name); /** + * @ingroup device + * * Return a udev handle to the device that is this libinput device, if any. * The returned handle has a refcount of at least 1, the caller must call * udev_device_unref() once to release the associated resources. @@ -1913,6 +1985,63 @@ libinput_device_get_size(struct libinput_device *device, int libinput_device_has_button(struct libinput_device *device, uint32_t code); +/** + * @ingroup device + * + * Increase the refcount of the device group. A device group will be freed + * whenever the refcount reaches 0. This may happen during dispatch if all + * devices of this group were removed from the system. A caller must ensure + * to reference the device group correctly to avoid dangling pointers. + * + * @param group A previously obtained device group + * @return The passed device group + */ +struct libinput_device_group * +libinput_device_group_ref(struct libinput_device_group *group); + +/** + * @ingroup device + * + * Decrease the refcount of the device group. A device group will be freed + * whenever the refcount reaches 0. This may happen during dispatch if all + * devices of this group were removed from the system. A caller must ensure + * to reference the device group correctly to avoid dangling pointers. + * + * @param group A previously obtained device group + * @return NULL if the device group was destroyed, otherwise the passed + * device group + */ +struct libinput_device_group * +libinput_device_group_unref(struct libinput_device_group *group); + +/** + * @ingroup device + * + * Set caller-specific data associated with this device group. libinput does + * not manage, look at, or modify this data. The caller must ensure the + * data is valid. + * + * @param group A previously obtained device group + * @param user_data Caller-specific data pointer + * @see libinput_device_group_get_user_data + */ +void +libinput_device_group_set_user_data(struct libinput_device_group *group, + void *user_data); + +/** + * @ingroup device + * + * Get the caller-specific data associated with this input device group, if + * any. + * + * @param group A previously obtained group + * @return Caller-specific data pointer or NULL if none was set + * @see libinput_device_group_set_user_data + */ +void * +libinput_device_group_get_user_data(struct libinput_device_group *group); + /** * @defgroup config Device configuration * diff --git a/src/libinput.sym b/src/libinput.sym index 925f37c2..d235e186 100644 --- a/src/libinput.sym +++ b/src/libinput.sym @@ -147,3 +147,11 @@ global: libinput_tool_set_user_data; libinput_tool_unref; } LIBINPUT_0.8.0; + +LIBINPUT_0.11.0 { + libinput_device_get_device_group; + libinput_device_group_get_user_data; + libinput_device_group_ref; + libinput_device_group_set_user_data; + libinput_device_group_unref; +} LIBINPUT_0.9.0; diff --git a/src/path.c b/src/path.c index dd70efc6..832a1fd8 100644 --- a/src/path.c +++ b/src/path.c @@ -291,14 +291,34 @@ libinput_path_create_context(const struct libinput_interface *interface, } static inline struct udev_device * -udev_device_from_devnode(struct udev *udev, const char *devnode) +udev_device_from_devnode(struct libinput *libinput, + struct udev *udev, + const char *devnode) { + struct udev_device *dev; struct stat st; + size_t count = 0; if (stat(devnode, &st) < 0) return NULL; - return udev_device_new_from_devnum(udev, 'c', st.st_rdev); + dev = udev_device_new_from_devnum(udev, 'c', st.st_rdev); + + while (dev && !udev_device_get_is_initialized(dev)) { + udev_device_unref(dev); + msleep(10); + dev = udev_device_new_from_devnum(udev, 'c', st.st_rdev); + + count++; + if (count > 10) { + log_bug_libinput(libinput, + "udev device never initialized (%s)\n", + devnode); + break; + } + } + + return dev; } LIBINPUT_EXPORT struct libinput_device * @@ -315,7 +335,7 @@ libinput_path_add_device(struct libinput *libinput, return NULL; } - udev_device = udev_device_from_devnode(udev, path); + udev_device = udev_device_from_devnode(libinput, udev, path); if (!udev_device) { log_bug_client(libinput, "Invalid path %s\n", path); return NULL; diff --git a/test/device.c b/test/device.c index be3fff34..b7a23e69 100644 --- a/test/device.c +++ b/test/device.c @@ -661,6 +661,54 @@ START_TEST(device_context) } END_TEST +START_TEST(device_group_get) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device_group *group; + + int userdata = 10; + + group = libinput_device_get_device_group(dev->libinput_device); + ck_assert_notnull(group); + + libinput_device_group_ref(group); + + libinput_device_group_set_user_data(group, &userdata); + ck_assert_ptr_eq(&userdata, + libinput_device_group_get_user_data(group)); + + libinput_device_group_unref(group); +} +END_TEST + +START_TEST(device_group_ref) +{ + struct libinput *li = litest_create_context(); + struct litest_device *dev = litest_add_device(li, + LITEST_MOUSE); + struct libinput_device *device = dev->libinput_device; + struct libinput_device_group *group; + + group = libinput_device_get_device_group(device); + ck_assert_notnull(group); + libinput_device_group_ref(group); + + libinput_device_ref(device); + litest_drain_events(li); + litest_delete_device(dev); + litest_drain_events(li); + + /* make sure the device is dead but the group is still around */ + ck_assert(libinput_device_unref(device) == NULL); + + libinput_device_group_ref(group); + ck_assert_notnull(libinput_device_group_unref(group)); + ck_assert(libinput_device_group_unref(group) == NULL); + + libinput_unref(li); +} +END_TEST + int main (int argc, char **argv) { litest_add("device:sendevents", device_sendevents_config, LITEST_ANY, LITEST_TOUCHPAD|LITEST_TABLET); @@ -687,5 +735,8 @@ int main (int argc, char **argv) litest_add("device:udev", device_get_udev_handle, LITEST_ANY, LITEST_ANY); + litest_add("device:group", device_group_get, LITEST_ANY, LITEST_ANY); + litest_add_no_device("device:group", device_group_ref); + return litest_run(argc, argv); } diff --git a/test/litest-int.h b/test/litest-int.h index d989b2f0..5a55d6dc 100644 --- a/test/litest-int.h +++ b/test/litest-int.h @@ -69,6 +69,8 @@ struct litest_test_device { */ struct input_absinfo *absinfo; struct litest_device_interface *interface; + + const char *udev_rule; }; struct litest_device_interface { diff --git a/test/litest-synaptics-x1-carbon-3rd.c b/test/litest-synaptics-x1-carbon-3rd.c index 8be29e03..67d6f467 100644 --- a/test/litest-synaptics-x1-carbon-3rd.c +++ b/test/litest-synaptics-x1-carbon-3rd.c @@ -65,7 +65,6 @@ static struct input_id input_id = { .bustype = 0x11, .vendor = 0x2, .product = 0x7, - .version = 0xfffa, /* Magic value, used to detect this test device */ }; static int events[] = { @@ -97,6 +96,16 @@ static struct input_absinfo absinfo[] = { { .value = -1 } }; +static const char udev_rule[] = +"ACTION==\"remove\", GOTO=\"touchpad_end\"\n" +"KERNEL!=\"event*\", GOTO=\"touchpad_end\"\n" +"ENV{ID_INPUT_TOUCHPAD}==\"\", GOTO=\"touchpad_end\"\n" +"\n" +"ATTRS{name}==\"litest*X1C3rd*\",\\\n" +" ENV{TOUCHPAD_HAS_TRACKPOINT_BUTTONS}=\"1\"\n" +"\n" +"LABEL=\"touchpad_end\""; + struct litest_test_device litest_synaptics_carbon3rd_device = { .type = LITEST_SYNAPTICS_TRACKPOINT_BUTTONS, .features = LITEST_TOUCHPAD | LITEST_CLICKPAD | LITEST_BUTTON, @@ -104,8 +113,9 @@ struct litest_test_device litest_synaptics_carbon3rd_device = { .setup = litest_synaptics_carbon3rd_setup, .interface = &interface, - .name = "SynPS/2 Synaptics TouchPad", + .name = "SynPS/2 Synaptics TouchPad X1C3rd", .id = &input_id, .events = events, .absinfo = absinfo, + .udev_rule = udev_rule, }; diff --git a/test/litest.c b/test/litest.c index 631f6a16..73228b47 100644 --- a/test/litest.c +++ b/test/litest.c @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -44,6 +45,9 @@ #include "litest-int.h" #include "libinput-util.h" +#define UDEV_RULES_D "/run/udev/rules.d" +#define UDEV_RULE_PREFIX "99-litest-" + static int in_debugger = -1; static int verbose = 0; @@ -122,6 +126,55 @@ struct litest_test_device* devices[] = { static struct list all_tests; +static void +litest_reload_udev_rules(void) +{ + system("udevadm control --reload-rules"); +} + +static int +litest_udev_rule_filter(const struct dirent *entry) +{ + return strncmp(entry->d_name, + UDEV_RULE_PREFIX, + strlen(UDEV_RULE_PREFIX)) == 0; +} + +static void +litest_drop_udev_rules(void) +{ + int n; + int rc; + struct dirent **entries; + char path[PATH_MAX]; + + n = scandir(UDEV_RULES_D, + &entries, + litest_udev_rule_filter, + alphasort); + if (n < 0) + return; + + while (n--) { + rc = snprintf(path, sizeof(path), + "%s/%s", + UDEV_RULES_D, + entries[n]->d_name); + if (rc > 0 && + (size_t)rc == strlen(UDEV_RULES_D) + + strlen(entries[n]->d_name) + 1) + unlink(path); + else + fprintf(stderr, + "Failed to delete %s. Remaining tests are unreliable\n", + entries[n]->d_name); + free(entries[n]); + } + free(entries); + + litest_reload_udev_rules(); +} + static void litest_add_tcase_for_device(struct suite *suite, void *func, @@ -142,6 +195,13 @@ litest_add_tcase_for_device(struct suite *suite, t->name = strdup(test_name); t->tc = tcase_create(test_name); list_insert(&suite->tests, &t->node); + /* we can't guarantee that we clean up properly if a test fails, the + udev rules used for a previous test may still be in place. Add an + unchecked fixture to always clean up all rules before/after a + test case completes */ + tcase_add_unchecked_fixture(t->tc, + litest_drop_udev_rules, + litest_drop_udev_rules); tcase_add_checked_fixture(t->tc, dev->setup, dev->teardown ? dev->teardown : litest_generic_device_teardown); tcase_add_test(t->tc, func); @@ -472,6 +532,40 @@ merge_events(const int *orig, const int *override) return events; } +static char * +litest_init_udev_rules(struct litest_test_device *dev) +{ + int rc; + FILE *f; + char *path; + + if (!dev->udev_rule) + return NULL; + + rc = mkdir(UDEV_RULES_D, 0755); + if (rc == -1 && errno != EEXIST) + ck_abort_msg("Failed to create udev rules directory (%s)\n", + strerror(errno)); + + rc = asprintf(&path, + "%s/%s%s.rules", + UDEV_RULES_D, + UDEV_RULE_PREFIX, + dev->shortname); + ck_assert_int_eq(rc, + strlen(UDEV_RULES_D) + + strlen(UDEV_RULE_PREFIX) + + strlen(dev->shortname) + 7); + f = fopen(path, "w"); + ck_assert_notnull(f); + ck_assert_int_ge(fputs(dev->udev_rule, f), 0); + fclose(f); + + litest_reload_udev_rules(); + + return path; +} + static struct litest_device * litest_create(enum litest_device_type which, const char *name_override, @@ -485,6 +579,7 @@ litest_create(enum litest_device_type which, const struct input_id *id; struct input_absinfo *abs; int *events; + char *udev_file; dev = devices; while (*dev) { @@ -499,12 +594,17 @@ litest_create(enum litest_device_type which, d = zalloc(sizeof(*d)); ck_assert(d != NULL); + udev_file = litest_init_udev_rules(*dev); + /* device has custom create method */ if ((*dev)->create) { (*dev)->create(d); - if (abs_override || events_override) + if (abs_override || events_override) { + if (udev_file) + unlink(udev_file); ck_abort_msg("Custom create cannot" "be overridden"); + } return d; } @@ -519,6 +619,7 @@ litest_create(enum litest_device_type which, abs, events); d->interface = (*dev)->interface; + d->udev_rule_file = udev_file; free(abs); free(events); @@ -637,6 +738,12 @@ litest_delete_device(struct litest_device *d) if (!d) return; + if (d->udev_rule_file) { + unlink(d->udev_rule_file); + free(d->udev_rule_file); + d->udev_rule_file = NULL; + } + libinput_device_unref(d->libinput_device); libinput_path_remove_device(d->libinput_device); if (d->owns_context) diff --git a/test/litest.h b/test/litest.h index cb4a04ff..4553c878 100644 --- a/test/litest.h +++ b/test/litest.h @@ -91,6 +91,8 @@ struct litest_device { bool skip_ev_syn; void *private; /* device-specific data */ + + char *udev_rule_file; }; struct libinput *litest_create_context(void); diff --git a/test/misc.c b/test/misc.c index 8036eaf2..ccb5c840 100644 --- a/test/misc.c +++ b/test/misc.c @@ -145,30 +145,20 @@ END_TEST START_TEST(event_conversion_pointer) { - struct libevdev_uinput *uinput; - struct libinput *li; + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; struct libinput_event *event; int motion = 0, button = 0; - uinput = create_simple_test_device("litest test device", - EV_REL, REL_X, - EV_REL, REL_Y, - EV_KEY, BTN_LEFT, - EV_KEY, BTN_MIDDLE, - EV_KEY, BTN_LEFT, - -1, -1); - li = libinput_path_create_context(&simple_interface, NULL); - libinput_path_add_device(li, libevdev_uinput_get_devnode(uinput)); - /* Queue at least two relative motion events as the first one may * be absorbed by the pointer acceleration filter. */ - libevdev_uinput_write_event(uinput, EV_REL, REL_X, -1); - libevdev_uinput_write_event(uinput, EV_REL, REL_Y, -1); - libevdev_uinput_write_event(uinput, EV_SYN, SYN_REPORT, 0); - libevdev_uinput_write_event(uinput, EV_REL, REL_X, -1); - libevdev_uinput_write_event(uinput, EV_REL, REL_Y, -1); - libevdev_uinput_write_event(uinput, EV_KEY, BTN_LEFT, 1); - libevdev_uinput_write_event(uinput, EV_SYN, SYN_REPORT, 0); + litest_event(dev, EV_REL, REL_X, -1); + litest_event(dev, EV_REL, REL_Y, -1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_event(dev, EV_REL, REL_X, -1); + litest_event(dev, EV_REL, REL_Y, -1); + litest_event(dev, EV_KEY, BTN_LEFT, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); libinput_dispatch(li); @@ -196,9 +186,6 @@ START_TEST(event_conversion_pointer) libinput_event_destroy(event); } - libinput_unref(li); - libevdev_uinput_destroy(uinput); - ck_assert_int_gt(motion, 0); ck_assert_int_gt(button, 0); } @@ -206,29 +193,18 @@ END_TEST START_TEST(event_conversion_pointer_abs) { - struct libevdev_uinput *uinput; - struct libinput *li; + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; struct libinput_event *event; int motion = 0, button = 0; - uinput = create_simple_test_device("litest test device", - EV_ABS, ABS_X, - EV_ABS, ABS_Y, - EV_KEY, BTN_LEFT, - EV_KEY, BTN_MIDDLE, - EV_KEY, BTN_LEFT, - -1, -1); - li = libinput_path_create_context(&simple_interface, NULL); - libinput_path_add_device(li, libevdev_uinput_get_devnode(uinput)); - libinput_dispatch(li); - - libevdev_uinput_write_event(uinput, EV_ABS, ABS_X, 10); - libevdev_uinput_write_event(uinput, EV_ABS, ABS_Y, 50); - libevdev_uinput_write_event(uinput, EV_KEY, BTN_LEFT, 1); - libevdev_uinput_write_event(uinput, EV_SYN, SYN_REPORT, 0); - libevdev_uinput_write_event(uinput, EV_ABS, ABS_X, 30); - libevdev_uinput_write_event(uinput, EV_ABS, ABS_Y, 30); - libevdev_uinput_write_event(uinput, EV_SYN, SYN_REPORT, 0); + litest_event(dev, EV_ABS, ABS_X, 10); + litest_event(dev, EV_ABS, ABS_Y, 50); + litest_event(dev, EV_KEY, BTN_LEFT, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_event(dev, EV_ABS, ABS_X, 30); + litest_event(dev, EV_ABS, ABS_Y, 30); + litest_event(dev, EV_SYN, SYN_REPORT, 0); libinput_dispatch(li); @@ -256,9 +232,6 @@ START_TEST(event_conversion_pointer_abs) libinput_event_destroy(event); } - libinput_unref(li); - libevdev_uinput_destroy(uinput); - ck_assert_int_gt(motion, 0); ck_assert_int_gt(button, 0); } @@ -266,23 +239,15 @@ END_TEST START_TEST(event_conversion_key) { - struct libevdev_uinput *uinput; - struct libinput *li; + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; struct libinput_event *event; int key = 0; - uinput = create_simple_test_device("litest test device", - EV_KEY, KEY_A, - EV_KEY, KEY_B, - -1, -1); - li = libinput_path_create_context(&simple_interface, NULL); - libinput_path_add_device(li, libevdev_uinput_get_devnode(uinput)); - libinput_dispatch(li); - - libevdev_uinput_write_event(uinput, EV_KEY, KEY_A, 1); - libevdev_uinput_write_event(uinput, EV_SYN, SYN_REPORT, 0); - libevdev_uinput_write_event(uinput, EV_KEY, KEY_A, 0); - libevdev_uinput_write_event(uinput, EV_SYN, SYN_REPORT, 0); + litest_event(dev, EV_KEY, KEY_A, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_event(dev, EV_KEY, KEY_A, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); libinput_dispatch(li); @@ -306,42 +271,28 @@ START_TEST(event_conversion_key) libinput_event_destroy(event); } - libinput_unref(li); - libevdev_uinput_destroy(uinput); - ck_assert_int_gt(key, 0); } END_TEST START_TEST(event_conversion_touch) { - struct libevdev_uinput *uinput; - struct libinput *li; + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; struct libinput_event *event; int touch = 0; - uinput = create_simple_test_device("litest test device", - EV_KEY, BTN_TOUCH, - EV_ABS, ABS_X, - EV_ABS, ABS_Y, - EV_ABS, ABS_MT_SLOT, - EV_ABS, ABS_MT_TRACKING_ID, - EV_ABS, ABS_MT_POSITION_X, - EV_ABS, ABS_MT_POSITION_Y, - -1, -1); - li = libinput_path_create_context(&simple_interface, NULL); - libinput_path_add_device(li, libevdev_uinput_get_devnode(uinput)); libinput_dispatch(li); - libevdev_uinput_write_event(uinput, EV_KEY, BTN_TOOL_FINGER, 1); - libevdev_uinput_write_event(uinput, EV_KEY, BTN_TOUCH, 1); - libevdev_uinput_write_event(uinput, EV_ABS, ABS_X, 10); - libevdev_uinput_write_event(uinput, EV_ABS, ABS_Y, 10); - libevdev_uinput_write_event(uinput, EV_ABS, ABS_MT_SLOT, 0); - libevdev_uinput_write_event(uinput, EV_ABS, ABS_MT_TRACKING_ID, 1); - libevdev_uinput_write_event(uinput, EV_ABS, ABS_MT_POSITION_X, 10); - libevdev_uinput_write_event(uinput, EV_ABS, ABS_MT_POSITION_Y, 10); - libevdev_uinput_write_event(uinput, EV_SYN, SYN_REPORT, 0); + litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 1); + litest_event(dev, EV_KEY, BTN_TOUCH, 1); + litest_event(dev, EV_ABS, ABS_X, 10); + litest_event(dev, EV_ABS, ABS_Y, 10); + litest_event(dev, EV_ABS, ABS_MT_SLOT, 0); + litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, 1); + litest_event(dev, EV_ABS, ABS_MT_POSITION_X, 10); + litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, 10); + litest_event(dev, EV_SYN, SYN_REPORT, 0); libinput_dispatch(li); @@ -366,9 +317,6 @@ START_TEST(event_conversion_touch) libinput_event_destroy(event); } - libinput_unref(li); - libevdev_uinput_destroy(uinput); - ck_assert_int_gt(touch, 0); } END_TEST @@ -639,11 +587,13 @@ END_TEST int main (int argc, char **argv) { litest_add_no_device("events:conversion", event_conversion_device_notify); - litest_add_no_device("events:conversion", event_conversion_pointer); - litest_add_no_device("events:conversion", event_conversion_pointer_abs); - litest_add_no_device("events:conversion", event_conversion_key); - litest_add_no_device("events:conversion", event_conversion_touch); + litest_add_for_device("events:conversion", event_conversion_pointer, LITEST_MOUSE); + litest_add_for_device("events:conversion", event_conversion_pointer, LITEST_MOUSE); + litest_add_for_device("events:conversion", event_conversion_pointer_abs, LITEST_XEN_VIRTUAL_POINTER); + litest_add_for_device("events:conversion", event_conversion_key, LITEST_KEYBOARD); + litest_add_for_device("events:conversion", event_conversion_touch, LITEST_WACOM_TOUCH); litest_add_no_device("bitfield_helpers", bitfield_helpers); + litest_add_no_device("context:refcount", context_ref_counting); litest_add_no_device("config:status string", config_status_string); diff --git a/test/pointer.c b/test/pointer.c index 45e0d57e..2e52a206 100644 --- a/test/pointer.c +++ b/test/pointer.c @@ -732,6 +732,86 @@ START_TEST(pointer_scroll_button) } END_TEST +START_TEST(pointer_accel_defaults) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device *device = dev->libinput_device; + enum libinput_config_status status; + double speed; + + ck_assert(libinput_device_config_accel_is_available(device)); + ck_assert(libinput_device_config_accel_get_default_speed(device) == 0.0); + ck_assert(libinput_device_config_accel_get_speed(device) == 0.0); + + for (speed = -2.0; speed < -1.0; speed += 0.2) { + status = libinput_device_config_accel_set_speed(device, + speed); + ck_assert_int_eq(status, + LIBINPUT_CONFIG_STATUS_INVALID); + ck_assert(libinput_device_config_accel_get_speed(device) == 0.0); + } + + for (speed = -1.0; speed <= 1.0; speed += 0.2) { + status = libinput_device_config_accel_set_speed(device, + speed); + ck_assert_int_eq(status, + LIBINPUT_CONFIG_STATUS_SUCCESS); + ck_assert(libinput_device_config_accel_get_speed(device) == speed); + } + + for (speed = 1.2; speed <= -2.0; speed += 0.2) { + status = libinput_device_config_accel_set_speed(device, + speed); + ck_assert_int_eq(status, + LIBINPUT_CONFIG_STATUS_INVALID); + ck_assert(libinput_device_config_accel_get_speed(device) == 1.0); + } + +} +END_TEST + +START_TEST(pointer_accel_invalid) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device *device = dev->libinput_device; + enum libinput_config_status status; + + ck_assert(libinput_device_config_accel_is_available(device)); + + status = libinput_device_config_accel_set_speed(device, + NAN); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID); + status = libinput_device_config_accel_set_speed(device, + INFINITY); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID); +} +END_TEST + +START_TEST(pointer_accel_defaults_absolute) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device *device = dev->libinput_device; + enum libinput_config_status status; + double speed; + + ck_assert(!libinput_device_config_accel_is_available(device)); + ck_assert(libinput_device_config_accel_get_default_speed(device) == 0.0); + ck_assert(libinput_device_config_accel_get_speed(device) == 0.0); + + for (speed = -2.0; speed <= 2.0; speed += 0.2) { + status = libinput_device_config_accel_set_speed(device, + speed); + if (speed >= -1.0 && speed <= 1.0) + ck_assert_int_eq(status, + LIBINPUT_CONFIG_STATUS_UNSUPPORTED); + else + ck_assert_int_eq(status, + LIBINPUT_CONFIG_STATUS_INVALID); + ck_assert(libinput_device_config_accel_get_speed(device) == 0.0); + } +} +END_TEST + int main (int argc, char **argv) { litest_add("pointer:motion", pointer_motion_relative, LITEST_RELATIVE, LITEST_ANY); @@ -754,5 +834,9 @@ int main (int argc, char **argv) { litest_add("pointer:left-handed", pointer_left_handed_during_click, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); litest_add("pointer:left-handed", pointer_left_handed_during_click_multiple_buttons, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:accel", pointer_accel_defaults, LITEST_RELATIVE, LITEST_ANY); + litest_add("pointer:accel", pointer_accel_invalid, LITEST_RELATIVE, LITEST_ANY); + litest_add("pointer:accel", pointer_accel_defaults_absolute, LITEST_ABSOLUTE, LITEST_ANY); + return litest_run(argc, argv); }