diff options
| author | user <user@clank> | 2026-09-29 14:26:06 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-09-29 14:26:06 +0200 |
| commit | e779c1fafa556330752b2c5f43c56415f655ff24 (patch) | |
| tree | b87d335587fbfb68696569c5a41d912435d67140 /src | |
| parent | mouse: encode pixel reports against the cell size we advertise (diff) | |
| download | slosh-e779c1fafa556330752b2c5f43c56415f655ff24.tar.gz slosh-e779c1fafa556330752b2c5f43c56415f655ff24.tar.xz slosh-e779c1fafa556330752b2c5f43c56415f655ff24.zip | |
panes: $SLOSH_PANE, so a program knows which pane it is in
A program in a pane could name every pane but its own. `panes` says which
one is *focused*, and focus belongs to the human, so anything that inferred
"where do I live" from it did its work in whichever tab somebody else
happened to be looking at -- and lost a race with anybody else asking the
same question.
So export the id into the child, the way tmux has $TMUX_PANE and zellij
$ZELLIJ_PANE_ID. The id is taken before the pane is made, because pane_new
spawns the program and assigning it afterwards is one instruction too late;
it lives on the pane rather than the layout, so a rerun keeps it -- an
identity that changed under a rerun would be no identity at all. Unset, not
zero, when there is none: a stale value inherited from an outer session
would read as this one's.
`panes` also reports each pane's pid now, which is the same question
answered for anything that cannot read its own environment: walk your
parents up until one of them is on that list.
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.c | 15 | ||||
| -rw-r--r-- | src/pane.c | 19 | ||||
| -rw-r--r-- | src/pty.c | 15 | ||||
| -rw-r--r-- | src/pty_win.c | 15 |
4 files changed, 54 insertions, 10 deletions
@@ -830,13 +830,18 @@ const char *app_clipboard(const app_t *a) { return a->clipboard; } node_t *leaf_new_ex(app_t *a, const char *const argv[], const char *cwd, bool suspended, const char *label) { - pane_t *p = pane_new_ex(argv, 1, 1, cwd, suspended, label); + /* The id is taken *before* the pane is made, because the pane's program is + * spawned by pane_new and has to be told which pane it is ($SLOSH_PANE). + * Assigning it afterwards, as this used to, is one instruction too late: + * the child is already running without it. */ + uint32_t id = ++a->next_id; + pane_t *p = pane_new_ex(argv, 1, 1, cwd, suspended, label, id); if (!p) return NULL; node_t *n = calloc(1, sizeof *n); n->kind = NODE_LEAF; n->weight = WEIGHT_UNIT; n->pane = p; - n->id = ++a->next_id; + n->id = id; pane_set_osc_handler(p, on_pane_osc, a); pane_set_clipboard_handler(p, on_pane_clipboard, a); pane_set_notify_handler(p, on_pane_notify, a); @@ -2137,6 +2142,12 @@ static void panes_cb(node_t *n, void *ud) { * false with a status is one whose program is over and which is waiting to * be re-run or closed. `exit_code` is -1 when there is no status to give. */ json_bool(j, "alive", pane_alive(n->pane)); + /* The pid of the program in the pane, so a process can work out which pane + * it is in by walking its own parents up to one of these -- the answer for + * anything that cannot read $SLOSH_PANE from its environment, which is + * every tool run with a scrubbed env and every process with no controlling + * tty. -1 when there is nothing running (a suspended or dead pane). */ + json_int(j, "pid", (long long)pane_pid(n->pane)); { int code = 0; bool sig = false; @@ -30,6 +30,11 @@ struct pane { GhosttyMouseEncoder menc; GhosttyMouseEvent mev; uint16_t cols, rows_n; + /* The id the session knows this pane by, handed to every spawn of it as + * $SLOSH_PANE. Kept on the pane rather than looked up from the layout + * because `rerun` respawns from here, and an identity that changed under a + * rerun would be no identity at all. */ + uint32_t id; /* The client's cell size in pixels. A default rather than a zero, because * zero is what makes an image vanish: a placement given no explicit cell * count is sized from the image's pixels and this, and 0 covers no cells. @@ -321,7 +326,7 @@ bool pane_start(pane_t *p) { if (!p->suspended) return false; p->suspended = false; if (pty_spawn(&p->pty, (const char *const *)p->argv, p->cols, p->rows_n, - p->cwd, p->cell_w, p->cell_h) != 0) { + p->cwd, p->cell_w, p->cell_h, p->id) != 0) { p->alive = false; return false; } @@ -339,8 +344,9 @@ static char **argv_dup(const char *const argv[]) { } pane_t *pane_new_ex(const char *const argv[], uint16_t cols, uint16_t rows, - const char *cwd, bool suspended, const char *label) { - pane_t *p = pane_new(argv, cols, rows, cwd); + const char *cwd, bool suspended, const char *label, + uint32_t id) { + pane_t *p = pane_new(argv, cols, rows, cwd, id); if (!p) return NULL; snprintf(p->label, sizeof p->label, "%s", label ? label : ""); if (suspended) { @@ -355,8 +361,9 @@ pane_t *pane_new_ex(const char *const argv[], uint16_t cols, uint16_t rows, } pane_t *pane_new(const char *const argv[], uint16_t cols, uint16_t rows, - const char *cwd) { + const char *cwd, uint32_t id) { pane_t *p = calloc(1, sizeof *p); + p->id = id; p->cols = cols; p->rows_n = rows; p->cell_w = 8; @@ -414,7 +421,7 @@ pane_t *pane_new(const char *const argv[], uint16_t cols, uint16_t rows, p->argv = argv_dup(argv); p->cwd = cwd ? strdup(cwd) : NULL; - if (pty_spawn(&p->pty, argv, cols, rows, cwd, p->cell_w, p->cell_h) != 0) + if (pty_spawn(&p->pty, argv, cols, rows, cwd, p->cell_w, p->cell_h, id) != 0) goto fail; p->alive = true; p->dirty = true; @@ -715,7 +722,7 @@ bool pane_restart(pane_t *p) { p->exit_code = 0; if (pty_spawn(&p->pty, (const char *const *)p->argv, p->cols, p->rows_n, - p->cwd, p->cell_w, p->cell_h) != 0) + p->cwd, p->cell_w, p->cell_h, p->id) != 0) return false; /* The terminal is deliberately not cleared: the run that ended, and the * line saying it ended, stay above this one in the scrollback. That is the @@ -124,7 +124,8 @@ static const char *pty_term(void) { } int pty_spawn(pty_t *p, const char *const argv[], uint16_t cols, uint16_t rows, - const char *cwd, uint16_t cell_w, uint16_t cell_h) { + const char *cwd, uint16_t cell_w, uint16_t cell_h, + uint32_t pane_id) { int master = posix_openpt(O_RDWR | O_NOCTTY); if (master < 0) return -1; if (grantpt(master) < 0 || unlockpt(master) < 0) { @@ -206,6 +207,18 @@ int pty_spawn(pty_t *p, const char *const argv[], uint16_t cols, uint16_t rows, * same contract inward. xterm-ghostty is what libghostty-vt implements. */ setenv("TERM", pty_term(), 1); setenv("SLOSH", "1", 1); + /* Which pane this is, so a program can act on its own pane instead of on + * whichever one has focus. Set in the child, after the fork, because it is + * per-pane: setting it in the parent would leak one pane's id into the + * next spawn. Unset when there is no id, so a stale inherited value from + * an outer session cannot be read as this one's. */ + if (pane_id) { + char idbuf[16]; + snprintf(idbuf, sizeof idbuf, "%u", pane_id); + setenv("SLOSH_PANE", idbuf, 1); + } else { + unsetenv("SLOSH_PANE"); + } unsetenv("ZELLIJ"); signal(SIGPIPE, SIG_DFL); diff --git a/src/pty_win.c b/src/pty_win.c index d4386ed..b33c7fb 100644 --- a/src/pty_win.c +++ b/src/pty_win.c @@ -179,7 +179,8 @@ static void build_cmdline(const char *const argv[], char *out, size_t cap) { } int pty_spawn(pty_t *p, const char *const argv[], uint16_t cols, uint16_t rows, - const char *cwd, uint16_t cell_w, uint16_t cell_h) { + const char *cwd, uint16_t cell_w, uint16_t cell_h, + uint32_t pane_id) { (void)cell_w; (void)cell_h; /* ConPTY carries no pixel geometry */ if (!p || !argv || !argv[0]) return -1; @@ -235,6 +236,18 @@ int pty_spawn(pty_t *p, const char *const argv[], uint16_t cols, uint16_t rows, /* Same contract as the POSIX child: we say what terminal this is. */ SetEnvironmentVariableA("TERM", "xterm-ghostty"); SetEnvironmentVariableA("SLOSH", "1"); + { + /* Same contract as the POSIX child: which pane this is. See pty_spawn in + * slosh.h -- a program must be able to name its own pane rather than + * infer it from whoever has focus. */ + char idbuf[16]; + if (pane_id) { + snprintf(idbuf, sizeof idbuf, "%u", pane_id); + SetEnvironmentVariableA("SLOSH_PANE", idbuf); + } else { + SetEnvironmentVariableA("SLOSH_PANE", NULL); + } + } /* A child attached to a pseudoconsole is supposed to take its stdio from * that console -- but CreateProcess copies the *parent's* standard handles |
