Using Linux uinput From a Test Script

[spb_text_block pb_margin_bottom="no" pb_border_bottom="no" width="1/1" el_position="first last"]

With UI testing, one may need the Squish API for so-called native functions. The Squish native functions, as well as the mouse* and keyboard* functions, use the by the windowing system provided methods.

Toolkit specific APIs, like mouseClick, may also use native functions but might as well post events within the toolkit event queue. The point is that with the mouse* functions one can move the mouse and control click timings. Hovering the mouse may be necessary before clicking because the object identification may change upon mouse hover.

However, when targeting an embedded Linux device, not using X11 but e.g. Linux framebuffer or Wayland, there aren't such functions. (For various Wayland compositors there will be a solution coming to Squish soon).

One way to get around this limitation is to create your own input device based on uinput 1. To interact with such an input device, the process implementing the fake device needs to read from somewhere. The easiest solution is probably to let such a program read from its standard input.
I'm going to use a named pipe, where one can write a line of text to and have the device program read the line from the named pipe. One caveat to overcome is that a single write using echo "x y z" > my-pipe will cause a close of the device process standard input. To keep the input open, at least one writer must keep the pipe open. A suggestion is to redirect the standard error of the shell that runs the device program, like this
[code language="shell"]
mkfifo /tmp/input
exec 3>/tmp/input &
sudo ./uinput < /tmp/input
[/code]
Which means the shell that runs the mouse device program must be kept open. (One could use screen if a permanent ssh connection is a problem).
Note that accessing /dev/uinput likely requires root privileges.

From the test script one can then use the Squish RemoteSystem API, e.g.
[code language="javascript"]
var sys = new RemoteSystem();
sys.execute(["/bin/sh", "-c", "echo 'm 50 10' > /tmp/input"])
sys.execute(["/bin/sh", "-c", "echo 'c' > /tmp/input"])
[/code]
to move the mouse 50 pixels to the right, 10 down and click.

1 Code listing

[code language="cpp" title="Mouse device with simple input protocol"]
/* gcc -o uinput uinput.c */
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/uinput.h>

static void emit(int fd, int type, int code, int val) {
struct input_event ie;

ie.type = type;
ie.code = code;
ie.value = val;
ie.time.tv_sec = 0;
ie.time.tv_usec = 0;

write(fd, &ie, sizeof(ie));
}

void deleteDevice(int fd) {
if (fd > 0) {
ioctl(fd, UI_DEV_DESTROY);
close(fd);
}
}

int setupMouse() {
struct uinput_setup usetup;
int i = 50;

int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
if (fd < 0) {
fprintf(stderr, "failed to open device %s\n", strerror(errno));
return;
}
/* enable mouse button left and relative events */
ioctl(fd, UI_SET_EVBIT, EV_KEY);
ioctl(fd, UI_SET_KEYBIT, BTN_LEFT);

ioctl(fd, UI_SET_EVBIT, EV_REL);
ioctl(fd, UI_SET_RELBIT, REL_X);
ioctl(fd, UI_SET_RELBIT, REL_Y);

memset(&usetup, 0, sizeof(usetup));
usetup.id.bustype = BUS_USB;
usetup.id.vendor = 0x1234; /* sample vendor */
usetup.id.product = 0x5678; /* sample product */
strcpy(usetup.name, "Example device");

ioctl(fd, UI_DEV_SETUP, &usetup);
ioctl(fd, UI_DEV_CREATE);
sleep(1);
return fd;
}

void pointerClick(int fd) {
emit(fd, EV_KEY, BTN_MOUSE, 1);
emit(fd, EV_SYN, SYN_REPORT, 0);
emit(fd, EV_KEY, BTN_MOUSE, 0);
emit(fd, EV_SYN, SYN_REPORT, 0);
}

void pointerMove(int fd, int x, int y) {
emit(fd, EV_REL, REL_X, x);
emit(fd, EV_REL, REL_Y, y);
emit(fd, EV_SYN, SYN_REPORT, 0);
}

void sighandler(int i) {
(void)i;
close(0);
}

int main(void) {
int done = 0;
int fd = setupMouse();

signal(SIGINT, sighandler);
signal(SIGTERM, sighandler);
signal(SIGPIPE, SIG_IGN);

while (!done) {
char ev;
int key, x, y;
int count = scanf("%c", &ev);
if (count <= 0 || ev <= 0) {
printf("count %d ev %d\n", count, ev);
break;
}
switch (ev) {
case 'c':
pointerClick(fd);
break;
case 'm':
count = scanf(" %d %d", &x, &y);
if (count != 2) {
done = 1;
} else {
pointerMove(fd, x, y);
}
break;
}
}
deleteDevice(fd);

return 0;
}
[/code]

Comments