aboutsummaryrefslogtreecommitdiffstats
path: root/surfaces
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-27 15:03:45 +0100
committereunhhu <[email protected]>2026-09-27 15:03:45 +0100
commitad3eb77af098f34c1ea8cb50f468a8b75fb7d61c (patch)
treefa38b131e428cb16d6ecf7bc616cacdde177a93e /surfaces
parent5313285bbfedf6154d2611c4923cff458d7ef271 (diff)
downloadvega-ad3eb77af098f34c1ea8cb50f468a8b75fb7d61c.tar.gz
vega-ad3eb77af098f34c1ea8cb50f468a8b75fb7d61c.zip
Take keyboards, mice and touch screens on the display (evdev)
vega display --live reads the kernel's input events with no library: keys become Pure's key names and the text they type on a US layout, going through the TUI's own handling (the input line takes text, else the app takes keys); a mouse moves an outlined pointer and clicks, a touch screen presses where it is touched, scaled from the device's range; the line pressed is the row. --input FILE reads recorded events to their end and prints the last frame, which is how this screenless Pi checks it: after typing two items with Shift, Enter, Tab, Space and a touch on a row, the display's last frame is the TUI's after the same keys and click, and came back from the display controller as drawn.
Diffstat (limited to 'surfaces')
-rw-r--r--surfaces/input.c190
-rw-r--r--surfaces/main.c125
-rw-r--r--surfaces/vega.h20
3 files changed, 324 insertions, 11 deletions
diff --git a/surfaces/input.c b/surfaces/input.c
new file mode 100644
index 0000000..b358d47
--- /dev/null
+++ b/surfaces/input.c
@@ -0,0 +1,190 @@
+#define _DEFAULT_SOURCE
+#include "vega.h"
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/input.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+/* Input from evdev (the kernel's input events): keyboards, mice and touch
+ * screens, read without a library. Keys come out as Pure's key names (as
+ * KeyboardEvent.code spells them) and the text they type on a US layout;
+ * pointers as presses at a pixel. */
+
+enum { DEVICES = 32 };
+
+typedef struct {
+ int fd;
+ bool touch; /* absolute positions, scaled to the screen */
+ int min[2], max[2]; /* the device's range on x and y */
+} Device;
+
+struct VegaInput {
+ Device devices[DEVICES];
+ unsigned count;
+ unsigned width, height; /* the screen, for pointers */
+ int x, y; /* the pointer */
+ bool shift, touched, moved;
+};
+
+/* Key names by code, with what a key types on a US layout (unshifted, shifted). */
+static const struct { unsigned short code; const char *name; char plain, shifted; } keys[] = {
+ {KEY_ESC, "Escape", 0, 0}, {KEY_1, "Digit1", '1', '!'}, {KEY_2, "Digit2", '2', '@'}, {KEY_3, "Digit3", '3', '#'},
+ {KEY_4, "Digit4", '4', '$'}, {KEY_5, "Digit5", '5', '%'}, {KEY_6, "Digit6", '6', '^'}, {KEY_7, "Digit7", '7', '&'},
+ {KEY_8, "Digit8", '8', '*'}, {KEY_9, "Digit9", '9', '('}, {KEY_0, "Digit0", '0', ')'}, {KEY_MINUS, "Minus", '-', '_'},
+ {KEY_EQUAL, "Equal", '=', '+'}, {KEY_BACKSPACE, "Backspace", 0, 0}, {KEY_TAB, "Tab", 0, 0},
+ {KEY_Q, "KeyQ", 'q', 'Q'}, {KEY_W, "KeyW", 'w', 'W'}, {KEY_E, "KeyE", 'e', 'E'}, {KEY_R, "KeyR", 'r', 'R'},
+ {KEY_T, "KeyT", 't', 'T'}, {KEY_Y, "KeyY", 'y', 'Y'}, {KEY_U, "KeyU", 'u', 'U'}, {KEY_I, "KeyI", 'i', 'I'},
+ {KEY_O, "KeyO", 'o', 'O'}, {KEY_P, "KeyP", 'p', 'P'}, {KEY_LEFTBRACE, "BracketLeft", '[', '{'},
+ {KEY_RIGHTBRACE, "BracketRight", ']', '}'}, {KEY_ENTER, "Enter", 0, 0}, {KEY_A, "KeyA", 'a', 'A'},
+ {KEY_S, "KeyS", 's', 'S'}, {KEY_D, "KeyD", 'd', 'D'}, {KEY_F, "KeyF", 'f', 'F'}, {KEY_G, "KeyG", 'g', 'G'},
+ {KEY_H, "KeyH", 'h', 'H'}, {KEY_J, "KeyJ", 'j', 'J'}, {KEY_K, "KeyK", 'k', 'K'}, {KEY_L, "KeyL", 'l', 'L'},
+ {KEY_SEMICOLON, "Semicolon", ';', ':'}, {KEY_APOSTROPHE, "Quote", '\'', '"'}, {KEY_GRAVE, "Backquote", '`', '~'},
+ {KEY_BACKSLASH, "Backslash", '\\', '|'}, {KEY_Z, "KeyZ", 'z', 'Z'}, {KEY_X, "KeyX", 'x', 'X'},
+ {KEY_C, "KeyC", 'c', 'C'}, {KEY_V, "KeyV", 'v', 'V'}, {KEY_B, "KeyB", 'b', 'B'}, {KEY_N, "KeyN", 'n', 'N'},
+ {KEY_M, "KeyM", 'm', 'M'}, {KEY_COMMA, "Comma", ',', '<'}, {KEY_DOT, "Period", '.', '>'},
+ {KEY_SLASH, "Slash", '/', '?'}, {KEY_SPACE, "Space", ' ', ' '}, {KEY_F1, "F1", 0, 0}, {KEY_F2, "F2", 0, 0},
+ {KEY_F3, "F3", 0, 0}, {KEY_F4, "F4", 0, 0}, {KEY_F5, "F5", 0, 0}, {KEY_F6, "F6", 0, 0}, {KEY_F7, "F7", 0, 0},
+ {KEY_F8, "F8", 0, 0}, {KEY_F9, "F9", 0, 0}, {KEY_F10, "F10", 0, 0}, {KEY_F11, "F11", 0, 0}, {KEY_F12, "F12", 0, 0},
+ {KEY_HOME, "Home", 0, 0}, {KEY_UP, "ArrowUp", 0, 0}, {KEY_PAGEUP, "PageUp", 0, 0}, {KEY_LEFT, "ArrowLeft", 0, 0},
+ {KEY_RIGHT, "ArrowRight", 0, 0}, {KEY_END, "End", 0, 0}, {KEY_DOWN, "ArrowDown", 0, 0},
+ {KEY_PAGEDOWN, "PageDown", 0, 0}, {KEY_INSERT, "Insert", 0, 0}, {KEY_DELETE, "Delete", 0, 0},
+};
+
+static bool has(int fd, unsigned type, unsigned code) {
+ unsigned long bits[KEY_MAX / (8 * sizeof(long)) + 1] = {0};
+ if (ioctl(fd, EVIOCGBIT(type, sizeof(bits)), bits) < 0) return false;
+ return bits[code / (8 * sizeof(long))] >> (code % (8 * sizeof(long))) & 1;
+}
+
+/* A device worth reading: one with keys, a relative pointer, or touch. */
+static bool add(VegaInput *input, int fd, bool probe) {
+ Device device = {.fd = fd};
+ if (probe) {
+ bool keys_ = has(fd, EV_KEY, KEY_A) || has(fd, EV_KEY, KEY_ENTER), mouse = has(fd, EV_REL, REL_X);
+ device.touch = has(fd, EV_ABS, ABS_MT_POSITION_X) || has(fd, EV_ABS, ABS_X);
+ if (!keys_ && !mouse && !device.touch) return false;
+ for (int axis = 0; device.touch && axis < 2; ++axis) {
+ struct input_absinfo range;
+ unsigned code = has(fd, EV_ABS, ABS_MT_POSITION_X) ? (axis ? ABS_MT_POSITION_Y : ABS_MT_POSITION_X)
+ : (axis ? ABS_Y : ABS_X);
+ if (ioctl(fd, EVIOCGABS(code), &range) < 0 || range.maximum <= range.minimum) device.touch = false;
+ else device.min[axis] = range.minimum, device.max[axis] = range.maximum;
+ }
+ } else device.touch = true, device.min[0] = device.min[1] = 0, device.max[0] = device.max[1] = 0; /* raw: screen pixels */
+ if (input->count == DEVICES) return false;
+ input->devices[input->count++] = device;
+ return true;
+}
+
+VegaInput *vega_input_open(const char *const *paths, unsigned count, unsigned width, unsigned height) {
+ VegaInput *input = calloc(1, sizeof(*input));
+ if (!input) return NULL;
+ input->width = width, input->height = height, input->x = (int)width / 2, input->y = (int)height / 2;
+ if (count) { /* files of recorded events, positions already in screen pixels */
+ for (unsigned i = 0; i < count; ++i) {
+ int fd = open(paths[i], O_RDONLY | O_CLOEXEC);
+ if (fd < 0 || !add(input, fd, false)) { if (fd >= 0) close(fd); vega_input_close(input); return NULL; }
+ }
+ return input;
+ }
+ DIR *directory = opendir("/dev/input");
+ struct dirent *entry;
+ while (directory && (entry = readdir(directory))) {
+ if (strncmp(entry->d_name, "event", 5)) continue;
+ char path[300];
+ snprintf(path, sizeof(path), "/dev/input/%s", entry->d_name);
+ int fd = open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
+ if (fd >= 0 && !add(input, fd, true)) close(fd);
+ }
+ if (directory) closedir(directory);
+ return input;
+}
+
+unsigned vega_input_fds(const VegaInput *input, struct pollfd *out, unsigned max) {
+ unsigned n = 0;
+ for (unsigned i = 0; i < input->count && n < max; ++i) out[n++] = (struct pollfd){input->devices[i].fd, POLLIN, 0};
+ return n;
+}
+
+static int scaled(const Device *device, int axis, int value, unsigned size) {
+ if (device->max[axis] <= device->min[axis]) return value; /* already in screen pixels */
+ long long span = device->max[axis] - device->min[axis];
+ return (int)((long long)(value - device->min[axis]) * (long long)(size - 1) / span);
+}
+
+static unsigned emit(VegaEvent *out, unsigned n, unsigned max, VegaEvent event) {
+ if (n < max) out[n++] = event;
+ return n;
+}
+
+/* One event of a device into what it means; returns the new count. */
+static unsigned translate(VegaInput *input, const Device *device, const struct input_event *e, VegaEvent *out, unsigned n,
+ unsigned max) {
+ if (e->type == EV_KEY && (e->code == KEY_LEFTSHIFT || e->code == KEY_RIGHTSHIFT)) {
+ input->shift = e->value != 0;
+ return n;
+ }
+ if (e->type == EV_KEY && (e->code == BTN_LEFT || e->code == BTN_TOUCH) && e->value == 1) {
+ if (e->code == BTN_TOUCH) input->touched = true;
+ return emit(out, n, max, (VegaEvent){VEGA_EVENT_PRESS, "", "", input->x, input->y, 0});
+ }
+ if (e->type == EV_KEY && e->value != 0) { /* a press or its repeat */
+ for (size_t i = 0; i < sizeof(keys) / sizeof(*keys); ++i)
+ if (keys[i].code == e->code) {
+ VegaEvent event = {VEGA_EVENT_KEY, "", "", input->x, input->y, 0};
+ snprintf(event.name, sizeof(event.name), "%s", keys[i].name);
+ char c = input->shift ? keys[i].shifted : keys[i].plain;
+ if (c) event.kind = VEGA_EVENT_TEXT, event.text[0] = c;
+ return emit(out, n, max, event);
+ }
+ return n;
+ }
+ if (e->type == EV_REL && (e->code == REL_X || e->code == REL_Y)) {
+ int *at = e->code == REL_X ? &input->x : &input->y, limit = (int)(e->code == REL_X ? input->width : input->height);
+ *at += e->value;
+ *at = *at < 0 ? 0 : *at >= limit ? limit - 1 : *at;
+ input->moved = true;
+ return n;
+ }
+ if (e->type == EV_REL && e->code == REL_WHEEL && e->value)
+ return emit(out, n, max, (VegaEvent){VEGA_EVENT_WHEEL, "", "", input->x, input->y, e->value > 0 ? -1 : 1});
+ if (e->type == EV_ABS && (e->code == ABS_MT_POSITION_X || e->code == ABS_X)) {
+ input->x = scaled(device, 0, e->value, input->width);
+ input->moved = true;
+ } else if (e->type == EV_ABS && (e->code == ABS_MT_POSITION_Y || e->code == ABS_Y)) {
+ input->y = scaled(device, 1, e->value, input->height);
+ input->moved = true;
+ }
+ return n;
+}
+
+unsigned vega_input_read(VegaInput *input, int fd, VegaEvent *out, unsigned max, bool *ended) {
+ Device *device = NULL;
+ for (unsigned i = 0; i < input->count; ++i) if (input->devices[i].fd == fd) device = &input->devices[i];
+ *ended = false;
+ if (!device) return 0;
+ struct input_event events[64];
+ ssize_t got = read(fd, events, sizeof(events));
+ if (got <= 0) {
+ *ended = got == 0 || (errno != EAGAIN && errno != EINTR);
+ return 0;
+ }
+ unsigned n = 0;
+ for (size_t i = 0; i < (size_t)got / sizeof(*events); ++i) n = translate(input, device, &events[i], out, n, max);
+ return n;
+}
+
+void vega_input_pointer(const VegaInput *input, int *x, int *y, bool *shown) {
+ *x = input->x, *y = input->y;
+ *shown = input->moved && !input->touched; /* a mouse has a pointer to see; a finger does not */
+}
+
+void vega_input_close(VegaInput *input) {
+ if (!input) return;
+ for (unsigned i = 0; i < input->count; ++i) close(input->devices[i].fd);
+ free(input);
+}
diff --git a/surfaces/main.c b/surfaces/main.c
index 0d02cce..26aa4eb 100644
--- a/surfaces/main.c
+++ b/surfaces/main.c
@@ -24,10 +24,13 @@
* the TUI's frames drawn with a font's glyphs, driven by a
* script: each show writes PREFIX-N.ppm after the text
* display --symbols FONT.vsym [--size PX] [--card PATH] [--writeback WxH --out PREFIX]
+ * [--live | --input EVENTS]
* the same on a display through KMS, as many cells as fit,
- * driven by a script; with writeback each shown frame comes
- * back from the display controller, is set against what was
- * drawn and written to PREFIX-N.ppm
+ * driven by a script, or live: keyboards, mice and touch
+ * screens (evdev), or events recorded in a file (read to its
+ * end, then the last frame shown as text); with writeback
+ * each shown frame comes back from the display controller,
+ * is set against what was drawn and written to PREFIX-N.ppm
* With --store the app's states live in that tree store: the app continues
* where it was, whichever surface runs it. With --watch (tui, web) a module
* written again replaces the running one and states keep their values by
@@ -499,12 +502,14 @@ static VegaDisplay *display;
/* A frame on the display; with writeback, what came back is set against it
* and written (differing: how many pixels are not what was drawn). */
-static bool show_frame(const VegaGrid *grid, char path[512], unsigned long *differing) {
+static bool show_frame(const VegaGrid *grid, char path[512], unsigned long *differing, bool compose) {
unsigned width, height;
size_t stride, captured_stride;
uint32_t *out = vega_display_buffer(display, &width, &height, &stride);
- vega_pixels_clear(out, stride, width, height);
- vega_pixels(grid, &face, out, stride);
+ if (compose) {
+ vega_pixels_clear(out, stride, width, height);
+ vega_pixels(grid, &face, out, stride);
+ }
if (!vega_display_show(display)) return false;
const uint32_t *back = vega_display_captured(display, &captured_stride);
*differing = 0;
@@ -532,6 +537,85 @@ static bool show_frame(const VegaGrid *grid, char path[512], unsigned long *diff
}
+/* ---- the display, live ---------------------------------------------- */
+
+static volatile sig_atomic_t stopping;
+static void on_stop(int signal) { (void)signal; stopping = 1; }
+
+static void show_pointer(uint32_t *out, size_t stride, int x, int y, unsigned width, unsigned height) {
+ /* The cell under a mouse pointer, outlined. */
+ int left = x / face.cell_width * face.cell_width, top = y / face.cell_height * face.cell_height;
+ for (int i = 0; i < face.cell_width; ++i)
+ for (int j = 0; j < face.cell_height; ++j)
+ if ((!i || !j || i == face.cell_width - 1 || j == face.cell_height - 1) && left + i < (int)width && top + j < (int)height)
+ out[(size_t)(top + j) * stride + (size_t)(left + i)] = 0xf5e0dc;
+}
+
+/* Draw, show, then wait for input or the next tick, until a signal (or the
+ * end of recorded events: the last frame is then printed as text). */
+static int live(VegaInput *input, unsigned columns, unsigned rows, bool recorded) {
+ VegaGrid grid = {0};
+ if (!vega_grid(&grid, columns, rows)) return 2;
+ drawn = &grid;
+ signal(SIGINT, on_stop);
+ signal(SIGTERM, on_stop);
+ VegaTimers timers;
+ vega_timers_start(&timers, app.module, vega_now());
+ char path[512] = "";
+ unsigned long differing = 0;
+ bool open_input = true;
+ int status = 0;
+ while (!stopping) {
+ bool ticked = false;
+ long due = vega_timers_run(&timers, &app, vega_now(), &ticked);
+ vega_draw(&grid, &app, &screen, entry, entry_length, typing);
+ unsigned width, height;
+ size_t stride;
+ uint32_t *out = vega_display_buffer(display, &width, &height, &stride);
+ vega_pixels_clear(out, stride, width, height);
+ vega_pixels(&grid, &face, out, stride);
+ int x, y;
+ bool pointer;
+ vega_input_pointer(input, &x, &y, &pointer);
+ if (pointer) show_pointer(out, stride, x, y, width, height);
+ if (!show_frame(&grid, path, &differing, false)) { fputs("vega: the display did not take the frame\n", stderr); status = 1; break; }
+ if (!open_input) break;
+ struct pollfd waits[32];
+ unsigned count = vega_input_fds(input, waits, 32);
+ int timeout = screen.ticks ? 16 : -1;
+ if (due >= 0 && (timeout < 0 || due < timeout)) timeout = (int)due;
+ if (poll(waits, count, timeout) <= 0) {
+ app.now = vega_now();
+ if (screen.ticks) pure_app_frame(&app);
+ continue;
+ }
+ app.now = vega_now();
+ for (unsigned i = 0; i < count; ++i) {
+ if (!(waits[i].revents & (POLLIN | POLLHUP | POLLERR))) continue;
+ VegaEvent events[64];
+ bool ended;
+ unsigned n = vega_input_read(input, waits[i].fd, events, 64, &ended);
+ for (unsigned k = 0; k < n; ++k) {
+ const VegaEvent *e = &events[k];
+ unsigned line = e->y >= 0 ? (unsigned)(e->y / face.cell_height) : 0;
+ if (e->kind == VEGA_EVENT_TEXT && typing) handle_text(e->text, strlen(e->text));
+ else if (e->kind == VEGA_EVENT_TEXT || e->kind == VEGA_EVENT_KEY) handle_key(e->name);
+ else if (e->kind == VEGA_EVENT_PRESS) mouse(0, line);
+ else if (e->kind == VEGA_EVENT_WHEEL) mouse(e->steps < 0 ? 64 : 65, line);
+ }
+ if (ended && recorded) open_input = false; /* draw once more, then stop */
+ }
+ }
+ if (recorded) {
+ dump(&grid);
+ if (path[0]) printf("frame %s differing %lu grid %ux%u cell %dx%d\n", path, differing, grid.width, grid.height, face.cell_width,
+ face.cell_height);
+ }
+ drawn = NULL;
+ vega_grid_free(&grid);
+ return status;
+}
+
/* painting: show writes the terminal bytes of the frame, not the grid. */
static int script(Surface surface, unsigned width, unsigned height, bool painting) {
static char line[1 << 16];
@@ -619,8 +703,9 @@ static int script(Surface surface, unsigned width, unsigned height, bool paintin
if (!write_frame(&grid, path)) { fprintf(stderr, "vega: cannot write %s\n", path); return 1; }
printf("frame %s %dx%d\n", path, (int)grid.width * face.cell_width, (int)grid.height * face.cell_height);
} else if (surface == SURFACE_DISPLAY) {
- if (!show_frame(&grid, path, &differing)) { fputs("vega: the display did not take the frame\n", stderr); return 1; }
- if (path[0]) printf("frame %s differing %lu grid %ux%u\n", path, differing, grid.width, grid.height);
+ if (!show_frame(&grid, path, &differing, true)) { fputs("vega: the display did not take the frame\n", stderr); return 1; }
+ if (path[0]) printf("frame %s differing %lu grid %ux%u cell %dx%d\n", path, differing, grid.width, grid.height, face.cell_width,
+ face.cell_height);
}
}
counted = vega_evaluations();
@@ -665,7 +750,8 @@ int main(int argc, char **argv) {
unsigned width = 0, height = 0, port = 8080;
const char *symbols_path = NULL, *card = "/dev/dri/card1";
float size = 16;
- bool writeback = false;
+ bool writeback = false, live_input = false;
+ const char *recorded = NULL;
bool dumped = false, painting = false, watch = false, usage = argc < 3;
const char *store = NULL, *listen_on = "127.0.0.1";
for (int i = 3; !usage && i < argc; i += 2) {
@@ -674,6 +760,11 @@ int main(int argc, char **argv) {
--i; /* a flag without a value */
continue;
}
+ if (!strcmp(argv[i], "--live") && !strcmp(argv[1], "display")) {
+ live_input = true;
+ --i;
+ continue;
+ }
if (i + 1 >= argc) usage = true;
else if (!strcmp(argv[i], "--store")) store = argv[i + 1];
else if (!strcmp(argv[i], "--listen") && !strcmp(argv[1], "web")) listen_on = argv[i + 1];
@@ -687,6 +778,7 @@ int main(int argc, char **argv) {
sscanf(argv[i + 1], "%ux%u", &width, &height) == 2 && width && height)
writeback = true;
else if (!strcmp(argv[i], "--card") && !strcmp(argv[1], "display")) card = argv[i + 1];
+ else if (!strcmp(argv[i], "--input") && !strcmp(argv[1], "display")) recorded = argv[i + 1];
else if (!strcmp(argv[i], "--symbols") && glyphed(argv[1])) symbols_path = argv[i + 1];
else if (!strcmp(argv[i], "--out") && glyphed(argv[1])) frames_prefix = argv[i + 1];
else if (!strcmp(argv[i], "--size") && glyphed(argv[1]) && sscanf(argv[i + 1], "%f", &size) == 1) {}
@@ -700,7 +792,7 @@ int main(int argc, char **argv) {
"vega web MODULE.pmod [--store PATH] [--listen ADDRESS] [--port N] [--watch]\n"
"vega pixels MODULE.pmod [--store PATH] --symbols FONT.vsym [--size PX] --grid WIDTHxHEIGHT --out PREFIX < script\n"
"vega display MODULE.pmod [--store PATH] --symbols FONT.vsym [--size PX] [--card PATH]\n"
- " [--writeback WIDTHxHEIGHT --out PREFIX] < script\n"
+ " [--writeback WIDTHxHEIGHT --out PREFIX] [--live | --input EVENTS] [< script]\n"
"vega check MODULE.pmod\n", stderr);
return 2;
}
@@ -730,7 +822,18 @@ int main(int argc, char **argv) {
watched = argv[2];
watched_digest = module_digest; /* of what load read */
}
- int status = surface == SURFACE_WEB ? vega_web(&app, &screen, listen_on, port, watched ? module_changed : NULL) :
+ int status;
+ if (on_display && (live_input || recorded)) {
+ unsigned pixels_across, pixels_down;
+ size_t stride;
+ vega_display_buffer(display, &pixels_across, &pixels_down, &stride);
+ const char *paths[1] = {recorded};
+ VegaInput *input = vega_input_open(paths, recorded ? 1 : 0, pixels_across, pixels_down);
+ if (!input) { fprintf(stderr, "vega: cannot read %s\n", recorded ? recorded : "input devices"); return 2; }
+ status = live(input, width, height, recorded != NULL);
+ vega_input_close(input);
+ } else
+ status = surface == SURFACE_WEB ? vega_web(&app, &screen, listen_on, port, watched ? module_changed : NULL) :
surface == SURFACE_TUI && !dumped ? interactive() : script(surface, width, height, painting);
/* Frame changes are stored with events; keep the last ones too. */
if (pure_app_save(&app) != PURE_OK) { fputs("vega: cannot save\n", stderr); status = status ? status : 1; }
diff --git a/surfaces/vega.h b/surfaces/vega.h
index 69eba0b..ba7b61f 100644
--- a/surfaces/vega.h
+++ b/surfaces/vega.h
@@ -2,6 +2,7 @@
#define VEGA_H
#include "module.h"
#include "symbols.h"
+#include <poll.h>
#include <stdio.h>
/* Vega reads a verified Pure module by convention (docs/design/001-first-component.md):
@@ -151,4 +152,23 @@ bool vega_display_show(VegaDisplay *);
/* Writeback: the last frame as the display controller put it out. */
const uint32_t *vega_display_captured(const VegaDisplay *, size_t *stride);
void vega_display_close(VegaDisplay *);
+
+/* Input from evdev: keyboards (key names and the text they type on a US
+ * layout), mice and touch screens (presses at a pixel of a width x height
+ * screen). With paths, recorded events are read from those files instead,
+ * positions in screen pixels. */
+typedef enum { VEGA_EVENT_KEY, VEGA_EVENT_TEXT, VEGA_EVENT_PRESS, VEGA_EVENT_WHEEL } VegaEventKind;
+typedef struct {
+ VegaEventKind kind;
+ char name[24], text[8]; /* a key's name; the text it types */
+ int x, y, steps; /* where a press or the wheel is; steps down (negative up) */
+} VegaEvent;
+typedef struct VegaInput VegaInput;
+VegaInput *vega_input_open(const char *const *paths, unsigned count, unsigned width, unsigned height);
+unsigned vega_input_fds(const VegaInput *, struct pollfd *out, unsigned max);
+/* What the device on fd has for us; ended: it has no more (a file read out). */
+unsigned vega_input_read(VegaInput *, int fd, VegaEvent *out, unsigned max, bool *ended);
+/* Where the pointer is, and whether a mouse moved it (a finger's is not shown). */
+void vega_input_pointer(const VegaInput *, int *x, int *y, bool *shown);
+void vega_input_close(VegaInput *);
#endif