libinput/test/litest.c
Peter Hutterer bc9f16b40e COPYING: Update boilerplate from MIT X11 to MIT Expat license
To quote Bryce Harrington from [1]:
"MIT has released software under several slightly different licenses,
including the old 'X11 License' or 'MIT License'.  Some code under this
license was in fact included in X.org's Xserver in the past.  However,
X.org now prefers the MIT Expat License as the standard (which,
confusingly, is also referred to as the 'MIT License').  See
http://cgit.freedesktop.org/xorg/xserver/tree/COPYING

When Wayland started, it was Kristian Høgsberg's intent to license it
compatibly with X.org.  "I wanted Wayland to be usable (license-wise)
whereever X was usable."  But, the text of the older X11 License was
taken for Wayland, rather than X11's current standard.  This patch
corrects this by swapping in the intended text."

libinput is a fork of weston and thus inherited the original license intent
and the license boilerplate itself.

See this thread on wayland-devel here for a discussion:
http://lists.freedesktop.org/archives/wayland-devel/2015-May/022301.html

[1] http://lists.freedesktop.org/archives/wayland-devel/2015-June/022552.html

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Acked-by: Benjamin Tissoires <benjamin.tissoires@gmail.com>
Acked-by: Jonas Ådahl <jadahl@gmail.com>
2015-06-16 14:36:04 +10:00

2372 lines
53 KiB
C

/*
* Copyright © 2013 Red Hat, Inc.
* Copyright © 2013 Marcin Slusarz <marcin.slusarz@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#if HAVE_CONFIG_H
#include "config.h"
#endif
#include <assert.h>
#include <check.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <getopt.h>
#include <poll.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include "linux/input.h"
#include <sys/ptrace.h>
#include <sys/sendfile.h>
#include <sys/timerfd.h>
#include <sys/wait.h>
#include "litest.h"
#include "litest-int.h"
#include "libinput-util.h"
#define UDEV_RULES_D "/run/udev/rules.d"
#define UDEV_RULE_PREFIX "99-litest-"
#define UDEV_HWDB_D "/etc/udev/hwdb.d"
#define UDEV_COMMON_RULE_FILE UDEV_RULES_D "/91-litest-model-quirks.rules"
#define UDEV_COMMON_HWDB_FILE UDEV_HWDB_D "/91-litest-model-quirks-REMOVEME.hwdb"
static int in_debugger = -1;
static int verbose = 0;
const char *filter_test = NULL;
const char *filter_device = NULL;
const char *filter_group = NULL;
static inline void litest_remove_model_quirks(void);
/* defined for the litest selftest */
#ifndef LITEST_DISABLE_BACKTRACE_LOGGING
#define litest_log(...) fprintf(stderr, __VA_ARGS__)
#define litest_vlog(format_, args_) vfprintf(stderr, format_, args_)
#else
#define litest_log(...) { /* __VA_ARGS__ */ }
#define litest_vlog(...) { /* __VA_ARGS__ */ }
#endif
#ifdef HAVE_LIBUNWIND
#define UNW_LOCAL_ONLY
#include <libunwind.h>
#include <dlfcn.h>
static char cwd[PATH_MAX];
static bool
litest_backtrace_get_lineno(const char *executable,
unw_word_t addr,
char *file_return,
int *line_return)
{
#if HAVE_ADDR2LINE
FILE* f;
char buffer[PATH_MAX];
char *s;
unsigned int i;
if (!cwd[0]) {
if (getcwd(cwd, sizeof(cwd)) == NULL)
cwd[0] = 0; /* contents otherwise undefined. */
}
sprintf (buffer,
ADDR2LINE " -C -e %s -i %lx",
executable,
(unsigned long) addr);
f = popen(buffer, "r");
if (f == NULL) {
litest_log("Failed to execute: %s\n", buffer);
return false;
}
buffer[0] = '?';
if (fgets(buffer, sizeof(buffer), f) == NULL) {
pclose(f);
return false;
}
pclose(f);
if (buffer[0] == '?')
return false;
s = strrchr(buffer, ':');
if (!s)
return false;
*s = '\0';
s++;
sscanf(s, "%d", line_return);
/* now strip cwd from buffer */
s = buffer;
i = 0;
while(i < strlen(cwd) && *s != '\0' && cwd[i] == *s) {
*s = '\0';
s++;
i++;
}
if (i > 0)
*(--s) = '.';
strcpy(file_return, s);
return true;
#else /* HAVE_ADDR2LINE */
return false;
#endif
}
static void
litest_backtrace(void)
{
unw_cursor_t cursor;
unw_context_t context;
unw_word_t off;
unw_proc_info_t pip;
int ret;
char procname[256];
Dl_info dlinfo;
/* filename and i are unused ifdef LITEST_SHUTUP */
const char *filename __attribute__((unused));
int i __attribute__((unused)) = 0;
pip.unwind_info = NULL;
ret = unw_getcontext(&context);
if (ret) {
litest_log("unw_getcontext failed: %s [%d]\n",
unw_strerror(ret),
ret);
return;
}
ret = unw_init_local(&cursor, &context);
if (ret) {
litest_log("unw_init_local failed: %s [%d]\n",
unw_strerror(ret),
ret);
return;
}
litest_log("\nBacktrace:\n");
ret = unw_step(&cursor);
while (ret > 0) {
char file[PATH_MAX];
int line;
bool have_lineno = false;
ret = unw_get_proc_info(&cursor, &pip);
if (ret) {
litest_log("unw_get_proc_info failed: %s [%d]\n",
unw_strerror(ret),
ret);
break;
}
ret = unw_get_proc_name(&cursor, procname, 256, &off);
if (ret && ret != -UNW_ENOMEM) {
if (ret != -UNW_EUNSPEC)
litest_log("unw_get_proc_name failed: %s [%d]\n",
unw_strerror(ret),
ret);
procname[0] = '?';
procname[1] = 0;
}
if (dladdr((void *)(pip.start_ip + off), &dlinfo) &&
dlinfo.dli_fname &&
*dlinfo.dli_fname) {
filename = dlinfo.dli_fname;
have_lineno = litest_backtrace_get_lineno(filename,
(pip.start_ip + off),
file,
&line);
} else {
filename = "?";
}
if (have_lineno) {
litest_log("%u: %s() (%s:%d)\n",
i,
procname,
file,
line);
} else {
litest_log("%u: %s (%s%s+%#x) [%p]\n",
i,
filename,
procname,
ret == -UNW_ENOMEM ? "..." : "",
(int)off,
(void *)(pip.start_ip + off));
}
i++;
ret = unw_step(&cursor);
if (ret < 0)
litest_log("unw_step failed: %s [%d]\n",
unw_strerror(ret),
ret);
}
litest_log("\n");
}
#else /* HAVE_LIBUNWIND */
static inline void
litest_backtrace(void)
{
/* thou shall install libunwind */
}
#endif
void
litest_fail_condition(const char *file,
int line,
const char *func,
const char *condition,
const char *message,
...)
{
litest_log("FAILED: %s\n", condition);
if (message) {
va_list args;
va_start(args, message);
litest_vlog(message, args);
va_end(args);
}
litest_log("in %s() (%s:%d)\n", func, file, line);
litest_backtrace();
abort();
}
void
litest_fail_comparison_int(const char *file,
int line,
const char *func,
const char *operator,
int a,
int b,
const char *astr,
const char *bstr)
{
litest_log("FAILED COMPARISON: %s %s %s\n", astr, operator, bstr);
litest_log("Resolved to: %d %s %d\n", a, operator, b);
litest_log("in %s() (%s:%d)\n", func, file, line);
litest_backtrace();
abort();
}
void
litest_fail_comparison_ptr(const char *file,
int line,
const char *func,
const char *comparison)
{
litest_log("FAILED COMPARISON: %s\n", comparison);
litest_log("in %s() (%s:%d)\n", func, file, line);
litest_backtrace();
abort();
}
struct test {
struct list node;
char *name;
TCase *tc;
enum litest_device_type devices;
};
struct suite {
struct list node;
struct list tests;
char *name;
Suite *suite;
};
static struct litest_device *current_device;
struct litest_device *litest_current_device(void)
{
return current_device;
}
void litest_set_current_device(struct litest_device *device)
{
current_device = device;
}
void litest_generic_device_teardown(void)
{
litest_delete_device(current_device);
current_device = NULL;
}
extern struct litest_test_device litest_keyboard_device;
extern struct litest_test_device litest_synaptics_clickpad_device;
extern struct litest_test_device litest_synaptics_touchpad_device;
extern struct litest_test_device litest_synaptics_t440_device;
extern struct litest_test_device litest_trackpoint_device;
extern struct litest_test_device litest_bcm5974_device;
extern struct litest_test_device litest_mouse_device;
extern struct litest_test_device litest_wacom_touch_device;
extern struct litest_test_device litest_alps_device;
extern struct litest_test_device litest_generic_singletouch_device;
extern struct litest_test_device litest_qemu_tablet_device;
extern struct litest_test_device litest_xen_virtual_pointer_device;
extern struct litest_test_device litest_vmware_virtmouse_device;
extern struct litest_test_device litest_synaptics_hover_device;
extern struct litest_test_device litest_synaptics_carbon3rd_device;
extern struct litest_test_device litest_protocol_a_screen;
extern struct litest_test_device litest_wacom_finger_device;
extern struct litest_test_device litest_keyboard_blackwidow_device;
extern struct litest_test_device litest_wheel_only_device;
extern struct litest_test_device litest_mouse_roccat_device;
extern struct litest_test_device litest_ms_surface_cover_device;
extern struct litest_test_device litest_logitech_trackball_device;
extern struct litest_test_device litest_atmel_hover_device;
struct litest_test_device* devices[] = {
&litest_synaptics_clickpad_device,
&litest_synaptics_touchpad_device,
&litest_synaptics_t440_device,
&litest_keyboard_device,
&litest_trackpoint_device,
&litest_bcm5974_device,
&litest_mouse_device,
&litest_wacom_touch_device,
&litest_alps_device,
&litest_generic_singletouch_device,
&litest_qemu_tablet_device,
&litest_xen_virtual_pointer_device,
&litest_vmware_virtmouse_device,
&litest_synaptics_hover_device,
&litest_synaptics_carbon3rd_device,
&litest_protocol_a_screen,
&litest_wacom_finger_device,
&litest_keyboard_blackwidow_device,
&litest_wheel_only_device,
&litest_mouse_roccat_device,
&litest_ms_surface_cover_device,
&litest_logitech_trackball_device,
&litest_atmel_hover_device,
NULL,
};
static struct list all_tests;
static inline void
litest_system(const char *command)
{
int ret;
ret = system(command);
if (ret == -1) {
litest_abort_msg("Failed to execute: %s", command);
} else if (WIFEXITED(ret)) {
if (WEXITSTATUS(ret))
litest_abort_msg("'%s' failed with %d",
command,
WEXITSTATUS(ret));
} else if (WIFSIGNALED(ret)) {
litest_abort_msg("'%s' terminated with signal %d",
command,
WTERMSIG(ret));
}
}
static void
litest_reload_udev_rules(void)
{
litest_system("udevadm control --reload-rules");
litest_system("udevadm hwdb --update");
}
static int
litest_udev_rule_filter(const struct dirent *entry)
{
return strneq(entry->d_name,
UDEV_RULE_PREFIX,
strlen(UDEV_RULE_PREFIX));
}
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_remove_model_quirks();
litest_reload_udev_rules();
}
static void
litest_add_tcase_for_device(struct suite *suite,
const char *funcname,
void *func,
const struct litest_test_device *dev,
const struct range *range)
{
struct test *t;
const char *test_name = dev->shortname;
list_for_each(t, &suite->tests, node) {
if (!streq(t->name, test_name))
continue;
if (range)
tcase_add_loop_test(t->tc,
func,
range->lower,
range->upper);
else
tcase_add_test(t->tc, func);
return;
}
t = zalloc(sizeof(*t));
assert(t != NULL);
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);
suite_add_tcase(suite->suite, t->tc);
}
static void
litest_add_tcase_no_device(struct suite *suite,
void *func,
const struct range *range)
{
struct test *t;
const char *test_name = "no device";
if (filter_device &&
fnmatch(filter_device, test_name, 0) != 0)
return;
list_for_each(t, &suite->tests, node) {
if (!streq(t->name, test_name))
continue;
if (range)
tcase_add_loop_test(t->tc, func, range->lower, range->upper);
else
tcase_add_test(t->tc, func);
return;
}
t = zalloc(sizeof(*t));
assert(t != NULL);
t->name = strdup(test_name);
t->tc = tcase_create(test_name);
list_insert(&suite->tests, &t->node);
tcase_add_test(t->tc, func);
suite_add_tcase(suite->suite, t->tc);
}
static struct suite *
get_suite(const char *name)
{
struct suite *s;
if (all_tests.next == NULL && all_tests.prev == NULL)
list_init(&all_tests);
list_for_each(s, &all_tests, node) {
if (streq(s->name, name))
return s;
}
s = zalloc(sizeof(*s));
assert(s != NULL);
s->name = strdup(name);
s->suite = suite_create(s->name);
list_init(&s->tests);
list_insert(&all_tests, &s->node);
return s;
}
static void
litest_add_tcase(const char *suite_name,
const char *funcname,
void *func,
enum litest_device_feature required,
enum litest_device_feature excluded,
const struct range *range)
{
struct litest_test_device **dev = devices;
struct suite *suite;
assert(required >= LITEST_DISABLE_DEVICE);
assert(excluded >= LITEST_DISABLE_DEVICE);
if (filter_test &&
fnmatch(filter_test, funcname, 0) != 0)
return;
if (filter_group &&
fnmatch(filter_group, suite_name, 0) != 0)
return;
suite = get_suite(suite_name);
if (required == LITEST_DISABLE_DEVICE &&
excluded == LITEST_DISABLE_DEVICE) {
litest_add_tcase_no_device(suite, func, range);
} else if (required != LITEST_ANY || excluded != LITEST_ANY) {
for (; *dev; dev++) {
if (filter_device &&
fnmatch(filter_device, (*dev)->shortname, 0) != 0)
continue;
if (((*dev)->features & required) != required ||
((*dev)->features & excluded) != 0)
continue;
litest_add_tcase_for_device(suite,
funcname,
func,
*dev,
range);
}
} else {
for (; *dev; dev++) {
if (filter_device &&
fnmatch(filter_device, (*dev)->shortname, 0) != 0)
continue;
litest_add_tcase_for_device(suite,
funcname,
func,
*dev,
range);
}
}
}
void
_litest_add_no_device(const char *name, const char *funcname, void *func)
{
_litest_add(name, funcname, func, LITEST_DISABLE_DEVICE, LITEST_DISABLE_DEVICE);
}
void
_litest_add_ranged_no_device(const char *name,
const char *funcname,
void *func,
const struct range *range)
{
_litest_add_ranged(name,
funcname,
func,
LITEST_DISABLE_DEVICE,
LITEST_DISABLE_DEVICE,
range);
}
void
_litest_add(const char *name,
const char *funcname,
void *func,
enum litest_device_feature required,
enum litest_device_feature excluded)
{
_litest_add_ranged(name,
funcname,
func,
required,
excluded,
NULL);
}
void
_litest_add_ranged(const char *name,
const char *funcname,
void *func,
enum litest_device_feature required,
enum litest_device_feature excluded,
const struct range *range)
{
litest_add_tcase(name, funcname, func, required, excluded, range);
}
void
_litest_add_for_device(const char *name,
const char *funcname,
void *func,
enum litest_device_type type)
{
_litest_add_ranged_for_device(name, funcname, func, type, NULL);
}
void
_litest_add_ranged_for_device(const char *name,
const char *funcname,
void *func,
enum litest_device_type type,
const struct range *range)
{
struct suite *s;
struct litest_test_device **dev = devices;
bool device_filtered = false;
assert(type < LITEST_NO_DEVICE);
if (filter_test &&
fnmatch(filter_test, funcname, 0) != 0)
return;
if (filter_group &&
fnmatch(filter_group, name, 0) != 0)
return;
s = get_suite(name);
for (; *dev; dev++) {
if (filter_device &&
fnmatch(filter_device, (*dev)->shortname, 0) != 0) {
device_filtered = true;
continue;
}
if ((*dev)->type == type) {
litest_add_tcase_for_device(s,
funcname,
func,
*dev,
range);
return;
}
}
/* only abort if no filter was set, that's a bug */
if (!device_filtered)
litest_abort_msg("Invalid test device type");
}
static int
is_debugger_attached(void)
{
int status;
int rc;
int pid = fork();
if (pid == -1)
return 0;
if (pid == 0) {
int ppid = getppid();
if (ptrace(PTRACE_ATTACH, ppid, NULL, NULL) == 0) {
waitpid(ppid, NULL, 0);
ptrace(PTRACE_CONT, NULL, NULL);
ptrace(PTRACE_DETACH, ppid, NULL, NULL);
rc = 0;
} else {
rc = 1;
}
_exit(rc);
} else {
waitpid(pid, &status, 0);
rc = WEXITSTATUS(status);
}
return rc;
}
static void
litest_log_handler(struct libinput *libinput,
enum libinput_log_priority pri,
const char *format,
va_list args)
{
const char *priority = NULL;
switch(pri) {
case LIBINPUT_LOG_PRIORITY_INFO: priority = "info"; break;
case LIBINPUT_LOG_PRIORITY_ERROR: priority = "error"; break;
case LIBINPUT_LOG_PRIORITY_DEBUG: priority = "debug"; break;
default:
abort();
}
fprintf(stderr, "litest %s: ", priority);
vfprintf(stderr, format, args);
}
static int
open_restricted(const char *path, int flags, void *userdata)
{
int fd = open(path, flags);
return fd < 0 ? -errno : fd;
}
static void
close_restricted(int fd, void *userdata)
{
close(fd);
}
struct libinput_interface interface = {
.open_restricted = open_restricted,
.close_restricted = close_restricted,
};
static inline int
litest_run(int argc, char **argv)
{
struct suite *s, *snext;
int failed;
SRunner *sr = NULL;
if (list_empty(&all_tests)) {
fprintf(stderr,
"Error: filters are too strict, no tests to run.\n");
return 1;
}
if (in_debugger == -1) {
in_debugger = is_debugger_attached();
if (in_debugger)
setenv("CK_FORK", "no", 0);
}
list_for_each(s, &all_tests, node) {
if (!sr)
sr = srunner_create(s->suite);
else
srunner_add_suite(sr, s->suite);
}
if (getenv("LITEST_VERBOSE"))
verbose = 1;
srunner_run_all(sr, CK_ENV);
failed = srunner_ntests_failed(sr);
srunner_free(sr);
list_for_each_safe(s, snext, &all_tests, node) {
struct test *t, *tnext;
list_for_each_safe(t, tnext, &s->tests, node) {
free(t->name);
list_remove(&t->node);
free(t);
}
list_remove(&s->node);
free(s->name);
free(s);
}
return failed;
}
static struct input_absinfo *
merge_absinfo(const struct input_absinfo *orig,
const struct input_absinfo *override)
{
struct input_absinfo *abs;
unsigned int nelem, i;
size_t sz = ABS_MAX + 1;
if (!orig)
return NULL;
abs = calloc(sz, sizeof(*abs));
litest_assert(abs != NULL);
nelem = 0;
while (orig[nelem].value != -1) {
abs[nelem] = orig[nelem];
nelem++;
litest_assert_int_lt(nelem, sz);
}
/* just append, if the same axis is present twice, libevdev will
only use the last value anyway */
i = 0;
while (override && override[i].value != -1) {
abs[nelem++] = override[i++];
litest_assert_int_lt(nelem, sz);
}
litest_assert_int_lt(nelem, sz);
abs[nelem].value = -1;
return abs;
}
static int*
merge_events(const int *orig, const int *override)
{
int *events;
unsigned int nelem, i;
size_t sz = KEY_MAX * 3;
if (!orig)
return NULL;
events = calloc(sz, sizeof(int));
litest_assert(events != NULL);
nelem = 0;
while (orig[nelem] != -1) {
events[nelem] = orig[nelem];
nelem++;
litest_assert_int_lt(nelem, sz);
}
/* just append, if the same axis is present twice, libevdev will
* ignore the double definition anyway */
i = 0;
while (override && override[i] != -1) {
events[nelem++] = override[i++];
litest_assert_int_le(nelem, sz);
}
litest_assert_int_lt(nelem, sz);
events[nelem] = -1;
return events;
}
static inline void
litest_copy_file(const char *dest, const char *src, const char *header)
{
int in, out;
out = open(dest, O_CREAT|O_WRONLY, 0644);
litest_assert_int_gt(out, -1);
if (header)
write(out, header, strlen(header));
in = open(src, O_RDONLY);
litest_assert_int_gt(in, -1);
/* lazy, just check for error and empty file copy */
litest_assert_int_gt(sendfile(out, in, NULL, 4096), 0);
close(out);
close(in);
}
static inline void
litest_install_model_quirks(void)
{
litest_copy_file(UDEV_COMMON_RULE_FILE, LIBINPUT_UDEV_RULES_FILE, NULL);
litest_copy_file(UDEV_COMMON_HWDB_FILE,
LIBINPUT_UDEV_HWDB_FILE,
"#################################################################\n"
"# WARNING: REMOVE THIS FILE\n"
"# This is the run-time hwdb file for the libinput test suite and\n"
"# should be removed on exit. If the test-suite is not currently \n"
"# running, remove this file and update your hwdb: \n"
"# sudo udevadm hwdb --update\n"
"#################################################################\n\n");
}
static inline void
litest_remove_model_quirks(void)
{
unlink(UDEV_COMMON_RULE_FILE);
unlink(UDEV_COMMON_HWDB_FILE);
}
static char *
litest_init_udev_rules(struct litest_test_device *dev)
{
int rc;
FILE *f;
char *path = 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 = mkdir(UDEV_HWDB_D, 0755);
if (rc == -1 && errno != EEXIST)
ck_abort_msg("Failed to create udev hwdb directory (%s)\n",
strerror(errno));
litest_install_model_quirks();
/* device-specific udev rules */
if (!dev->udev_rule)
goto out;
rc = xasprintf(&path,
"%s/%s%s.rules",
UDEV_RULES_D,
UDEV_RULE_PREFIX,
dev->shortname);
litest_assert_int_eq(rc,
(int)(
strlen(UDEV_RULES_D) +
strlen(UDEV_RULE_PREFIX) +
strlen(dev->shortname) + 7));
f = fopen(path, "w");
litest_assert_notnull(f);
litest_assert_int_ge(fputs(dev->udev_rule, f), 0);
fclose(f);
out:
litest_reload_udev_rules();
return path;
}
static struct litest_device *
litest_create(enum litest_device_type which,
const char *name_override,
struct input_id *id_override,
const struct input_absinfo *abs_override,
const int *events_override)
{
struct litest_device *d = NULL;
struct litest_test_device **dev;
const char *name;
const struct input_id *id;
struct input_absinfo *abs;
int *events;
char *udev_file;
dev = devices;
while (*dev) {
if ((*dev)->type == which)
break;
dev++;
}
if (!*dev)
ck_abort_msg("Invalid device type %d\n", which);
d = zalloc(sizeof(*d));
litest_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) {
litest_remove_model_quirks();
if (udev_file)
unlink(udev_file);
litest_abort_msg("Custom create cannot be overridden");
}
return d;
}
abs = merge_absinfo((*dev)->absinfo, abs_override);
events = merge_events((*dev)->events, events_override);
name = name_override ? name_override : (*dev)->name;
id = id_override ? id_override : (*dev)->id;
d->uinput = litest_create_uinput_device_from_description(name,
id,
abs,
events);
d->interface = (*dev)->interface;
d->udev_rule_file = udev_file;
free(abs);
free(events);
return d;
}
struct libinput *
litest_create_context(void)
{
struct libinput *libinput =
libinput_path_create_context(&interface, NULL);
litest_assert_notnull(libinput);
libinput_log_set_handler(libinput, litest_log_handler);
if (verbose)
libinput_log_set_priority(libinput, LIBINPUT_LOG_PRIORITY_DEBUG);
return libinput;
}
void
litest_disable_log_handler(struct libinput *libinput)
{
libinput_log_set_handler(libinput, NULL);
}
void
litest_restore_log_handler(struct libinput *libinput)
{
libinput_log_set_handler(libinput, litest_log_handler);
}
static inline void
litest_wait_for_udev(int fd)
{
struct udev *udev;
struct udev_device *device;
struct stat st;
int loop_count = 0;
litest_assert_int_ge(fstat(fd, &st), 0);
udev = udev_new();
device = udev_device_new_from_devnum(udev, 'c', st.st_rdev);
litest_assert_ptr_notnull(device);
while (device && !udev_device_get_property_value(device, "ID_INPUT")) {
loop_count++;
litest_assert_int_lt(loop_count, 300);
udev_device_unref(device);
msleep(2);
device = udev_device_new_from_devnum(udev, 'c', st.st_rdev);
}
udev_device_unref(device);
udev_unref(udev);
}
struct litest_device *
litest_add_device_with_overrides(struct libinput *libinput,
enum litest_device_type which,
const char *name_override,
struct input_id *id_override,
const struct input_absinfo *abs_override,
const int *events_override)
{
struct litest_device *d;
int fd;
int rc;
const char *path;
d = litest_create(which,
name_override,
id_override,
abs_override,
events_override);
path = libevdev_uinput_get_devnode(d->uinput);
litest_assert(path != NULL);
fd = open(path, O_RDWR|O_NONBLOCK);
litest_assert_int_ne(fd, -1);
rc = libevdev_new_from_fd(fd, &d->evdev);
litest_assert_int_eq(rc, 0);
litest_wait_for_udev(fd);
d->libinput = libinput;
d->libinput_device = libinput_path_add_device(d->libinput, path);
litest_assert(d->libinput_device != NULL);
libinput_device_ref(d->libinput_device);
if (d->interface) {
d->interface->min[ABS_X] = libevdev_get_abs_minimum(d->evdev, ABS_X);
d->interface->max[ABS_X] = libevdev_get_abs_maximum(d->evdev, ABS_X);
d->interface->min[ABS_Y] = libevdev_get_abs_minimum(d->evdev, ABS_Y);
d->interface->max[ABS_Y] = libevdev_get_abs_maximum(d->evdev, ABS_Y);
}
return d;
}
struct litest_device *
litest_add_device(struct libinput *libinput,
enum litest_device_type which)
{
return litest_add_device_with_overrides(libinput,
which,
NULL,
NULL,
NULL,
NULL);
}
struct litest_device *
litest_create_device_with_overrides(enum litest_device_type which,
const char *name_override,
struct input_id *id_override,
const struct input_absinfo *abs_override,
const int *events_override)
{
struct litest_device *dev =
litest_add_device_with_overrides(litest_create_context(),
which,
name_override,
id_override,
abs_override,
events_override);
dev->owns_context = true;
return dev;
}
struct litest_device *
litest_create_device(enum litest_device_type which)
{
return litest_create_device_with_overrides(which, NULL, NULL, NULL, NULL);
}
int
litest_handle_events(struct litest_device *d)
{
struct pollfd fd;
fd.fd = libinput_get_fd(d->libinput);
fd.events = POLLIN;
while (poll(&fd, 1, 1))
libinput_dispatch(d->libinput);
return 0;
}
void
litest_delete_device(struct litest_device *d)
{
if (!d)
return;
if (d->udev_rule_file) {
litest_remove_model_quirks();
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)
libinput_unref(d->libinput);
libevdev_free(d->evdev);
libevdev_uinput_destroy(d->uinput);
free(d->private);
memset(d,0, sizeof(*d));
free(d);
/* zzz so udev can catch up with things, so we don't accidentally open
* an old device in the next test and then get all upset when things blow
* up */
msleep(10);
}
void
litest_event(struct litest_device *d, unsigned int type,
unsigned int code, int value)
{
int ret;
if (d->skip_ev_syn && type == EV_SYN && code == SYN_REPORT)
return;
ret = libevdev_uinput_write_event(d->uinput, type, code, value);
litest_assert_int_eq(ret, 0);
}
int
litest_auto_assign_value(struct litest_device *d,
const struct input_event *ev,
int slot, double x, double y,
bool touching)
{
static int tracking_id;
int value = ev->value;
if (value != LITEST_AUTO_ASSIGN || ev->type != EV_ABS)
return value;
switch (ev->code) {
case ABS_X:
case ABS_MT_POSITION_X:
value = litest_scale(d, ABS_X, x);
break;
case ABS_Y:
case ABS_MT_POSITION_Y:
value = litest_scale(d, ABS_Y, y);
break;
case ABS_MT_TRACKING_ID:
value = ++tracking_id;
break;
case ABS_MT_SLOT:
value = slot;
break;
case ABS_MT_DISTANCE:
value = touching ? 0 : 1;
break;
}
return value;
}
static void
send_btntool(struct litest_device *d)
{
litest_event(d, EV_KEY, BTN_TOUCH, d->ntouches_down != 0);
litest_event(d, EV_KEY, BTN_TOOL_FINGER, d->ntouches_down == 1);
litest_event(d, EV_KEY, BTN_TOOL_DOUBLETAP, d->ntouches_down == 2);
litest_event(d, EV_KEY, BTN_TOOL_TRIPLETAP, d->ntouches_down == 3);
litest_event(d, EV_KEY, BTN_TOOL_QUADTAP, d->ntouches_down == 4);
litest_event(d, EV_KEY, BTN_TOOL_QUINTTAP, d->ntouches_down == 5);
}
static void
litest_slot_start(struct litest_device *d, unsigned int slot,
double x, double y, bool touching)
{
struct input_event *ev;
assert(d->ntouches_down >= 0);
d->ntouches_down++;
send_btntool(d);
if (d->interface->touch_down) {
d->interface->touch_down(d, slot, x, y);
return;
}
ev = d->interface->touch_down_events;
while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) {
int value = litest_auto_assign_value(d,
ev,
slot,
x,
y,
touching);
litest_event(d, ev->type, ev->code, value);
ev++;
}
}
void
litest_touch_down(struct litest_device *d, unsigned int slot,
double x, double y)
{
litest_slot_start(d, slot, x, y, 1);
}
void
litest_touch_up(struct litest_device *d, unsigned int slot)
{
struct input_event *ev;
struct input_event up[] = {
{ .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
{ .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = -1 },
{ .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
{ .type = -1, .code = -1 }
};
assert(d->ntouches_down > 0);
d->ntouches_down--;
send_btntool(d);
if (d->interface->touch_up) {
d->interface->touch_up(d, slot);
return;
} else if (d->interface->touch_up_events) {
ev = d->interface->touch_up_events;
} else
ev = up;
while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) {
int value = litest_auto_assign_value(d,
ev,
slot,
0,
0,
false);
litest_event(d, ev->type, ev->code, value);
ev++;
}
}
static void
litest_slot_move(struct litest_device *d, unsigned int slot,
double x, double y, bool touching)
{
struct input_event *ev;
if (d->interface->touch_move) {
d->interface->touch_move(d, slot, x, y);
return;
}
ev = d->interface->touch_move_events;
while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) {
int value = litest_auto_assign_value(d,
ev,
slot,
x,
y,
touching);
litest_event(d, ev->type, ev->code, value);
ev++;
}
}
void
litest_touch_move(struct litest_device *d, unsigned int slot,
double x, double y)
{
litest_slot_move(d, slot, x, y, true);
}
void
litest_touch_move_to(struct litest_device *d,
unsigned int slot,
double x_from, double y_from,
double x_to, double y_to,
int steps, int sleep_ms)
{
for (int i = 0; i < steps - 1; i++) {
litest_touch_move(d, slot,
x_from + (x_to - x_from)/steps * i,
y_from + (y_to - y_from)/steps * i);
if (sleep_ms) {
libinput_dispatch(d->libinput);
msleep(sleep_ms);
libinput_dispatch(d->libinput);
}
}
litest_touch_move(d, slot, x_to, y_to);
}
void
litest_touch_move_two_touches(struct litest_device *d,
double x0, double y0,
double x1, double y1,
double dx, double dy,
int steps, int sleep_ms)
{
for (int i = 0; i < steps - 1; i++) {
litest_touch_move(d, 0, x0 + dx / steps * i,
y0 + dy / steps * i);
litest_touch_move(d, 1, x1 + dx / steps * i,
y1 + dy / steps * i);
if (sleep_ms) {
libinput_dispatch(d->libinput);
msleep(sleep_ms);
libinput_dispatch(d->libinput);
}
}
litest_touch_move(d, 0, x0 + dx, y0 + dy);
litest_touch_move(d, 1, x1 + dx, y1 + dy);
}
void
litest_hover_start(struct litest_device *d, unsigned int slot,
double x, double y)
{
litest_slot_start(d, slot, x, y, 0);
}
void
litest_hover_end(struct litest_device *d, unsigned int slot)
{
struct input_event *ev;
struct input_event up[] = {
{ .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
{ .type = EV_ABS, .code = ABS_MT_DISTANCE, .value = 1 },
{ .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = -1 },
{ .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
{ .type = -1, .code = -1 }
};
assert(d->ntouches_down > 0);
d->ntouches_down--;
send_btntool(d);
if (d->interface->touch_up) {
d->interface->touch_up(d, slot);
return;
} else if (d->interface->touch_up_events) {
ev = d->interface->touch_up_events;
} else
ev = up;
while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) {
int value = litest_auto_assign_value(d, ev, slot, 0, 0, 0);
litest_event(d, ev->type, ev->code, value);
ev++;
}
}
void
litest_hover_move(struct litest_device *d, unsigned int slot,
double x, double y)
{
litest_slot_move(d, slot, x, y, false);
}
void
litest_hover_move_to(struct litest_device *d,
unsigned int slot,
double x_from, double y_from,
double x_to, double y_to,
int steps, int sleep_ms)
{
for (int i = 0; i < steps - 1; i++) {
litest_hover_move(d, slot,
x_from + (x_to - x_from)/steps * i,
y_from + (y_to - y_from)/steps * i);
if (sleep_ms) {
libinput_dispatch(d->libinput);
msleep(sleep_ms);
libinput_dispatch(d->libinput);
}
}
litest_hover_move(d, slot, x_to, y_to);
}
void
litest_hover_move_two_touches(struct litest_device *d,
double x0, double y0,
double x1, double y1,
double dx, double dy,
int steps, int sleep_ms)
{
for (int i = 0; i < steps - 1; i++) {
litest_push_event_frame(d);
litest_hover_move(d, 0, x0 + dx / steps * i,
y0 + dy / steps * i);
litest_hover_move(d, 1, x1 + dx / steps * i,
y1 + dy / steps * i);
litest_pop_event_frame(d);
if (sleep_ms) {
libinput_dispatch(d->libinput);
msleep(sleep_ms);
libinput_dispatch(d->libinput);
}
}
litest_push_event_frame(d);
litest_hover_move(d, 0, x0 + dx, y0 + dy);
litest_hover_move(d, 1, x1 + dx, y1 + dy);
litest_pop_event_frame(d);
}
void
litest_button_click(struct litest_device *d, unsigned int button, bool is_press)
{
struct input_event *ev;
struct input_event click[] = {
{ .type = EV_KEY, .code = button, .value = is_press ? 1 : 0 },
{ .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
};
ARRAY_FOR_EACH(click, ev)
litest_event(d, ev->type, ev->code, ev->value);
}
void
litest_button_scroll(struct litest_device *dev,
unsigned int button,
double dx, double dy)
{
struct libinput *li = dev->libinput;
litest_button_click(dev, button, 1);
libinput_dispatch(li);
litest_timeout_buttonscroll();
libinput_dispatch(li);
litest_event(dev, EV_REL, REL_X, dx);
litest_event(dev, EV_REL, REL_Y, dy);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_button_click(dev, button, 0);
libinput_dispatch(li);
}
void
litest_keyboard_key(struct litest_device *d, unsigned int key, bool is_press)
{
litest_button_click(d, key, is_press);
}
int
litest_scale(const struct litest_device *d, unsigned int axis, double val)
{
int min, max;
litest_assert_int_ge((int)val, 0);
litest_assert_int_le((int)val, 100);
litest_assert_int_le(axis, (unsigned int)ABS_Y);
min = d->interface->min[axis];
max = d->interface->max[axis];
return (max - min) * val/100.0 + min;
}
void
litest_wait_for_event(struct libinput *li)
{
return litest_wait_for_event_of_type(li, -1);
}
void
litest_wait_for_event_of_type(struct libinput *li, ...)
{
va_list args;
enum libinput_event_type types[32] = {LIBINPUT_EVENT_NONE};
size_t ntypes = 0;
enum libinput_event_type type;
va_start(args, li);
type = va_arg(args, int);
while ((int)type != -1) {
assert(type > 0);
assert(ntypes < ARRAY_LENGTH(types));
types[ntypes++] = type;
type = va_arg(args, int);
}
va_end(args);
while (1) {
size_t i;
struct libinput_event *event;
while ((type = libinput_next_event_type(li)) == LIBINPUT_EVENT_NONE) {
msleep(10);
libinput_dispatch(li);
}
/* no event mask means wait for any event */
if (ntypes == 0)
return;
for (i = 0; i < ntypes; i++) {
if (type == types[i])
return;
}
event = libinput_get_event(li);
libinput_event_destroy(event);
}
}
void
litest_drain_events(struct libinput *li)
{
struct libinput_event *event;
libinput_dispatch(li);
while ((event = libinput_get_event(li))) {
libinput_event_destroy(event);
libinput_dispatch(li);
}
}
static const char *
litest_event_type_str(struct libinput_event *event)
{
const char *str = NULL;
switch (libinput_event_get_type(event)) {
case LIBINPUT_EVENT_NONE:
abort();
case LIBINPUT_EVENT_DEVICE_ADDED:
str = "ADDED";
break;
case LIBINPUT_EVENT_DEVICE_REMOVED:
str = "REMOVED";
break;
case LIBINPUT_EVENT_KEYBOARD_KEY:
str = "KEY";
break;
case LIBINPUT_EVENT_POINTER_MOTION:
str = "MOTION";
break;
case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
str = "ABSOLUTE";
break;
case LIBINPUT_EVENT_POINTER_BUTTON:
str = "BUTTON";
break;
case LIBINPUT_EVENT_POINTER_AXIS:
str = "AXIS";
break;
case LIBINPUT_EVENT_TOUCH_DOWN:
str = "TOUCH DOWN";
break;
case LIBINPUT_EVENT_TOUCH_UP:
str = "TOUCH UP";
break;
case LIBINPUT_EVENT_TOUCH_MOTION:
str = "TOUCH MOTION";
break;
case LIBINPUT_EVENT_TOUCH_CANCEL:
str = "TOUCH CANCEL";
break;
case LIBINPUT_EVENT_TOUCH_FRAME:
str = "TOUCH FRAME";
break;
}
return str;
}
static void
litest_print_event(struct libinput_event *event)
{
struct libinput_event_pointer *p;
struct libinput_device *dev;
enum libinput_event_type type;
double x, y;
dev = libinput_event_get_device(event);
type = libinput_event_get_type(event);
fprintf(stderr,
"device %s type %s ",
libinput_device_get_sysname(dev),
litest_event_type_str(event));
switch (type) {
case LIBINPUT_EVENT_POINTER_MOTION:
p = libinput_event_get_pointer_event(event);
x = libinput_event_pointer_get_dx(p);
y = libinput_event_pointer_get_dy(p);
fprintf(stderr, "%.2f/%.2f", x, y);
break;
case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
p = libinput_event_get_pointer_event(event);
x = libinput_event_pointer_get_absolute_x(p);
y = libinput_event_pointer_get_absolute_y(p);
fprintf(stderr, "%.2f/%.2f", x, y);
break;
case LIBINPUT_EVENT_POINTER_BUTTON:
p = libinput_event_get_pointer_event(event);
fprintf(stderr,
"button %d state %d",
libinput_event_pointer_get_button(p),
libinput_event_pointer_get_button_state(p));
break;
case LIBINPUT_EVENT_POINTER_AXIS:
p = libinput_event_get_pointer_event(event);
fprintf(stderr,
"vert %.f horiz %.2f",
libinput_event_pointer_get_axis_value(p,
LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL),
libinput_event_pointer_get_axis_value(p,
LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL));
break;
default:
break;
}
fprintf(stderr, "\n");
}
void
litest_assert_empty_queue(struct libinput *li)
{
bool empty_queue = true;
struct libinput_event *event;
libinput_dispatch(li);
while ((event = libinput_get_event(li))) {
empty_queue = false;
fprintf(stderr,
"Unexpected event: ");
litest_print_event(event);
libinput_event_destroy(event);
libinput_dispatch(li);
}
litest_assert(empty_queue);
}
struct libevdev_uinput *
litest_create_uinput_device_from_description(const char *name,
const struct input_id *id,
const struct input_absinfo *abs_info,
const int *events)
{
struct libevdev_uinput *uinput;
struct libevdev *dev;
int type, code;
int rc, fd;
const struct input_absinfo *abs;
const struct input_absinfo default_abs = {
.value = 0,
.minimum = 0,
.maximum = 0xffff,
.fuzz = 0,
.flat = 0,
.resolution = 100
};
char buf[512];
const char *devnode;
dev = libevdev_new();
litest_assert(dev != NULL);
snprintf(buf, sizeof(buf), "litest %s", name);
libevdev_set_name(dev, buf);
if (id) {
libevdev_set_id_bustype(dev, id->bustype);
libevdev_set_id_vendor(dev, id->vendor);
libevdev_set_id_product(dev, id->product);
libevdev_set_id_version(dev, id->version);
}
abs = abs_info;
while (abs && abs->value != -1) {
rc = libevdev_enable_event_code(dev, EV_ABS,
abs->value, abs);
litest_assert_int_eq(rc, 0);
abs++;
}
while (events &&
(type = *events++) != -1 &&
(code = *events++) != -1) {
if (type == INPUT_PROP_MAX) {
rc = libevdev_enable_property(dev, code);
} else {
rc = libevdev_enable_event_code(dev, type, code,
type == EV_ABS ? &default_abs : NULL);
}
litest_assert_int_eq(rc, 0);
}
rc = libevdev_uinput_create_from_device(dev,
LIBEVDEV_UINPUT_OPEN_MANAGED,
&uinput);
/* workaround for a bug in libevdev pre-1.3
http://cgit.freedesktop.org/libevdev/commit/?id=debe9b030c8069cdf78307888ef3b65830b25122 */
if (rc == -EBADF)
rc = -EACCES;
litest_assert_msg(rc == 0, "Failed to create uinput device: %s", strerror(-rc));
libevdev_free(dev);
devnode = libevdev_uinput_get_devnode(uinput);
litest_assert_notnull(devnode);
fd = open(devnode, O_RDONLY);
litest_assert_int_gt(fd, -1);
rc = libevdev_new_from_fd(fd, &dev);
litest_assert_int_eq(rc, 0);
/* uinput does not yet support setting the resolution, so we set it
* afterwards. This is of course racy as hell but the way we
* _generally_ use this function by the time libinput uses the
* device, we're finished here */
abs = abs_info;
while (abs && abs->value != -1) {
if (abs->resolution != 0) {
rc = libevdev_kernel_set_abs_info(dev,
abs->value,
abs);
litest_assert_int_eq(rc, 0);
}
abs++;
}
close(fd);
libevdev_free(dev);
return uinput;
}
static struct libevdev_uinput *
litest_create_uinput_abs_device_v(const char *name,
struct input_id *id,
const struct input_absinfo *abs,
va_list args)
{
int events[KEY_MAX * 2 + 2]; /* increase this if not sufficient */
int *event = events;
int type, code;
while ((type = va_arg(args, int)) != -1 &&
(code = va_arg(args, int)) != -1) {
*event++ = type;
*event++ = code;
litest_assert(event < &events[ARRAY_LENGTH(events) - 2]);
}
*event++ = -1;
*event++ = -1;
return litest_create_uinput_device_from_description(name, id,
abs, events);
}
struct libevdev_uinput *
litest_create_uinput_abs_device(const char *name,
struct input_id *id,
const struct input_absinfo *abs,
...)
{
struct libevdev_uinput *uinput;
va_list args;
va_start(args, abs);
uinput = litest_create_uinput_abs_device_v(name, id, abs, args);
va_end(args);
return uinput;
}
struct libevdev_uinput *
litest_create_uinput_device(const char *name, struct input_id *id, ...)
{
struct libevdev_uinput *uinput;
va_list args;
va_start(args, id);
uinput = litest_create_uinput_abs_device_v(name, id, NULL, args);
va_end(args);
return uinput;
}
struct libinput_event_pointer*
litest_is_button_event(struct libinput_event *event,
unsigned int button,
enum libinput_button_state state)
{
struct libinput_event_pointer *ptrev;
enum libinput_event_type type = LIBINPUT_EVENT_POINTER_BUTTON;
litest_assert(event != NULL);
litest_assert_int_eq(libinput_event_get_type(event), type);
ptrev = libinput_event_get_pointer_event(event);
litest_assert_int_eq(libinput_event_pointer_get_button(ptrev),
button);
litest_assert_int_eq(libinput_event_pointer_get_button_state(ptrev),
state);
return ptrev;
}
struct libinput_event_pointer *
litest_is_axis_event(struct libinput_event *event,
enum libinput_pointer_axis axis,
enum libinput_pointer_axis_source source)
{
struct libinput_event_pointer *ptrev;
enum libinput_event_type type = LIBINPUT_EVENT_POINTER_AXIS;
litest_assert(event != NULL);
litest_assert_int_eq(libinput_event_get_type(event), type);
ptrev = libinput_event_get_pointer_event(event);
litest_assert(libinput_event_pointer_has_axis(ptrev, axis));
if (source != 0)
litest_assert_int_eq(libinput_event_pointer_get_axis_source(ptrev),
source);
return ptrev;
}
struct libinput_event_pointer *
litest_is_motion_event(struct libinput_event *event)
{
struct libinput_event_pointer *ptrev;
enum libinput_event_type type = LIBINPUT_EVENT_POINTER_MOTION;
double x, y, ux, uy;
litest_assert(event != NULL);
litest_assert_int_eq(libinput_event_get_type(event), type);
ptrev = libinput_event_get_pointer_event(event);
x = libinput_event_pointer_get_dx(ptrev);
y = libinput_event_pointer_get_dy(ptrev);
ux = libinput_event_pointer_get_dx_unaccelerated(ptrev);
uy = libinput_event_pointer_get_dy_unaccelerated(ptrev);
/* No 0 delta motion events */
litest_assert(x != 0.0 || y != 0.0 ||
ux != 0.0 || uy != 0.0);
return ptrev;
}
void
litest_assert_button_event(struct libinput *li, unsigned int button,
enum libinput_button_state state)
{
struct libinput_event *event;
litest_wait_for_event(li);
event = libinput_get_event(li);
litest_is_button_event(event, button, state);
libinput_event_destroy(event);
}
struct libinput_event_touch *
litest_is_touch_event(struct libinput_event *event,
enum libinput_event_type type)
{
struct libinput_event_touch *touch;
litest_assert(event != NULL);
if (type == 0)
type = libinput_event_get_type(event);
switch (type) {
case LIBINPUT_EVENT_TOUCH_DOWN:
case LIBINPUT_EVENT_TOUCH_UP:
case LIBINPUT_EVENT_TOUCH_MOTION:
case LIBINPUT_EVENT_TOUCH_FRAME:
litest_assert_int_eq(libinput_event_get_type(event), type);
break;
default:
ck_abort_msg("%s: invalid touch type %d\n", __func__, type);
}
touch = libinput_event_get_touch_event(event);
return touch;
}
struct libinput_event_keyboard *
litest_is_keyboard_event(struct libinput_event *event,
unsigned int key,
enum libinput_key_state state)
{
struct libinput_event_keyboard *kevent;
enum libinput_event_type type = LIBINPUT_EVENT_KEYBOARD_KEY;
litest_assert(event != NULL);
litest_assert_int_eq(libinput_event_get_type(event), type);
kevent = libinput_event_get_keyboard_event(event);
litest_assert(kevent != NULL);
litest_assert_int_eq(libinput_event_keyboard_get_key(kevent), key);
litest_assert_int_eq(libinput_event_keyboard_get_key_state(kevent),
state);
return kevent;
}
void
litest_assert_scroll(struct libinput *li,
enum libinput_pointer_axis axis,
int minimum_movement)
{
struct libinput_event *event, *next_event;
struct libinput_event_pointer *ptrev;
int value;
event = libinput_get_event(li);
next_event = libinput_get_event(li);
litest_assert(next_event != NULL); /* At least 1 scroll + stop scroll */
while (event) {
ptrev = litest_is_axis_event(event, axis, 0);
if (next_event) {
value = libinput_event_pointer_get_axis_value(ptrev,
axis);
/* Normal scroll event, check dir */
if (minimum_movement > 0) {
litest_assert_int_ge(value, minimum_movement);
} else {
litest_assert_int_le(value, minimum_movement);
}
} else {
/* Last scroll event, must be 0 */
litest_assert_int_eq(
libinput_event_pointer_get_axis_value(ptrev, axis),
0);
}
libinput_event_destroy(event);
event = next_event;
next_event = libinput_get_event(li);
}
}
void
litest_assert_only_typed_events(struct libinput *li,
enum libinput_event_type type)
{
struct libinput_event *event;
assert(type != LIBINPUT_EVENT_NONE);
libinput_dispatch(li);
event = libinput_get_event(li);
litest_assert_notnull(event);
while (event) {
litest_assert_int_eq(libinput_event_get_type(event),
type);
libinput_event_destroy(event);
libinput_dispatch(li);
event = libinput_get_event(li);
}
}
void
litest_timeout_tap(void)
{
msleep(200);
}
void
litest_timeout_tapndrag(void)
{
msleep(520);
}
void
litest_timeout_softbuttons(void)
{
msleep(300);
}
void
litest_timeout_buttonscroll(void)
{
msleep(300);
}
void
litest_timeout_finger_switch(void)
{
msleep(120);
}
void
litest_timeout_edgescroll(void)
{
msleep(300);
}
void
litest_timeout_middlebutton(void)
{
msleep(70);
}
void
litest_timeout_dwt_short(void)
{
msleep(220);
}
void
litest_timeout_dwt_long(void)
{
msleep(520);
}
void
litest_push_event_frame(struct litest_device *dev)
{
assert(!dev->skip_ev_syn);
dev->skip_ev_syn = true;
}
void
litest_pop_event_frame(struct litest_device *dev)
{
assert(dev->skip_ev_syn);
dev->skip_ev_syn = false;
litest_event(dev, EV_SYN, SYN_REPORT, 0);
}
static void
send_abs_xy(struct litest_device *d, double x, double y)
{
struct input_event e;
int val;
e.type = EV_ABS;
e.code = ABS_X;
e.value = LITEST_AUTO_ASSIGN;
val = litest_auto_assign_value(d, &e, 0, x, y, true);
litest_event(d, EV_ABS, ABS_X, val);
e.code = ABS_Y;
val = litest_auto_assign_value(d, &e, 0, x, y, true);
litest_event(d, EV_ABS, ABS_Y, val);
}
static void
send_abs_mt_xy(struct litest_device *d, double x, double y)
{
struct input_event e;
int val;
e.type = EV_ABS;
e.code = ABS_MT_POSITION_X;
e.value = LITEST_AUTO_ASSIGN;
val = litest_auto_assign_value(d, &e, 0, x, y, true);
litest_event(d, EV_ABS, ABS_MT_POSITION_X, val);
e.code = ABS_MT_POSITION_Y;
e.value = LITEST_AUTO_ASSIGN;
val = litest_auto_assign_value(d, &e, 0, x, y, true);
litest_event(d, EV_ABS, ABS_MT_POSITION_Y, val);
}
void
litest_semi_mt_touch_down(struct litest_device *d,
struct litest_semi_mt *semi_mt,
unsigned int slot,
double x, double y)
{
double t, l, r = 0, b = 0; /* top, left, right, bottom */
if (d->ntouches_down > 2 || slot > 1)
return;
if (d->ntouches_down == 1) {
l = x;
t = y;
} else {
int other = (slot + 1) % 2;
l = min(x, semi_mt->touches[other].x);
t = min(y, semi_mt->touches[other].y);
r = max(x, semi_mt->touches[other].x);
b = max(y, semi_mt->touches[other].y);
}
send_abs_xy(d, l, t);
litest_event(d, EV_ABS, ABS_MT_SLOT, 0);
if (d->ntouches_down == 1)
litest_event(d, EV_ABS, ABS_MT_TRACKING_ID, ++semi_mt->tracking_id);
send_abs_mt_xy(d, l, t);
if (d->ntouches_down == 2) {
litest_event(d, EV_ABS, ABS_MT_SLOT, 1);
litest_event(d, EV_ABS, ABS_MT_TRACKING_ID, ++semi_mt->tracking_id);
send_abs_mt_xy(d, r, b);
}
litest_event(d, EV_SYN, SYN_REPORT, 0);
semi_mt->touches[slot].x = x;
semi_mt->touches[slot].y = y;
}
void
litest_semi_mt_touch_move(struct litest_device *d,
struct litest_semi_mt *semi_mt,
unsigned int slot,
double x, double y)
{
double t, l, r = 0, b = 0; /* top, left, right, bottom */
if (d->ntouches_down > 2 || slot > 1)
return;
if (d->ntouches_down == 1) {
l = x;
t = y;
} else {
int other = (slot + 1) % 2;
l = min(x, semi_mt->touches[other].x);
t = min(y, semi_mt->touches[other].y);
r = max(x, semi_mt->touches[other].x);
b = max(y, semi_mt->touches[other].y);
}
send_abs_xy(d, l, t);
litest_event(d, EV_ABS, ABS_MT_SLOT, 0);
send_abs_mt_xy(d, l, t);
if (d->ntouches_down == 2) {
litest_event(d, EV_ABS, ABS_MT_SLOT, 1);
send_abs_mt_xy(d, r, b);
}
litest_event(d, EV_SYN, SYN_REPORT, 0);
semi_mt->touches[slot].x = x;
semi_mt->touches[slot].y = y;
}
void
litest_semi_mt_touch_up(struct litest_device *d,
struct litest_semi_mt *semi_mt,
unsigned int slot)
{
/* note: ntouches_down is decreased before we get here */
if (d->ntouches_down >= 2 || slot > 1)
return;
litest_event(d, EV_ABS, ABS_MT_SLOT, d->ntouches_down);
litest_event(d, EV_ABS, ABS_MT_TRACKING_ID, -1);
/* if we have one finger left, send x/y coords for that finger left.
this is likely to happen with a real touchpad */
if (d->ntouches_down == 1) {
int other = (slot + 1) % 2;
send_abs_xy(d, semi_mt->touches[other].x, semi_mt->touches[other].y);
litest_event(d, EV_ABS, ABS_MT_SLOT, 0);
send_abs_mt_xy(d, semi_mt->touches[other].x, semi_mt->touches[other].y);
}
litest_event(d, EV_SYN, SYN_REPORT, 0);
}
enum litest_mode {
LITEST_MODE_ERROR,
LITEST_MODE_TEST,
LITEST_MODE_LIST,
};
static inline enum litest_mode
litest_parse_argv(int argc, char **argv)
{
enum {
OPT_FILTER_TEST,
OPT_FILTER_DEVICE,
OPT_FILTER_GROUP,
OPT_LIST,
OPT_VERBOSE,
};
static const struct option opts[] = {
{ "filter-test", 1, 0, OPT_FILTER_TEST },
{ "filter-device", 1, 0, OPT_FILTER_DEVICE },
{ "filter-group", 1, 0, OPT_FILTER_GROUP },
{ "list", 0, 0, OPT_LIST },
{ "verbose", 0, 0, OPT_VERBOSE },
{ 0, 0, 0, 0}
};
while(1) {
int c;
int option_index = 0;
c = getopt_long(argc, argv, "", opts, &option_index);
if (c == -1)
break;
switch(c) {
case OPT_FILTER_TEST:
filter_test = optarg;
break;
case OPT_FILTER_DEVICE:
filter_device = optarg;
break;
case OPT_FILTER_GROUP:
filter_group = optarg;
break;
case OPT_LIST:
return LITEST_MODE_LIST;
case OPT_VERBOSE:
verbose = 1;
break;
default:
fprintf(stderr, "usage: %s [--list]\n", argv[0]);
return LITEST_MODE_ERROR;
}
}
return LITEST_MODE_TEST;
}
#ifndef LITEST_NO_MAIN
static void
litest_list_tests(struct list *tests)
{
struct suite *s;
list_for_each(s, tests, node) {
struct test *t;
printf("%s:\n", s->name);
list_for_each(t, &s->tests, node) {
printf(" %s\n", t->name);
}
}
}
int
main(int argc, char **argv)
{
enum litest_mode mode;
list_init(&all_tests);
mode = litest_parse_argv(argc, argv);
if (mode == LITEST_MODE_ERROR)
return EXIT_FAILURE;
litest_setup_tests();
if (mode == LITEST_MODE_LIST) {
litest_list_tests(&all_tests);
return EXIT_SUCCESS;
}
return litest_run(argc, argv);
}
#endif