mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2025-12-20 12:40:05 +01:00
litest_add_ranged* takes a range parameter that serves as the lower/upper boundary for a loop. This enables tests to be run multiple times, avoiding the timeouts we triggered by having the loops inside (e.g. see2bf8d035cand6dd02468). This just wraps the underlying check framework, the ranged variable is available as "_i" in the test. Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net> Reviewed-by: Hans de Goede <hdegoede@redhat.com>
1882 lines
42 KiB
C
1882 lines
42 KiB
C
/*
|
|
* Copyright © 2013 Red Hat, Inc.
|
|
*
|
|
* Permission to use, copy, modify, distribute, and sell this software and its
|
|
* documentation for any purpose is hereby granted without fee, provided that
|
|
* the above copyright notice appear in all copies and that both that copyright
|
|
* notice and this permission notice appear in supporting documentation, and
|
|
* that the name of the copyright holders not be used in advertising or
|
|
* publicity pertaining to distribution of the software without specific,
|
|
* written prior permission. The copyright holders make no representations
|
|
* about the suitability of this software for any purpose. It is provided "as
|
|
* is" without express or implied warranty.
|
|
*
|
|
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
|
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
|
|
* EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
|
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
|
|
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
|
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
|
|
* OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#if HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
#include <check.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <fcntl.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/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-"
|
|
|
|
static int in_debugger = -1;
|
|
static int verbose = 0;
|
|
|
|
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 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,
|
|
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 (strcmp(t->name, test_name) != 0)
|
|
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";
|
|
|
|
list_for_each(t, &suite->tests, node) {
|
|
if (strcmp(t->name, test_name) != 0)
|
|
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 void
|
|
litest_add_tcase(struct suite *suite, void *func,
|
|
enum litest_device_feature required,
|
|
enum litest_device_feature excluded,
|
|
const struct range *range)
|
|
{
|
|
struct litest_test_device **dev = devices;
|
|
|
|
assert(required >= LITEST_DISABLE_DEVICE);
|
|
assert(excluded >= LITEST_DISABLE_DEVICE);
|
|
|
|
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) {
|
|
while (*dev) {
|
|
if (((*dev)->features & required) == required &&
|
|
((*dev)->features & excluded) == 0)
|
|
litest_add_tcase_for_device(suite, func, *dev, range);
|
|
dev++;
|
|
}
|
|
} else {
|
|
while (*dev) {
|
|
litest_add_tcase_for_device(suite, func, *dev, range);
|
|
dev++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
litest_add_no_device(const char *name, void *func)
|
|
{
|
|
litest_add(name, func, LITEST_DISABLE_DEVICE, LITEST_DISABLE_DEVICE);
|
|
}
|
|
|
|
void
|
|
litest_add_ranged_no_device(const char *name,
|
|
void *func,
|
|
const struct range *range)
|
|
{
|
|
litest_add_ranged(name,
|
|
func,
|
|
LITEST_DISABLE_DEVICE,
|
|
LITEST_DISABLE_DEVICE,
|
|
range);
|
|
}
|
|
|
|
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 (strcmp(s->name, name) == 0)
|
|
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;
|
|
}
|
|
|
|
void
|
|
litest_add(const char *name,
|
|
void *func,
|
|
enum litest_device_feature required,
|
|
enum litest_device_feature excluded)
|
|
{
|
|
litest_add_ranged(name, func, required, excluded, NULL);
|
|
}
|
|
|
|
void
|
|
litest_add_ranged(const char *name,
|
|
void *func,
|
|
enum litest_device_feature required,
|
|
enum litest_device_feature excluded,
|
|
const struct range *range)
|
|
{
|
|
litest_add_tcase(get_suite(name), func, required, excluded, range);
|
|
}
|
|
|
|
void
|
|
litest_add_for_device(const char *name,
|
|
void *func,
|
|
enum litest_device_type type)
|
|
{
|
|
litest_add_ranged_for_device(name, func, type, NULL);
|
|
}
|
|
|
|
void
|
|
litest_add_ranged_for_device(const char *name,
|
|
void *func,
|
|
enum litest_device_type type,
|
|
const struct range *range)
|
|
{
|
|
struct suite *s;
|
|
struct litest_test_device **dev = devices;
|
|
|
|
assert(type < LITEST_NO_DEVICE);
|
|
|
|
s = get_suite(name);
|
|
while (*dev) {
|
|
if ((*dev)->type == type) {
|
|
litest_add_tcase_for_device(s, func, *dev, range);
|
|
return;
|
|
}
|
|
dev++;
|
|
}
|
|
|
|
ck_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_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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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 const struct option opts[] = {
|
|
{ "list", 0, 0, 'l' },
|
|
{ "verbose", 0, 0, 'v' },
|
|
{ 0, 0, 0, 0}
|
|
};
|
|
|
|
int
|
|
litest_run(int argc, char **argv) {
|
|
struct suite *s, *snext;
|
|
int failed;
|
|
SRunner *sr = NULL;
|
|
|
|
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);
|
|
}
|
|
|
|
while(1) {
|
|
int c;
|
|
int option_index = 0;
|
|
|
|
c = getopt_long(argc, argv, "", opts, &option_index);
|
|
if (c == -1)
|
|
break;
|
|
switch(c) {
|
|
case 'l':
|
|
litest_list_tests(&all_tests);
|
|
return 0;
|
|
case 'v':
|
|
verbose = 1;
|
|
break;
|
|
default:
|
|
fprintf(stderr, "usage: %s [--list]\n", argv[0]);
|
|
return 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));
|
|
ck_assert(abs != NULL);
|
|
|
|
nelem = 0;
|
|
while (orig[nelem].value != -1) {
|
|
abs[nelem] = orig[nelem];
|
|
nelem++;
|
|
ck_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++];
|
|
ck_assert_int_lt(nelem, sz);
|
|
}
|
|
|
|
ck_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));
|
|
ck_assert(events != NULL);
|
|
|
|
nelem = 0;
|
|
while (orig[nelem] != -1) {
|
|
events[nelem] = orig[nelem];
|
|
nelem++;
|
|
ck_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++];
|
|
ck_assert_int_le(nelem, sz);
|
|
}
|
|
|
|
ck_assert_int_lt(nelem, sz);
|
|
events[nelem] = -1;
|
|
|
|
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,
|
|
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));
|
|
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 (udev_file)
|
|
unlink(udev_file);
|
|
ck_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);
|
|
ck_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);
|
|
}
|
|
|
|
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);
|
|
ck_assert(path != NULL);
|
|
fd = open(path, O_RDWR|O_NONBLOCK);
|
|
ck_assert_int_ne(fd, -1);
|
|
|
|
rc = libevdev_new_from_fd(fd, &d->evdev);
|
|
ck_assert_int_eq(rc, 0);
|
|
|
|
d->libinput = libinput;
|
|
d->libinput_device = libinput_path_add_device(d->libinput, path);
|
|
ck_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) {
|
|
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);
|
|
}
|
|
|
|
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);
|
|
ck_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;
|
|
ck_assert_int_ge(val, 0);
|
|
ck_assert_int_le(val, 100);
|
|
ck_assert_int_le(axis, 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);
|
|
}
|
|
|
|
ck_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();
|
|
ck_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);
|
|
ck_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);
|
|
}
|
|
ck_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;
|
|
ck_assert_msg(rc == 0, "Failed to create uinput device: %s", strerror(-rc));
|
|
|
|
libevdev_free(dev);
|
|
|
|
devnode = libevdev_uinput_get_devnode(uinput);
|
|
ck_assert_notnull(devnode);
|
|
fd = open(devnode, O_RDONLY);
|
|
ck_assert_int_gt(fd, -1);
|
|
rc = libevdev_new_from_fd(fd, &dev);
|
|
ck_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);
|
|
ck_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;
|
|
ck_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;
|
|
|
|
ck_assert(event != NULL);
|
|
ck_assert_int_eq(libinput_event_get_type(event),
|
|
LIBINPUT_EVENT_POINTER_BUTTON);
|
|
ptrev = libinput_event_get_pointer_event(event);
|
|
ck_assert_int_eq(libinput_event_pointer_get_button(ptrev),
|
|
button);
|
|
ck_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;
|
|
|
|
ck_assert(event != NULL);
|
|
ck_assert_int_eq(libinput_event_get_type(event), type);
|
|
ptrev = libinput_event_get_pointer_event(event);
|
|
ck_assert(libinput_event_pointer_has_axis(ptrev, axis));
|
|
|
|
if (source != 0)
|
|
ck_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;
|
|
|
|
ck_assert(event != NULL);
|
|
ck_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 */
|
|
ck_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;
|
|
|
|
ck_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:
|
|
ck_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;
|
|
|
|
ck_assert_notnull(event);
|
|
ck_assert_int_eq(libinput_event_get_type(event), type);
|
|
|
|
kevent = libinput_event_get_keyboard_event(event);
|
|
ck_assert_notnull(kevent);
|
|
|
|
ck_assert_int_eq(libinput_event_keyboard_get_key(kevent), key);
|
|
ck_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;
|
|
|
|
event = libinput_get_event(li);
|
|
next_event = libinput_get_event(li);
|
|
ck_assert(next_event != NULL); /* At least 1 scroll + stop scroll */
|
|
|
|
while (event) {
|
|
ptrev = litest_is_axis_event(event, axis, 0);
|
|
|
|
if (next_event) {
|
|
/* Normal scroll event, check dir */
|
|
if (minimum_movement > 0) {
|
|
ck_assert_int_ge(
|
|
libinput_event_pointer_get_axis_value(ptrev,
|
|
axis),
|
|
minimum_movement);
|
|
} else {
|
|
ck_assert_int_le(
|
|
libinput_event_pointer_get_axis_value(ptrev,
|
|
axis),
|
|
minimum_movement);
|
|
}
|
|
} else {
|
|
/* Last scroll event, must be 0 */
|
|
ck_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);
|
|
ck_assert_notnull(event);
|
|
|
|
while (event) {
|
|
ck_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_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);
|
|
}
|