aboutsummaryrefslogtreecommitdiffstats
path: root/src/headless.c
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-14 12:13:43 +0200
committeruser <user@clank>2026-08-14 12:13:43 +0200
commitcba63d04627c7bd06ce80f2be83b7c0d54fb8568 (patch)
treef6a72b8950a673f3eb3cf433279bde1122c0c0ac /src/headless.c
parentsl0ppty: rename from sl0ptty (diff)
downloadslosh-cba63d04627c7bd06ce80f2be83b7c0d54fb8568.tar.gz
slosh-cba63d04627c7bd06ce80f2be83b7c0d54fb8568.tar.xz
slosh-cba63d04627c7bd06ce80f2be83b7c0d54fb8568.zip
sl0ppty: one-shot --headless printed a blank screen
`--headless` is documented as "run once, print the screen", and it printed nothing for any command that finished — which is most of them. `make smoke` had been showing six blank rows. settle() calls app_reap() every iteration, and app_reap closes a pane whose child has exited; closing the last one takes the tab with it. A command like `sh -c printf ...` exits immediately, so the pane and its content were gone before app_compose ever ran, and the compose found an app with no tabs. So settle() now takes an optional screen and composes into it before the reap rather than after. This is exact rather than a narrowed race: pane_pump() only marks a pane dead on read() returning 0, and it reaches EOF only after it has drained everything the pty had, so a pane cannot be marked dead while its output is still pending and the last snapshot always holds the finished screen. --script passes NULL and is byte-for-byte unchanged. smoke also asked for 6 rows, which after the outer margin, the tab strip and two borders leaves about one row for the pane — under min_pane rows, so it collapsed to an empty frame and would have shown nothing even with the fix. It asks for 12 now. Found while renaming; it predates the rename, reproduced on the commit before.
Diffstat (limited to '')
-rw-r--r--src/headless.c19
1 files changed, 14 insertions, 5 deletions
diff --git a/src/headless.c b/src/headless.c
index dab817d..451996c 100644
--- a/src/headless.c
+++ b/src/headless.c
@@ -27,8 +27,14 @@ static int64_t ms_now(void) {
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
-/* Pump every pane until none has produced anything for `quiet` ms. */
-static void settle(app_t *a, int quiet) {
+/* Pump every pane until none has produced anything for `quiet` ms.
+ *
+ * `snap`, when given, is composed after each pump and *before* the reap that
+ * would close an exited pane, so a one-shot run still prints what the command
+ * drew. This is exact, not a narrowed race: pane_pump() only marks a pane dead
+ * on EOF, which it reaches after consuming everything the pty had, so the last
+ * snapshot always holds the finished output. */
+static void settle(app_t *a, int quiet, screen_t *snap) {
int64_t last = ms_now();
int64_t deadline = last + 3000;
while (!app_should_quit(a) && ms_now() < deadline) {
@@ -45,6 +51,7 @@ static void settle(app_t *a, int quiet) {
last = ms_now();
}
}
+ if (snap) app_compose(a, snap);
app_reap(a);
if (ms_now() - last >= quiet) break;
}
@@ -68,8 +75,10 @@ int run_headless(const char *const argv[], uint16_t cols, uint16_t rows,
input_parser_t *in = input_new();
if (!script) { /* one-shot: run it, let it settle, print the screen */
- settle(a, idle_ms);
- app_compose(a, &s);
+ settle(a, idle_ms, &s);
+ /* Panes still alive: take the freshest frame. Otherwise keep the one
+ * settle() snapshotted just before the last pane was reaped. */
+ if (!app_should_quit(a)) app_compose(a, &s);
char *dump = screen_dump(&s);
fputs(dump, stdout);
free(dump);
@@ -87,7 +96,7 @@ int run_headless(const char *const argv[], uint16_t cols, uint16_t rows,
if (!line[0]) continue;
if (strncmp(line, "settle", 6) == 0) {
- settle(a, line[6] == ' ' ? atoi(line + 7) : idle_ms);
+ settle(a, line[6] == ' ' ? atoi(line + 7) : idle_ms, NULL);
continue;
}