aboutsummaryrefslogtreecommitdiffstats
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
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.
-rw-r--r--Makefile2
-rw-r--r--src/headless.c19
2 files changed, 15 insertions, 6 deletions
diff --git a/Makefile b/Makefile
index dc614d4..88f0f3d 100644
--- a/Makefile
+++ b/Makefile
@@ -70,7 +70,7 @@ test-live: $(BIN) ## the checks that need a real tty (slow)
test-all: test test-live ## everything
smoke: $(BIN) ## compose a screen headlessly and print it
- ./$(BIN) --headless --cols 40 --rows 6 -- /bin/sh -c 'printf "hello \033[1;32msl0ppty\033[0m\n"; echo "wide: 日本語"'
+ ./$(BIN) --headless --cols 40 --rows 12 -- /bin/sh -c 'printf "hello \033[1;32msl0ppty\033[0m\n"; echo "wide: 日本語"'
run: $(BIN) ## build and run
./$(BIN)
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;
}