aboutsummaryrefslogtreecommitdiffstats
path: root/surfaces
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-27 19:37:33 +0100
committereunhhu <[email protected]>2026-09-27 19:37:33 +0100
commit5fe8104744d3e0a48674b1368db6fd104a10bdd4 (patch)
treeed9ea83c6a84a09136f40dc7ff6141968e75faec /surfaces
parentad3eb77af098f34c1ea8cb50f468a8b75fb7d61c (diff)
downloadvega-5fe8104744d3e0a48674b1368db6fd104a10bdd4.tar.gz
vega-5fe8104744d3e0a48674b1368db6fd104a10bdd4.zip
Show a Pure node's app on its display, as a surface in the node
The vega host builds a second way, as build/vega-surface.so, which pure-serve maps and runs beside its connection threads (pure-serve --surface vega-surface.so -- display ..., Pure's design 010). There the app it draws is a reader of the node's last save, read where it is, and what people type goes to the node's owner: a key press as the key input's event, the typed line as the text input's, each applied and saved with the node's requests. It waits on its input devices and the node's changes descriptor together, leaves timers and signals to the node, and stops with it. The library calls the app model pure-serve exports and links none of its own; it exports pure_surface_run alone. Without --live or --input it takes no input: the node's saves alone draw it. The check gives a node an item over HTTP, then runs the display in the node with recorded keys (an item, Enter, Tab, Space): the last frame is the one vega tui draws for the same state and keys, it came back from the display controller as drawn, and the node's views show both items.
Diffstat (limited to 'surfaces')
-rw-r--r--surfaces/input.c1
-rw-r--r--surfaces/main.c125
-rw-r--r--surfaces/vega.h4
3 files changed, 116 insertions, 14 deletions
diff --git a/surfaces/input.c b/surfaces/input.c
index b358d47..9403a75 100644
--- a/surfaces/input.c
+++ b/surfaces/input.c
@@ -84,6 +84,7 @@ VegaInput *vega_input_open(const char *const *paths, unsigned count, unsigned wi
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 (!paths) return input; /* none */
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);
diff --git a/surfaces/main.c b/surfaces/main.c
index 26aa4eb..a7488ab 100644
--- a/surfaces/main.c
+++ b/surfaces/main.c
@@ -2,6 +2,7 @@
#include "vega.h"
#include "module-store.h"
#include "store.h"
+#include "surface.h"
#include <poll.h>
#include <signal.h>
#include <stdlib.h>
@@ -42,11 +43,19 @@
* (0 first) of a list or table; click is a mouse click on the last shown TUI
* frame (1 first, as terminals report it); time sets the time the next
* events see (live surfaces use the clock) and tick ticks a timer (live
- * surfaces tick them on time); swap replaces the module as --watch does. */
+ * surfaces tick them on time); swap replaces the module as --watch does.
+ *
+ * Built with VEGA_SURFACE the same host is a library a Pure node maps
+ * (pure-serve --surface vega-surface.so -- display ARGUMENTS, Pure's
+ * serve/surface.h): the display shows the node's app as of each save, read
+ * where it is, and what people type goes to the node's owner. */
static PureModule modules[2]; /* the running one (app.module) and the next */
static unsigned current;
static PureApp app;
+/* In a node: the node, whose owner applies the events; app then shows its
+ * last save (a reader) and applies none itself. */
+static const PureSurface *node;
static VegaScreen screen;
static VegaGrid *drawn; /* the TUI's last frame, which a click lands on */
static char entry[VEGA_TEXT];
@@ -153,10 +162,16 @@ static int key_input(const char *key) { return vega_key_input(app.module, key);
/* A key press and release; false when the app has no such key or the press failed. */
static bool press(const char *key, int *input) {
*input = key_input(key);
+ if (*input >= 0 && node) return node->press(node->host, (uint32_t)*input) == PURE_OK;
return *input >= 0 && vega_press(&app, key);
}
-static bool commit(const char *text, size_t length) { return vega_commit(&app, &screen, text, length); }
+static bool commit(const char *text, size_t length) {
+ if (node)
+ return screen.prompt >= 0 &&
+ node->type(node->host, (uint32_t)screen.prompt, (PureSpan){(const unsigned char *)text, length}) == PURE_OK;
+ return vega_commit(&app, &screen, text, length);
+}
static const VegaPart *part_named(const char *name) {
for (unsigned i = 0; i < screen.count; ++i)
@@ -179,7 +194,7 @@ static bool mouse(unsigned button, unsigned line) {
item = button == 64 ? focus - 1 : focus + 1;
}
} else if (button) return false;
- return vega_point(&app, part, item);
+ return !node && vega_point(&app, part, item); /* a node takes keys and text, not pointing yet */
}
static int hex(char c) { return c >= '0' && c <= '9' ? c - '0' : c >= 'a' && c <= 'f' ? c - 'a' + 10 : -1; }
@@ -552,23 +567,29 @@ static void show_pointer(uint32_t *out, size_t stride, int x, int y, unsigned wi
}
/* 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). */
+ * end of recorded events: the last frame is then printed as text). In a
+ * node: draw its last save, wait for input or its next save, until it stops;
+ * its owner runs the timers, and the signals are its own. */
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);
+ if (!node) {
+ 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) {
+ while (!stopping && !(node && node->stopping(node->host))) {
bool ticked = false;
- long due = vega_timers_run(&timers, &app, vega_now(), &ticked);
- vega_draw(&grid, &app, &screen, entry, entry_length, typing);
+ long due = node ? -1 : vega_timers_run(&timers, &app, vega_now(), &ticked);
+ PureSnapshot *snapshot = node ? node->read(node->host, &app) : NULL;
+ if (!node || snapshot) vega_draw(&grid, &app, &screen, entry, entry_length, typing);
+ if (snapshot) node->release(node->host, snapshot);
unsigned width, height;
size_t stride;
uint32_t *out = vega_display_buffer(display, &width, &height, &stride);
@@ -580,18 +601,25 @@ static int live(VegaInput *input, unsigned columns, unsigned rows, bool recorded
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];
+ struct pollfd waits[33];
unsigned count = vega_input_fds(input, waits, 32);
- int timeout = screen.ticks ? 16 : -1;
+ if (node) waits[count++] = (struct pollfd){node->changes, POLLIN, 0}; /* the node saved: draw again */
+ int timeout = screen.ticks && !node ? 16 : -1;
if (due >= 0 && (timeout < 0 || due < timeout)) timeout = (int)due;
if (poll(waits, count, timeout) <= 0) {
+ if (node) continue;
app.now = vega_now();
if (screen.ticks) pure_app_frame(&app);
continue;
}
- app.now = vega_now();
+ if (!node) app.now = vega_now();
for (unsigned i = 0; i < count; ++i) {
if (!(waits[i].revents & (POLLIN | POLLHUP | POLLERR))) continue;
+ if (node && waits[i].fd == node->changes) {
+ uint64_t saves;
+ if (read(node->changes, &saves, sizeof(saves)) < 0) { /* cleared already */ }
+ continue;
+ }
VegaEvent events[64];
bool ended;
unsigned n = vega_input_read(input, waits[i].fd, events, 64, &ended);
@@ -610,6 +638,7 @@ static int live(VegaInput *input, unsigned columns, unsigned rows, bool 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);
+ fflush(stdout);
}
drawn = NULL;
vega_grid_free(&grid);
@@ -746,6 +775,77 @@ static bool open_face(const char *path, float size) {
return ok; /* the bytes stay: the face reads them */
}
+#ifdef VEGA_SURFACE
+/* Vega in a Pure node (serve/surface.h): pure-serve ... --surface
+ * vega-surface.so -- display --symbols FONT.vsym [--size PX] [--card PATH]
+ * [--writeback WIDTHxHEIGHT --out PREFIX] [--live | --input EVENTS]. The
+ * display shows the node's app as of each save; keys and text go to its
+ * owner. Recorded events are read to their end, then the last frame is
+ * printed as text (on the node's standard output); live, it runs until the
+ * node stops. */
+__attribute__((visibility("default"))) int pure_surface_run(const PureSurface *surface, int argc, char **argv) {
+ if (surface->version != PURE_SURFACE_VERSION || surface->app_size != sizeof(PureApp) ||
+ surface->module_size != sizeof(PureModule)) {
+ fputs("vega: this surface was built for another Pure\n", stderr);
+ return 2;
+ }
+ const char *symbols_path = NULL, *card = "/dev/dri/card1", *recorded = NULL;
+ unsigned width = 0, height = 0;
+ float size = 16;
+ bool writeback = false, live_input = false, usage = argc < 2 || strcmp(argv[1], "display");
+ for (int i = 2; !usage && i < argc; i += 2) {
+ if (!strcmp(argv[i], "--live")) {
+ live_input = true;
+ --i;
+ continue;
+ }
+ if (i + 1 >= argc) usage = true;
+ else if (!strcmp(argv[i], "--symbols")) symbols_path = argv[i + 1];
+ else if (!strcmp(argv[i], "--size") && sscanf(argv[i + 1], "%f", &size) == 1) {}
+ else if (!strcmp(argv[i], "--card")) card = argv[i + 1];
+ else if (!strcmp(argv[i], "--input")) recorded = argv[i + 1];
+ else if (!strcmp(argv[i], "--out")) frames_prefix = argv[i + 1];
+ else if (!strcmp(argv[i], "--writeback") && sscanf(argv[i + 1], "%ux%u", &width, &height) == 2 && width && height)
+ writeback = true;
+ else usage = true;
+ }
+ if (usage || !symbols_path || (writeback && !frames_prefix) || (live_input && recorded)) {
+ fputs("vega-surface.so display --symbols FONT.vsym [--size PX] [--card PATH] [--writeback WIDTHxHEIGHT --out PREFIX]\n"
+ " [--live | --input EVENTS]\n", stderr);
+ return 2;
+ }
+ if (!open_face(symbols_path, size)) return 2;
+ char error[256];
+ if (!(display = vega_display_open(card, writeback, writeback ? width : 0, writeback ? height : 0, error, sizeof(error)))) {
+ fprintf(stderr, "vega: %s\n", error);
+ return 2;
+ }
+ unsigned pixels_across, pixels_down;
+ size_t stride;
+ vega_display_buffer(display, &pixels_across, &pixels_down, &stride);
+ node = surface;
+ vega_screen(&screen, surface->module);
+ typing = screen.prompt >= 0;
+ const char *paths[1] = {recorded};
+ /* Without --live or --input, no devices: the node's saves alone draw it. */
+ VegaInput *input = vega_input_open(live_input || recorded ? paths : NULL, recorded ? 1 : 0, pixels_across, pixels_down);
+ int status = 2;
+ if (!input) fprintf(stderr, "vega: cannot read %s\n", recorded ? recorded : "input devices");
+ else {
+ PureSnapshot *first = node->read(node->host, &app); /* app shows the node's states from here on */
+ if (first) node->release(node->host, first);
+ status = first ? live(input, pixels_across / (unsigned)face.cell_width, pixels_down / (unsigned)face.cell_height,
+ recorded != NULL)
+ : 2;
+ vega_input_close(input);
+ }
+ pure_app_stop(&app);
+ vega_display_close(display);
+ display = NULL;
+ node = NULL;
+ return status;
+}
+#else
int main(int argc, char **argv) {
unsigned width = 0, height = 0, port = 8080;
const char *symbols_path = NULL, *card = "/dev/dri/card1";
@@ -841,3 +941,4 @@ int main(int argc, char **argv) {
vega_display_close(display);
return status;
}
+#endif
diff --git a/surfaces/vega.h b/surfaces/vega.h
index ba7b61f..fd8780b 100644
--- a/surfaces/vega.h
+++ b/surfaces/vega.h
@@ -155,8 +155,8 @@ 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. */
+ * screen). With count paths, recorded events are read from those files
+ * instead, positions in screen pixels; with paths NULL, there is none. */
typedef enum { VEGA_EVENT_KEY, VEGA_EVENT_TEXT, VEGA_EVENT_PRESS, VEGA_EVENT_WHEEL } VegaEventKind;
typedef struct {
VegaEventKind kind;