libei/tools/eis-demo-server.c
Jason Gerecke acff519ac3 tools: Support EIS_EVENT_DEVICE_READY in eis-demo-server
We move the calls to resume and start emulation of new devices into a
handler that gets called only once a device is ready. We also set the
EIS_FLAG_DEVICE_READY flag to inform libeis that we want to process
these ready events.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/346>
2025-10-17 13:38:47 +10:00

828 lines
22 KiB
C

/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2020 Red Hat, Inc.
*
* 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.
*/
/* A simple tool that provides an EIS implementation that prints incoming
* events.
*
* This tool is useful for testing libei event flow, to make sure the
* connection works and events arrive at the EIS implementation.
*
* The server has hardcoded regions and creates fixed devices, it's really
* just for basic purposes.
*
* Usually, you'd want to:
* - run the eis-demo-server (or some other EIS implementation)
* - export LIBEI_SOCKET=eis-0, or whatever value was given
* - run the libei client
*/
#include "config.h"
#include <assert.h>
#include <errno.h>
#include <getopt.h>
#include <poll.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <sys/mman.h>
#include <linux/input.h>
#include <inttypes.h>
#if HAVE_LIBXKBCOMMON
#include <xkbcommon/xkbcommon.h>
#endif
#include "src/util-color.h"
#include "src/util-mem.h"
#include "src/util-memfile.h"
#include "src/util-strings.h"
#include "src/util-time.h"
#include "eis-demo-server.h"
static bool stop = false;
static void sighandler(int signal) {
stop = true;
}
static
OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_demo_client);
static void
log_handler(struct eis *eis, enum eis_log_priority priority,
const char *message, struct eis_log_context *ctx)
{
struct lut {
const char *color;
const char *prefix;
} lut[] = {
{ .color = ansi_colorcode[RED], .prefix = "<undefined>", }, /* debug starts at 10 */
{ .color = ansi_colorcode[HIGHLIGHT], .prefix = "DEBUG", },
{ .color = ansi_colorcode[GREEN], .prefix = "INFO", },
{ .color = ansi_colorcode[BLUE], .prefix = "WARN", },
{ .color = ansi_colorcode[RED], .prefix = "ERROR", },
};
static time_t last_time = 0;
const char *reset_code = ansi_colorcode[RESET];
run_only_once {
if (!isatty(STDOUT_FILENO)) {
struct lut *l;
ARRAY_FOR_EACH(lut, l)
l->color = "";
reset_code = "";
}
}
time_t now = time(NULL);
char timestamp[64];
if (last_time != now) {
struct tm *tm = localtime(&now);
strftime(timestamp, sizeof(timestamp), "%T", tm);
} else {
xsnprintf(timestamp, sizeof(timestamp), "...");
}
size_t idx = priority/10;
assert(idx < ARRAY_LENGTH(lut));
fprintf(stdout, " EIS: %8s | %s%4s%s | %s\n", timestamp,
lut[idx].color, lut[idx].prefix, reset_code, message);
last_time = now;
}
static void
eis_demo_client_destroy(struct eis_demo_client *democlient)
{
list_remove(&democlient->link);
eis_client_unref(democlient->client);
eis_device_unref(democlient->ptr);
eis_device_unref(democlient->abs);
eis_device_unref(democlient->kbd);
eis_device_unref(democlient->touchscreen);
}
static
OBJECT_IMPLEMENT_CREATE(eis_demo_client);
static struct eis_demo_client *
eis_demo_client_new(struct eis_demo_server *server, struct eis_client *client)
{
struct eis_demo_client *c = eis_demo_client_create(NULL);
c->client = eis_client_ref(client);
list_append(&server->clients, &c->link);
return c;
}
static struct eis_demo_client *
eis_demo_client_find(struct eis_demo_server *server, struct eis_client *client)
{
struct eis_demo_client *c;
list_for_each(c, &server->clients, link) {
if (c->client == client)
return c;
}
return NULL;
}
DEFINE_UNREF_CLEANUP_FUNC(eis);
DEFINE_UNREF_CLEANUP_FUNC(eis_event);
DEFINE_UNREF_CLEANUP_FUNC(eis_keymap);
DEFINE_UNREF_CLEANUP_FUNC(eis_seat);
DEFINE_UNREF_CLEANUP_FUNC(eis_region);
static void unlink_free(char **path) {
if (*path) {
unlink(*path);
free(*path);
}
}
#define _cleanup_unlink_free_ _cleanup_(unlink_free)
static inline void
_printf_(1, 2)
colorprint(const char *format, ...)
{
static uint64_t color = 0;
run_only_once {
color = rgb(1, 1, 1) | rgb_bg(255, 127, 0);
}
const char prefix[] = "EIS socket server: ";
char buf[1024];
snprintf(buf, sizeof(buf), "%s", prefix);
va_list args;
va_start(args, format);
vsnprintf(buf + strlen(prefix), sizeof(buf) - strlen(prefix), format, args);
va_end(args);
cprintf(color, "%s", buf);
}
#if HAVE_MEMFD_CREATE && HAVE_LIBXKBCOMMON
DEFINE_UNREF_CLEANUP_FUNC(xkb_context);
DEFINE_UNREF_CLEANUP_FUNC(xkb_keymap);
DEFINE_UNREF_CLEANUP_FUNC(xkb_state);
#endif
static void
setup_keymap(struct eis_demo_server *server, struct eis_device *device)
{
#if HAVE_MEMFD_CREATE && HAVE_LIBXKBCOMMON
colorprint("Using server layout: %s\n", server->layout);
_unref_(xkb_context) *ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!ctx)
return;
struct xkb_rule_names names = {
.rules = "evdev",
.model = "pc105",
.layout = server->layout,
};
_unref_(xkb_keymap) *keymap = xkb_keymap_new_from_names(ctx, &names, 0);
if (!keymap)
return;
const char *str = xkb_keymap_get_as_string(keymap, XKB_KEYMAP_FORMAT_TEXT_V1);
size_t len = strlen(str) - 1;
struct memfile *f = memfile_new(str, len);
if (!f)
return;
_unref_(eis_keymap) *k = eis_device_new_keymap(device,
EIS_KEYMAP_TYPE_XKB, memfile_get_fd(f),
memfile_get_size(f));
eis_keymap_add(k);
memfile_unref(f);
_unref_(xkb_state) *state = xkb_state_new(keymap);
if (!state)
return;
server->ctx = steal(&ctx);
server->keymap = steal(&keymap);
server->state = steal(&state);
#endif
}
static void
handle_key(struct eis_demo_server *server, uint32_t keycode, bool is_press)
{
char keysym_name[64] = {0};
#if HAVE_LIBXKBCOMMON
if (server->state) {
uint32_t xkbkc = keycode + 8;
xkb_state_update_key(server->state, xkbkc, is_press ? XKB_KEY_DOWN : XKB_KEY_UP);
xkb_state_key_get_utf8(server->state, xkbkc, keysym_name, sizeof(keysym_name));
}
#endif
colorprint("key %u (%s) [%s]\n",
keycode, is_press ? "press" : "release",
keysym_name);
}
static struct eis_device *
add_device(struct eis_demo_server *server, struct eis_client *client,
struct eis_seat *seat, enum eis_device_capability cap)
{
struct eis_device *device = NULL;
switch (cap) {
case EIS_DEVICE_CAP_POINTER:
{
struct eis_device *ptr = eis_seat_new_device(seat);
eis_device_configure_name(ptr, "test pointer");
eis_device_configure_capability(ptr, EIS_DEVICE_CAP_POINTER);
eis_device_configure_capability(ptr, EIS_DEVICE_CAP_BUTTON);
eis_device_configure_capability(ptr, EIS_DEVICE_CAP_SCROLL);
_unref_(eis_region) *rel_region = eis_device_new_region(ptr);
eis_region_set_mapping_id(rel_region, "demo region");
eis_region_set_size(rel_region, 1920, 1080);
eis_region_set_offset(rel_region, 0, 0);
eis_region_add(rel_region);
colorprint("Creating pointer device %s for %s\n", eis_device_get_name(ptr),
eis_client_get_name(client));
eis_device_add(ptr);
device = steal(&ptr);
break;
}
case EIS_DEVICE_CAP_POINTER_ABSOLUTE:
{
struct eis_device *abs = eis_seat_new_device(seat);
eis_device_configure_name(abs, "test abs pointer");
eis_device_configure_capability(abs, EIS_DEVICE_CAP_POINTER_ABSOLUTE);
eis_device_configure_capability(abs, EIS_DEVICE_CAP_BUTTON);
eis_device_configure_capability(abs, EIS_DEVICE_CAP_SCROLL);
_unref_(eis_region) *region = eis_device_new_region(abs);
eis_region_set_mapping_id(region, "demo region");
eis_region_set_size(region, 1920, 1080);
eis_region_set_offset(region, 0, 0);
eis_region_add(region);
colorprint("Creating abs pointer device %s for %s\n", eis_device_get_name(abs),
eis_client_get_name(client));
eis_device_add(abs);
device = steal(&abs);
break;
}
case EIS_DEVICE_CAP_KEYBOARD:
{
struct eis_device *kbd = eis_seat_new_device(seat);
eis_device_configure_name(kbd, "test keyboard");
eis_device_configure_capability(kbd, EIS_DEVICE_CAP_KEYBOARD);
if (server->layout)
setup_keymap(server, kbd);
colorprint("Creating keyboard device %s for %s\n", eis_device_get_name(kbd),
eis_client_get_name(client));
eis_device_add(kbd);
device = steal(&kbd);
break;
}
case EIS_DEVICE_CAP_TOUCH:
{
struct eis_device *touchscreen = eis_seat_new_device(seat);
eis_device_configure_name(touchscreen, "test touchscreen");
eis_device_configure_capability(touchscreen, EIS_DEVICE_CAP_TOUCH);
colorprint("Creating touchscreen device %s for %s\n", eis_device_get_name(touchscreen),
eis_client_get_name(client));
eis_device_add(touchscreen);
device = steal(&touchscreen);
break;
}
case EIS_DEVICE_CAP_BUTTON:
case EIS_DEVICE_CAP_SCROLL:
/* Mixed in with pointer/abs - good enough for a demo server */
break;
}
return device;
}
static void
resume_device(struct eis_device *device, struct eis_client *client)
{
static uint32_t sequence;
colorprint("Resuming device %s for %s\n", eis_device_get_name(device),
eis_client_get_name(client));
eis_device_resume(device);
if (!eis_client_is_sender(client))
eis_device_start_emulating(device, ++sequence);
}
/**
* The simplest event handler. Connect any client and any device and just
* printf the events as the come in. This is an incomplete implementation,
* it just does the basics for pointers and keyboards atm.
*/
static int
eis_demo_server_printf_handle_event(struct eis_demo_server *server,
struct eis_event *e)
{
switch(eis_event_get_type(e)) {
case EIS_EVENT_CLIENT_CONNECT:
{
struct eis_client *client = eis_event_get_client(e);
bool is_sender = eis_client_is_sender(client);
colorprint("new %s client: %s\n",
is_sender ? "sender" : "receiver",
eis_client_get_name(client));
eis_demo_client_new(server, client);
if (!is_sender)
server->nreceiver_clients++;
/* insert sophisticated authentication here */
eis_client_connect(client);
colorprint("accepting client, creating new seat 'default'\n");
_unref_(eis_seat) *seat = eis_client_new_seat(client, "default");
eis_seat_configure_capability(seat, EIS_DEVICE_CAP_POINTER);
eis_seat_configure_capability(seat, EIS_DEVICE_CAP_POINTER_ABSOLUTE);
eis_seat_configure_capability(seat, EIS_DEVICE_CAP_KEYBOARD);
eis_seat_configure_capability(seat, EIS_DEVICE_CAP_TOUCH);
eis_seat_configure_capability(seat, EIS_DEVICE_CAP_BUTTON);
eis_seat_configure_capability(seat, EIS_DEVICE_CAP_SCROLL);
eis_seat_add(seat);
/* Note: we don't have a ref to this seat ourselves anywhere */
break;
}
case EIS_EVENT_CLIENT_DISCONNECT:
{
struct eis_client *client = eis_event_get_client(e);
bool is_sender = eis_client_is_sender(client);
if (!is_sender)
server->nreceiver_clients--;
colorprint("client %s disconnected\n", eis_client_get_name(client));
eis_client_disconnect(client);
eis_demo_client_unref(eis_demo_client_find(server, client));
break;
}
case EIS_EVENT_SEAT_BIND:
{
struct eis_client *client = eis_event_get_client(e);
struct eis_demo_client *democlient = eis_demo_client_find(server, client);
assert(democlient);
struct eis_seat *seat = eis_event_get_seat(e);
if (eis_event_seat_has_capability(e, EIS_DEVICE_CAP_POINTER)) {
if (!democlient->ptr)
democlient->ptr = add_device(server, client, seat, EIS_DEVICE_CAP_POINTER);
} else {
if (democlient->ptr) {
eis_device_remove(democlient->ptr);
democlient->ptr = eis_device_unref(democlient->ptr);
}
}
if (eis_event_seat_has_capability(e, EIS_DEVICE_CAP_POINTER_ABSOLUTE)) {
if (!democlient->abs)
democlient->abs = add_device(server, client, seat, EIS_DEVICE_CAP_POINTER_ABSOLUTE);
} else {
if (democlient->abs) {
eis_device_remove(democlient->abs);
democlient->abs = eis_device_unref(democlient->abs);
}
}
if (eis_event_seat_has_capability(e, EIS_DEVICE_CAP_KEYBOARD)) {
if (!democlient->kbd)
democlient->kbd = add_device(server, client, seat, EIS_DEVICE_CAP_KEYBOARD);
} else {
if (democlient->kbd) {
eis_device_remove(democlient->kbd);
democlient->kbd = eis_device_unref(democlient->kbd);
}
}
if (eis_event_seat_has_capability(e, EIS_DEVICE_CAP_TOUCH)) {
if (!democlient->touchscreen)
democlient->touchscreen = add_device(server, client, seat, EIS_DEVICE_CAP_TOUCH);
} else {
if (democlient->touchscreen) {
eis_device_remove(democlient->touchscreen);
democlient->touchscreen = eis_device_unref(democlient->touchscreen);
}
}
/* Special "Feature", if all caps are unbound remove the seat.
* This is a demo server after all, so let's demo this. */
if (!eis_event_seat_has_capability(e, EIS_DEVICE_CAP_POINTER) &&
!eis_event_seat_has_capability(e, EIS_DEVICE_CAP_POINTER_ABSOLUTE) &&
!eis_event_seat_has_capability(e, EIS_DEVICE_CAP_KEYBOARD) &&
!eis_event_seat_has_capability(e, EIS_DEVICE_CAP_TOUCH))
eis_seat_remove(seat);
break;
}
case EIS_EVENT_DEVICE_READY:
{
struct eis_client *client = eis_event_get_client(e);
struct eis_demo_client *democlient = eis_demo_client_find(server, client);
assert(democlient);
struct eis_device *device = eis_event_get_device(e);
colorprint("Device %s is ready to send events\n", eis_device_get_name(device));
resume_device(device, client);
break;
}
case EIS_EVENT_DEVICE_CLOSED:
{
struct eis_client *client = eis_event_get_client(e);
struct eis_demo_client *democlient = eis_demo_client_find(server, client);
assert(democlient);
struct eis_device *device = eis_event_get_device(e);
eis_device_remove(device);
if (democlient->ptr == device)
democlient->ptr = NULL;
if (democlient->abs == device)
democlient->abs = NULL;
if (democlient->kbd == device)
democlient->kbd = NULL;
if (democlient->touchscreen == device)
democlient->touchscreen = NULL;
eis_device_unref(device);
}
break;
case EIS_EVENT_DEVICE_START_EMULATING:
{
struct eis_device *device = eis_event_get_device(e);
colorprint("Device %s may start sending events\n", eis_device_get_name(device));
}
break;
case EIS_EVENT_DEVICE_STOP_EMULATING:
{
struct eis_device *device = eis_event_get_device(e);
colorprint("Device %s will no longer send events\n", eis_device_get_name(device));
}
break;
case EIS_EVENT_POINTER_MOTION:
{
colorprint("motion by %.2f/%.2f\n",
eis_event_pointer_get_dx(e),
eis_event_pointer_get_dy(e));
}
break;
case EIS_EVENT_POINTER_MOTION_ABSOLUTE:
{
colorprint("absmotion to %.2f/%.2f\n",
eis_event_pointer_get_absolute_x(e),
eis_event_pointer_get_absolute_y(e));
}
break;
case EIS_EVENT_BUTTON_BUTTON:
{
colorprint("button %u (%s)\n",
eis_event_button_get_button(e),
eis_event_button_get_is_press(e) ? "press" : "release");
}
break;
case EIS_EVENT_SCROLL_DELTA:
{
colorprint("scroll %.2f/%.2f\n",
eis_event_scroll_get_dx(e),
eis_event_scroll_get_dy(e));
}
break;
case EIS_EVENT_SCROLL_DISCRETE:
{
colorprint("scroll discrete %d/%d\n",
eis_event_scroll_get_discrete_dx(e),
eis_event_scroll_get_discrete_dy(e));
}
break;
case EIS_EVENT_KEYBOARD_KEY:
{
handle_key(server,
eis_event_keyboard_get_key(e),
eis_event_keyboard_get_key_is_press(e));
}
break;
case EIS_EVENT_TOUCH_DOWN:
case EIS_EVENT_TOUCH_MOTION:
{
colorprint("touch %s %u %.2f/%.2f\n",
eis_event_get_type(e) == EIS_EVENT_TOUCH_DOWN ? "down" : "motion",
eis_event_touch_get_id(e),
eis_event_touch_get_x(e),
eis_event_touch_get_y(e));
}
break;
case EIS_EVENT_TOUCH_UP:
{
colorprint("touch up %u\n", eis_event_touch_get_id(e));
}
break;
case EIS_EVENT_FRAME:
{
colorprint("frame timestamp: %" PRIu64 "\n",
eis_event_get_time(e));
}
break;
case EIS_EVENT_PONG:
{
colorprint("pong\n");
}
break;
case EIS_EVENT_SYNC:
{
colorprint("sync\n");
}
break;
default:
/* This is a demo server and we abort to make it easy to debug
* a missing implementation of some new event.
*
* Do not do this in a real EIS implementation, unknown
* events must be passed to eis_event_unref() and otherwise
* ignored.
*/
abort();
}
return 0;
}
static void
usage(FILE *fp, const char *argv0)
{
fprintf(fp,
"Usage: %s [--verbose] [--uinput] [--socketpath=/path/to/socket] [--interval=1000]\n"
"\n"
"Start an EIS demo server. The server accepts all client connections\n"
"and devices and prints any events from the client to stdout.\n"
"\n"
"Options:\n"
" --socketpath Use the given socket path. Default: $XDG_RUNTIME_DIR/eis-0\n"
" --layout Use the given XKB layout (requires libxkbcommon). Default: none\n"
" --uinput Set up each device as uinput device (this requires root)\n"
" --interval Interval in milliseconds between polling\n"
" --verbose Enable debugging output\n"
"",
argv0);
}
int main(int argc, char **argv)
{
bool verbose = false;
bool uinput = false;
unsigned int interval = 1000;
const char *layout = NULL;
_cleanup_unlink_free_ char *socketpath = NULL;
const char *xdg = getenv("XDG_RUNTIME_DIR");
if (xdg)
socketpath = xaprintf("%s/eis-0", xdg);
while (true) {
enum {
OPT_VERBOSE,
OPT_LAYOUT,
OPT_SOCKETPATH,
OPT_UINPUT,
OPT_INTERVAL,
};
static struct option long_opts[] = {
{"socketpath", required_argument, 0, OPT_SOCKETPATH},
{"layout", required_argument, 0, OPT_LAYOUT},
{"uinput", no_argument, 0, OPT_UINPUT},
{"verbose", no_argument, 0, OPT_VERBOSE},
{"interval", required_argument, 0, OPT_INTERVAL},
{"help", no_argument, 0, 'h'},
{NULL},
};
int optind = 0;
int c = getopt_long(argc, argv, "h", long_opts, &optind);
if (c == -1)
break;
switch(c) {
case 'h':
usage(stdout, argv[0]);
return EXIT_SUCCESS;
case OPT_SOCKETPATH:
free(socketpath);
socketpath = xstrdup(optarg);
break;
case OPT_LAYOUT:
layout = optarg;
break;
case OPT_UINPUT:
uinput = true;
break;
case OPT_VERBOSE:
verbose = true;
break;
case OPT_INTERVAL:
interval = atoi(optarg);
break;
default:
usage(stderr, argv[0]);
return EXIT_FAILURE;
}
}
if (socketpath == NULL) {
fprintf(stderr, "No socketpath given and $XDG_RUNTIME_DIR is not set\n");
return EXIT_FAILURE;
}
struct eis_demo_server server = {
.layout = layout,
.handler.handle_event = eis_demo_server_printf_handle_event,
};
list_init(&server.clients);
if (uinput) {
int rc = -ENOTSUP;
#if HAVE_LIBEVDEV
rc = eis_demo_server_setup_uinput_handler(&server);
#endif
if (rc != 0) {
fprintf(stderr, "Failed to set up uinput handler: %s\n", strerror(-rc));
return EXIT_FAILURE;
}
}
_unref_(eis) *eis = eis_new(NULL);
assert(eis);
if (verbose) {
eis_log_set_priority(eis, EIS_LOG_PRIORITY_DEBUG);
eis_log_set_handler(eis, log_handler);
}
signal(SIGINT, sighandler);
if (eis_set_flag(eis, EIS_FLAG_DEVICE_READY)) {
fprintf(stderr, "set flag failed: %s\n", strerror(errno));
return 1;
}
int rc = eis_setup_backend_socket(eis, socketpath);
if (rc != 0) {
fprintf(stderr, "init failed: %s\n", strerror(errno));
return 1;
}
colorprint("waiting on %s\n", socketpath);
struct pollfd fds = {
.fd = eis_get_fd(eis),
.events = POLLIN,
.revents = 0,
};
int nevents;
while (!stop && (nevents = poll(&fds, 1, interval)) > -1) {
if (nevents == 0 && server.nreceiver_clients == 0)
continue;
uint64_t now = eis_now(eis);
colorprint("now: %" PRIu64 "\n", now);
eis_dispatch(eis);
while (true) {
_unref_(eis_event) *e = eis_get_event(eis);
if (!e)
break;
int rc = server.handler.handle_event(&server, e);
if (rc != 0)
break;
}
struct eis_demo_client *democlient;
const int interval = ms2us(12); /* events are 12ms apart */
list_for_each(democlient, &server.clients, link) {
if (eis_client_is_sender(democlient->client))
continue;
struct eis_device *ptr = democlient->ptr;
struct eis_device *kbd = democlient->kbd;
struct eis_device *abs = democlient->abs;
struct eis_device *touchscreen = democlient->touchscreen;
if (ptr) {
colorprint("sending motion event\n");
eis_device_pointer_motion(ptr, -1, 1);
/* BTN_LEFT */
colorprint("sending button event\n");
eis_device_button_button(ptr, BTN_LEFT, true);
eis_device_frame(ptr, now);
now += interval;
eis_device_button_button(ptr, BTN_LEFT, false);
eis_device_frame(ptr, now);
now += interval;
colorprint("sending scroll events\n");
eis_device_scroll_delta(ptr, 1, 1);
eis_device_frame(ptr, now);
now += interval;
eis_device_scroll_discrete(ptr, 120, 120);
eis_device_frame(ptr, now);
now += interval;
}
if (kbd) {
static int key = 0;
colorprint("sending key event\n");
eis_device_keyboard_key(kbd, KEY_Q + key, true); /* KEY_Q */
eis_device_frame(kbd, now);
now += interval;
eis_device_keyboard_key(kbd, KEY_Q + key, false); /* KEY_Q */
eis_device_frame(kbd, now);
now += interval;
key = (key + 1) % 6;
}
if (abs) {
static int x, y;
colorprint("sending abs event\n");
eis_device_pointer_motion_absolute(abs, 150 + ++x, 150 - ++y);
eis_device_frame(abs, now);
now += interval;
}
if (touchscreen) {
static int x, y;
static int counter = 0;
struct eis_touch *touch = democlient->touch;
switch (counter++ % 5) {
case 0:
touch = eis_device_touch_new(touchscreen);
if (touch) { /* NULL if client was disconnected internally already */
colorprint("sending touch down event\n");
eis_touch_down(touch, 100 + ++x, 200 - ++y);
eis_device_frame(touchscreen, now);
democlient->touch = touch;
}
break;
case 4:
if (touch) {
colorprint("sending touch down event\n");
eis_touch_up(touch);
eis_device_frame(touchscreen, now);
democlient->touch = eis_touch_unref(touch);
}
break;
default:
if (touch) {
eis_touch_motion(touch, 100 + ++x, 200 - ++y);
eis_device_frame(touchscreen, now);
}
break;
}
}
}
}
struct eis_demo_client *democlient;
list_for_each_safe(democlient, &server.clients, link) {
eis_demo_client_unref(democlient);
}
return 0;
}