skip to content
==> src/main.c <==
1/* mesh-top (crates/mesh-top src/main.rs): `mesh-top [ENDPOINT ...]`: one background poller per
2 * meshd endpoint (default 127.0.0.1:7700), the dashboard in the foreground.
3 * `mesh-top --print TAB [WxH] [--ansi] [--marks] [ENDPOINT ...]`: polls for ~3 s, then prints one
4 * frame of tab TAB (its key: 1-9, 0, -, =) as text, without a terminal. */
5#include "mesh/top_client.h"
6#include "mesh/top_ui.h"
7#include "tui/render.h"
8#include "tui/term.h"
9
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <time.h>
14
15/* ~15 Hz polling; the Doom tab polls at ~30 Hz so the game feels live. */
16#define POLL_MS 66
17#define POLL_DOOM_MS 33
18/* Doom keys queued beyond this are dropped so held keys never lag behind. */
19#define MAX_PENDING_KEYS 4
20
21static mt_instant real_now(void) { return mt_now(); }
22
23static void nap_ms(long ms) {
24    struct timespec t = {ms / 1000, (ms % 1000) * 1000000L};
25    nanosleep(&t, NULL);
26}
27
28static bool parse_u16(const char *s, size_t n, uint16_t *out) {
29    if (n == 0 || n > 5) return false;
30    uint32_t v = 0;
31    for (size_t i = 0; i < n; i++) {
32        if (s[i] < '0' || s[i] > '9') return false;
33        v = v * 10 + (uint32_t)(s[i] - '0');
34    }
35    if (v > 65535) return false;
36    *out = (uint16_t)v;
37    return true;
38}
39
40/* Background refreshes (trace, log, views) never queue behind a command. */
41static void background(mt_app *app, mt_endpoint **eps, size_t n) {
42    bool idle[64];
43    for (size_t i = 0; i < n && i < 64; i++) idle[i] = mt_endpoint_pending(eps[i]) == 0;
44    mt_action a[2];
45    size_t na = mt_app_tick(app, real_now(), a);
46    for (size_t i = 0; i < na; i++) {
47        if (a[i].kind == MT_ACTION_POLL && a[i].machine < n && idle[a[i].machine]) mt_endpoint_send(eps[a[i].machine], a[i].line);
48        mt_action_free(&a[i]);
49    }
50}
51
52static void drain(mt_app *app, mt_updates *q) {
53    mt_update u;
54    mt_instant now = real_now();
55    while (mt_updates_pop(q, 0, &u)) mt_app_apply(app, &u, now);
56}
57
58static void print_frame(const mt_app *app, uint16_t w, uint16_t h, bool ansi) {
59    tui_rect area = {0, 0, w, h};
60    tui_buffer fb, out;
61    tui_buffer_init(&fb, area);
62    tui_buffer_init(&out, area);
63    tui_arena a = {0};
64    mt_ui_draw(&a, &fb, app, real_now());
65    mt_ui_flush(&fb, &out);
66    size_t cap = (size_t)w * 64 + 16;
67    char *line = malloc(cap);
68    for (uint16_t y = 0; y < h; y++) {
69        size_t n = 0;
70        for (uint16_t x = 0; x < w; x++) {
71            const tui_cell *c = tui_buffer_cell_c(&out, x, y);
72            if (ansi && c->fg.kind == TUI_COLOR_RGB) n += (size_t)snprintf(line + n, cap - n, "\x1b[38;2;%u;%u;%um", c->fg.r, c->fg.g, c->fg.b);
73            if (ansi && c->bg.kind == TUI_COLOR_RGB) n += (size_t)snprintf(line + n, cap - n, "\x1b[48;2;%u;%u;%um", c->bg.r, c->bg.g, c->bg.b);
74            n += (size_t)snprintf(line + n, cap - n, "%s", tui_cell_symbol(c));
75        }
76        if (ansi) {
77            printf("%.*s\x1b[0m\n", (int)n, line);
78        } else {
79            while (n && (line[n - 1] == ' ' || (line[n - 1] >= '\t' && line[n - 1] <= '\r'))) n--;
80            printf("%.*s\n", (int)n, line);
81        }
82    }
83    free(line);
84    tui_arena_free(&a);
85    tui_buffer_free(&fb);
86    tui_buffer_free(&out);
87}
88
89/* Handles one key; false to quit. */
90static bool on_key(mt_app *app, mt_endpoint **eps, size_t n, tui_key k) {
91    mt_action a = mt_app_handle_key(app, k);
92    bool go = a.kind != MT_ACTION_QUIT;
93    if (a.kind == MT_ACTION_SEND && a.machine < n) {
94        bool is_key = strncmp(a.line, "input doom ", 11) == 0;
95        if (!is_key || mt_endpoint_pending(eps[a.machine]) < MAX_PENDING_KEYS) mt_endpoint_send(eps[a.machine], a.line);
96    }
97    mt_action_free(&a);
98    return go;
99}
100
101static int interactive(mt_app *app, mt_endpoint **eps, size_t n, mt_updates *q) {
102    if (!tui_term_enter()) {
103        fprintf(stderr, "mesh-top: stdin is not a terminal (try --print)\n");
104        return 1;
105    }
106    tui_terminal term;
107    tui_terminal_init(&term, 100, 30);
108    char in[4096];
109    size_t have = 0;
110    bool go = true;
111    while (go) {
112        tui_arena a = {0};
113        tui_buffer *buf = tui_terminal_begin(&term);
114        mt_ui_draw(&a, buf, app, real_now());
115        tui_terminal_end(&term);
116        tui_arena_free(&a);
117        /* Drain every queued key before the next frame. */
118        long r = tui_term_read(in + have, sizeof in - have, 16);
119        while (r > 0) {
120            have += (size_t)r;
121            r = have < sizeof in ? tui_term_read(in + have, sizeof in - have, 0) : 0;
122        }
123        if (r < 0 && have == 0) break;
124        size_t at = 0;
125        while (go && at < have) {
126            tui_key k;
127            size_t used = tui_input_parse(in + at, have - at, true, &k);
128            if (used == 0) break;
129            at += used;
130            if (k.code != TUI_KEY_NONE) go = on_key(app, eps, n, k);
131        }
132        memmove(in, in + at, have - at);
133        have -= at;
134        uint64_t poll = app->tab == MT_TAB_DOOM ? POLL_DOOM_MS : POLL_MS;
135        /* Only the focused machine needs fast frames. */
136        for (size_t i = 0; i < n; i++) mt_endpoint_set_interval(eps[i], i == app->focus ? poll : POLL_MS);
137        background(app, eps, n);
138        drain(app, q);
139    }
140    tui_terminal_free(&term);
141    tui_term_leave();
142    return 0;
143}
144
145int main(int argc, char **argv) {
146    const char **args = calloc((size_t)argc + 1, sizeof *args);
147    size_t na = 0;
148    bool ansi = false, marks = false;
149    for (int i = 1; i < argc; i++) {
150        if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
151            printf("usage: mesh-top [HOST:PORT ...]   (default 127.0.0.1:%u; one endpoint per machine of a cluster, any order)\n", MESH_CONTROL_PORT);
152            printf("       mesh-top --print TAB [WxH] [--ansi] [--marks] [HOST:PORT ...]   (one frame of tab TAB, by its key 1-9 0 - =, as text; "
153                   "--ansi: with its colours)\n");
154            return 0;
155        }
156    }
157    for (int i = 1; i < argc; i++) {
158        /* --ansi (with --print): the frame with its colours; --marks: the Render tab's tiles marked. */
159        if (!strcmp(argv[i], "--ansi")) ansi = true;
160        else if (!strcmp(argv[i], "--marks")) marks = true;
161        else args[na++] = argv[i];
162    }
163    bool print = false;
164    int tab = 0;
165    uint16_t pw = 100, ph = 30;
166    size_t first = 0;
167    if (na > 0 && !strcmp(args[0], "--print")) {
168        int key = -1;
169        if (na > 1 && args[1][0])
170            for (int i = 0; i < MT_TABS; i++)
171                if (args[1][0] == MT_TAB_KEYS[i]) key = i;
172        if (key < 0) {
173            fprintf(stderr, "mesh-top --print: TAB is a tab's key (1-9 0 - =):");
174            for (int i = 0; i < MT_TABS; i++) fprintf(stderr, " %s", MT_TAB_NAMES[i]);
175            fprintf(stderr, "\n");
176            return 2;
177        }
178        print = true;
179        tab = key;
180        first = 2;
181        const char *x = first < na ? strchr(args[first], 'x') : NULL;
182        uint16_t w, h;
183        if (x && parse_u16(args[first], (size_t)(x - args[first]), &w) && parse_u16(x + 1, strlen(x + 1), &h)) {
184            pw = w;
185            ph = h;
186            first++;
187        }
188    }
189    char def[32];
190    snprintf(def, sizeof def, "127.0.0.1:%u", MESH_CONTROL_PORT);
191    const char **endpoints = args + first;
192    size_t n = na - first;
193    if (n == 0) {
194        endpoints = (const char *[]){def};
195        n = 1;
196    }
197    mt_updates *q = mt_updates_new();
198    mt_endpoint **eps = calloc(n, sizeof *eps);
199    for (size_t i = 0; i < n; i++) eps[i] = mt_endpoint_spawn(i, endpoints[i], q, POLL_MS);
200    mt_app app;
201    mt_app_init(&app, endpoints, n);
202    int rc = 0;
203    if (print) {
204        app.tab = tab;
205        app.render_marks = marks;
206        mt_instant t0 = real_now();
207        while (real_now() - t0 < MT_SECS(3)) {
208            background(&app, eps, n);
209            drain(&app, q);
210            nap_ms(20);
211        }
212        print_frame(&app, pw, ph, ansi);
213    } else {
214        rc = interactive(&app, eps, n, q);
215    }
216    for (size_t i = 0; i < n; i++) mt_endpoint_free(eps[i]);
217    mt_updates_free(q);
218    mt_app_free(&app);
219    free(eps);
220    free(args);
221    return rc;
222}