/*
* Copyright (c) 2015 Cedric Hnyda <chnyda@suse.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it would be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <linux/input.h>
#include <linux/uinput.h>
#include <fnmatch.h>
#include <errno.h>
#include <poll.h>
#include "test.h"
#include "safe_macros.h"
#include "input_helper.h"
#define VIRTUAL_DEVICE "virtual-device-ltp"
#define VIRTUAL_DEVICE_REGEX "*virtual-device-ltp*"
static int uinput_loaded;
static int check_device(void);
static int try_open_device(void)
{
char path[256];
char name[256];
int ret, fd = -1;
unsigned int i;
for (i = 0; i < 100; i++) {
snprintf(path, sizeof(path), "/dev/input/event%i", i);
fd = open(path, O_RDONLY);
if (fd < 0 && errno == ENOENT)
continue;
if (fd < 0) {
tst_resm(TINFO | TERRNO, "failed to open %s", path);
break;
}
ret = ioctl(fd, EVIOCGNAME(sizeof(name)), name);
if (ret < 0) {
tst_resm(TINFO | TERRNO,
"ioctl(%s, EVIOCGNAME(256), ...) failed",
path);
break;
}
if (strcmp(name, VIRTUAL_DEVICE) == 0)
return fd;
close(fd);
}
return -1;
}
int open_device(void)
{
int fd;
int retries = 10;
while (retries--) {
fd = try_open_device();
if (fd > 0)
return fd;
tst_resm(TINFO, "Device not found, retrying...");
usleep(10000);
}
tst_brkm(TBROK, NULL, "Unable to find the input device");
}
static int try_load_uinput(void)
{
const char *argv[] = {"modprobe", "uinput", NULL};
int ret;
tst_resm(TINFO, "Trying to load uinput kernel module");
ret = tst_run_cmd(NULL, argv, NULL, NULL, 1);
if (ret) {
tst_resm(TINFO, "Failed to load the uinput module");
return 0;
}
return 1;
}
static void unload_uinput(void)
{
const char *argv[] = {"modprobe", "-r", "uinput", NULL};
int ret;
tst_resm(TINFO, "Unloading uinput kernel module");
ret = tst_run_cmd(NULL, argv, NULL, NULL, 1);
if (ret)
tst_resm(TWARN, "Failed to unload uinput module");
}
static const char *uinput_paths[] = {
"/dev/input/uinput",
"/dev/uinput",
};
static int try_open_uinput(void)
{
unsigned int i;
int fd;
for (i = 0; i < ARRAY_SIZE(uinput_paths); i++) {
fd = open(uinput_paths[i], O_WRONLY | O_NONBLOCK);
if (fd > 0) {
tst_resm(TINFO, "Found uinput dev at %s",
uinput_paths[i]);
return fd;
}
if (fd < 0 && errno != ENOENT) {
tst_brkm(TBROK | TERRNO, NULL,
"open(%s)", uinput_paths[i]);
}
}
return -1;
}
int open_uinput(void)
{
int fd;
int retries = 10;
fd = try_open_uinput();
if (fd > 0)
return fd;
if (try_load_uinput()) {
while (retries--) {
fd = try_open_uinput();
if (fd > 0) {
uinput_loaded = 1;
return fd;
}
tst_resm(TINFO, "Uinput dev not found, retrying...");
usleep(10000);
}
unload_uinput();
}
tst_brkm(TCONF, NULL, "Unable to find and open uinput");
}
void send_event(int fd, int event, int code, int value)
{
struct input_event ev = {
.type = event,
.code = code,
.value = value,
};
SAFE_WRITE(NULL, 1, fd, &ev, sizeof(ev));
}
void send_rel_move(int fd, int x, int y)
{
send_event(fd, EV_REL, REL_X, x);
send_event(fd, EV_REL, REL_Y, y);
send_event(fd, EV_SYN, 0, 0);
}
void create_device(int fd)
{
int nb;
struct uinput_user_dev uidev = {
.name = VIRTUAL_DEVICE,
.id = {
.bustype = BUS_USB,
.vendor = 0x1,
.product = 0x1,
.version = 1,
}
};
SAFE_WRITE(NULL, 1, fd, &uidev, sizeof(uidev));
SAFE_IOCTL(NULL, fd, UI_DEV_CREATE, NULL);
for (nb = 100; nb > 0; nb--) {
if (check_device())
return;
usleep(10000);
}
destroy_device(fd);
tst_brkm(TBROK, NULL, "Failed to create device");
}
void setup_mouse_events(int fd)
{
SAFE_IOCTL(NULL, fd, UI_SET_EVBIT, EV_KEY);
SAFE_IOCTL(NULL, fd, UI_SET_KEYBIT, BTN_LEFT);
SAFE_IOCTL(NULL, fd, UI_SET_EVBIT, EV_REL);
SAFE_IOCTL(NULL, fd, UI_SET_RELBIT, REL_X);
SAFE_IOCTL(NULL, fd, UI_SET_RELBIT, REL_Y);
}
void destroy_device(int fd)
{
SAFE_IOCTL(NULL, fd, UI_DEV_DESTROY, NULL);
SAFE_CLOSE(NULL, fd);
if (uinput_loaded)
unload_uinput();
}
int check_event_code(struct input_event *iev, int event, int code)
{
return iev->type == event && iev->code == code;
}
int check_sync_event(struct input_event *iev)
{
return check_event_code(iev, EV_SYN, SYN_REPORT);
}
/*
* the value of stray_sync_event:
* 0: EV_SYN/SYN_REPORT events should not be received in /dev/input/eventX
* 1: EV_SYN/SYN_REPORT events may be received in /dev/input/eventX
* On an old kernel(before v3.7.0), EV_SYN/SYN_REPORT events are always
* received even though we send empty moves.
*/
int no_events_queued(int fd, int stray_sync_event)
{
struct pollfd fds = {.fd = fd, .events = POLLIN};
int ret, res, sync_event_ignored;
struct input_event ev;
if (tst_kvercmp(3, 7, 0) < 0 && stray_sync_event)
sync_event_ignored = 1;
ret = poll(&fds, 1, 30);
if (ret > 0) {
res = read(fd, &ev, sizeof(ev));
if (res == sizeof(ev)) {
if (sync_event_ignored && check_sync_event(&ev)) {
ret = 0;
tst_resm(TINFO,
"Ignoring stray sync event (known problem)");
} else {
tst_resm(TINFO,
"Unexpected ev type=%i code=%i value=%i",
ev.type, ev.code, ev.value);
}
}
}
return ret == 0;
}
static int check_device(void)
{
FILE *file;
char line[256];
file = fopen("/proc/bus/input/devices", "r");
if (!file)
return 0;
while (fgets(line, 256, file)) {
if (fnmatch(VIRTUAL_DEVICE_REGEX, line, 0) == 0)
return 1;
}
fclose(file);
return 0;
}