Add ability use script as cwd.

This commit is contained in:
Greyson Parrelli
2026-08-13 08:51:03 -04:00
parent e16b147e16
commit 0836cbc772
6 changed files with 526 additions and 8 deletions
+62 -5
View File
@@ -121,6 +121,7 @@ OpenLayoutDialog.zig prompts for a layout's parameters
SaveLayoutDialog.zig turns the current tab into a saved layout
Session.zig libghostty-vt Terminal + parser, fed by the PTY
Pty.zig openpt/fork/exec, controlling terminal setup
script.zig runs a command for `$(...)` in a layout's directory
key.zig GDK keyval -> libghostty-vt key mapping
theme.zig colors libghostty-vt has no opinion about, per scheme
Settings.zig preferences: model, JSON on disk
@@ -198,6 +199,46 @@ leading `~` is expanded afterwards. An unknown `{{name}}` is left as written
rather than blanked, so a typo shows up in the pane instead of silently
producing an empty path.
### Directories from a script
A **directory** can also be `$(a command)`, and what the command prints becomes
the path. A parameter can say *which* project; only a command can answer
"wherever that branch is checked out":
```json
"cwd": "$(git -C ~/src/{{repo}} worktree list | awk '/{{branch}}/ {print $1}')"
```
It runs under `/bin/sh -c`, so pipes and `&&` work as written, and `$(...)` may
appear anywhere in the field — `~/src/$(pick-project)/api` is fine, as are
several in one path. Its stdout is the value, trimmed of surrounding
whitespace; its stderr stays attached to Playpen's own, so a script that
complains complains somewhere you can read it.
**Parameters reach the script two ways.** They are substituted into it first,
as `{{name}}` above, and every one is also in its environment as
`PLAYPEN_<NAME>` — uppercased, with anything that isn't a shell identifier
character replaced by `_`, so `repo-path` arrives as `PLAYPEN_REPO_PATH`. Reach
for the environment form whenever a value might contain a space: `{{name}}`
splices text straight into the command line, where a path with a space in it
silently becomes two arguments, and `"$PLAYPEN_NAME"` cannot.
Order is `{{name}}``$(...)` → leading `~`, so a script is free to print a
`~/...` path and land where it meant to.
Two limits worth knowing. **A script blocks the window while it runs**, because
layouts are built by a synchronous walk of the split tree; it is killed after
five seconds, so a mistake costs a visible pause and an error rather than a
window that never comes back. And **a script that fails leaves an empty string**
rather than aborting the tab — the rest of the layout is usually fine, and a
pane that opened in the wrong directory is both obvious and recoverable, which
a tab that refused to open is not. Failures are logged with the command that
caused them.
This applies to the directory only. A pane's *script* field is already typed
into a shell, which does its own command substitution; running it here first
would evaluate it twice, at two different moments.
**Scripts are typed into the shell, not run instead of it.** A pane starts your
login shell as usual, and the script is fed to it once it is ready. The shell
is still there when the script finishes, with your environment loaded and the
@@ -568,11 +609,27 @@ not of the terminal. Note that this applies per invocation, so a trailing
`wtype -k Return` in its own call is swallowed entirely — pass it as part of
the same `wtype` command as the text it submits.
Only plain text injection reaches the app. Modifier chords (`wtype -M ctrl`)
and synthetic clicks (`swaymsg seat - cursor`) are both accepted by the
compositor and never delivered to the client, so shortcuts and buttons can't be
exercised this way; to screenshot a state that a chord would reach, open it
from code instead.
**Modifier chords do work**, but only with that same caveat applied to the
chord itself. `wtype -M ctrl -k comma -m ctrl` on its own is unreliable — the
dropped first event is the *modifier press*, leaving a bare comma — so the
throwaway key has to go inside the same invocation:
```sh
./shot.sh out.png "-k Shift_L -M ctrl -k comma -m ctrl"
```
Note that `shot.sh` quotes `$KEYS`, so a chord needs the quoting relaxed for
the words to reach `wtype` as separate arguments.
**Synthetic clicks do not work.** `swaymsg seat seat0 cursor set/press` is
accepted and reports success, but the headless backend has no pointer device to
emit from and the client never sees it. Anything reachable only by clicking has
to be reached another way — a keyboard shortcut, or `zig build test` if the
thing being checked is logic rather than pixels.
`zig build test` runs the unit tests, which cover layout parsing and the
parameter/`$(...)` substitution pipeline. They are rooted at `Layouts.zig` so
the test binary never links GTK.
Web panes need an EGL display and the headless compositor has no GPU, so
`shot.sh` forces the dev shell's mesa down to its software rasterizer. The mesa