aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authoruser <user@clank>2026-09-29 14:26:06 +0200
committeruser <user@clank>2026-09-29 14:26:06 +0200
commite779c1fafa556330752b2c5f43c56415f655ff24 (patch)
treeb87d335587fbfb68696569c5a41d912435d67140 /src
parentmouse: encode pixel reports against the cell size we advertise (diff)
downloadslosh-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.c15
-rw-r--r--src/pane.c19
-rw-r--r--src/pty.c15
-rw-r--r--src/pty_win.c15
4 files changed, 54 insertions, 10 deletions
diff --git a/src/app.c b/src/app.c
index 385c5f9..2031e09 100644
--- a/src/app.c
+++ b/src/app.c
@@ -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;
diff --git a/src/pane.c b/src/pane.c
index 5252f14..4c5da85 100644
--- a/src/pane.c
+++ b/src/pane.c
@@ -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
diff --git a/src/pty.c b/src/pty.c
index 00fa190..deb67c0 100644
--- a/src/pty.c
+++ b/src/pty.c
@@ -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