Add a web browser tab.
This commit is contained in:
@@ -31,15 +31,43 @@ theme, then refreshes the desktop and icon caches. `mise tasks` lists the rest
|
||||
Two things the installer has to handle that a normal `install` wouldn't:
|
||||
|
||||
- **A launcher, not a symlink.** The binary links against GTK in the Nix store
|
||||
and has an RPATH, so it runs anywhere — but it still needs to be pointed at
|
||||
GTK's GSettings schemas and icon themes, or it aborts on a missing schema.
|
||||
A desktop launcher starts the app with a minimal environment, so those paths
|
||||
are baked into the launcher script. They are read out of the dev shell at
|
||||
install time rather than hardcoded, so they can't drift from the flake.
|
||||
and has an RPATH, so it runs anywhere — but a handful of things are found by
|
||||
*path* at runtime rather than linked, and they have to be pointed at the Nix
|
||||
copies or the app misbehaves in ways that look nothing like a packaging
|
||||
problem. A desktop launcher starts the app with a minimal environment, so
|
||||
those paths are baked into the launcher script, read out of the dev shell at
|
||||
install time rather than hardcoded so they can't drift from the flake. See
|
||||
[Running off-NixOS](#running-off-nixos) for what each one is.
|
||||
- **Nix GC roots.** The installed app depends on store paths that
|
||||
`nix-collect-garbage` would otherwise be free to delete, which would break it
|
||||
later with no obvious connection to the command that did it. Install pins
|
||||
every store path in the binary's RPATH; uninstall releases them.
|
||||
every store path in the binary's RPATH, plus the by-path ones above, which
|
||||
never appear in an RPATH; uninstall releases them.
|
||||
|
||||
### Running off-NixOS
|
||||
|
||||
Everything here is set up by the dev shell and baked into the installed
|
||||
launcher, so it should be invisible — but each one cost a debugging session, so
|
||||
it is written down.
|
||||
|
||||
- **GSettings schemas and icon themes** (`GSETTINGS_SCHEMA_DIR`,
|
||||
`XDG_DATA_DIRS`). Without them GTK aborts on a missing schema, or renders
|
||||
broken-image icons.
|
||||
- **A TLS backend** (`GIO_EXTRA_MODULES` → `glib-networking`). GIO has none
|
||||
built in; it loads one as a module. Without it every `https://` page in a web
|
||||
pane fails with "TLS support is not available", while `http://` still works.
|
||||
- **One consistent mesa** (`__EGL_VENDOR_LIBRARY_DIRS`, `LIBGL_DRIVERS_PATH`,
|
||||
`GBM_BACKENDS_PATH`). This is the subtle one. WebKit's web process is linked
|
||||
against Nix's `libgbm`, but libglvnd falls back to
|
||||
`/usr/share/glvnd/egl_vendor.d` and loads the *host's* mesa as the EGL
|
||||
vendor — and a GBM device created by one mesa is rejected by the other.
|
||||
Nix's `libgbm` also looks for its backend under `/run/opengl-driver`, which
|
||||
only exists on NixOS. Either one aborts the web process at startup with
|
||||
`Could not create default EGL display: EGL_BAD_PARAMETER`, so web panes open
|
||||
fine, begin loading, and then go permanently blank — the terminal side is
|
||||
completely unaffected, which makes it look like an app bug rather than a
|
||||
graphics one. Pointing all three at the Nix mesa fixes it; the real GPU is
|
||||
still used, via `/dev/dri`.
|
||||
|
||||
## Which "libghostty" this uses, and why
|
||||
|
||||
@@ -80,20 +108,31 @@ main.zig AdwApplication, CSS loading
|
||||
Window.zig sidebar + GtkStack of views, tab management, shortcuts
|
||||
View.zig one tab's content: its panes, their layout, and drag handling
|
||||
Layout.zig the split tree: nodes, rearranging, GtkPaned materialization
|
||||
Pane.zig a terminal plus its header, drag source, and drop target
|
||||
Pane.zig content plus its header, drag source, and drop target
|
||||
Terminal.zig GtkDrawingArea: Cairo/Pango renderer + input handling
|
||||
Browser.zig WebKitWebView plus a back/forward/reload/address bar
|
||||
webkit.zig hand-written bindings for the WebKitGTK calls we make
|
||||
Session.zig libghostty-vt Terminal + parser, fed by the PTY
|
||||
Pty.zig openpt/fork/exec, controlling terminal setup
|
||||
key.zig GDK keyval -> libghostty-vt key mapping
|
||||
theme.zig colors libghostty-vt has no opinion about
|
||||
```
|
||||
|
||||
A tab is a **view**, and a view holds one or more terminal **panes** arranged
|
||||
in a **binary split tree**: every interior node is a split with an orientation,
|
||||
A tab is a **view**, and a view holds one or more **panes** arranged in a
|
||||
**binary split tree**: every interior node is a split with an orientation,
|
||||
every leaf is a pane. That is what allows the layouts a single shared
|
||||
orientation can't express — three panes in a row, drag the rightmost to the
|
||||
bottom, and you get two on top with one spanning the full width beneath them.
|
||||
|
||||
A pane holds either a terminal or a web view, behind a `Pane.Content` union.
|
||||
Both kinds expose the same three operations (widget, focus, destroy) and report
|
||||
back through the same three callbacks (title, exit, focus), so the split tree,
|
||||
drag and drop, and focus tracking are written once and never branch on which
|
||||
kind of pane they are moving. A web pane carries its own navigation bar rather
|
||||
than putting an address entry in the pane header, because the header is the
|
||||
drag handle and a text entry there would swallow the drags that rearrange the
|
||||
view.
|
||||
|
||||
Each split node owns a `GtkPaned`, so dividers are draggable and every split
|
||||
remembers its position as a **ratio** rather than a pixel count. The ratio is
|
||||
the source of truth: it is re-applied whenever the available space changes, so
|
||||
@@ -141,9 +180,13 @@ in principle, but a terminal grid is small.
|
||||
- Window title (OSC 0/2) becomes the pane header and tab label; the tab shows
|
||||
its pane count once a view holds more than one
|
||||
- Tabs: create, close, switch; closing the last one closes the window
|
||||
- Multiple terminals per tab in an arbitrary split tree, rearranged by keyboard
|
||||
- Multiple panes per tab in an arbitrary split tree, rearranged by keyboard
|
||||
or by dragging a pane's header, with draggable dividers between them;
|
||||
closing the last pane in a view closes its tab
|
||||
- **Web panes**, on WebKitGTK, sitting in the split tree beside terminals and
|
||||
dragging around exactly like they do: back/forward/reload, an address bar
|
||||
that takes a URL or falls back to a search, a load-progress indicator in the
|
||||
entry, and the page title feeding the pane header and tab label
|
||||
|
||||
### Shortcuts
|
||||
|
||||
@@ -151,16 +194,20 @@ in principle, but a terminal grid is small.
|
||||
|---|---|
|
||||
| `Ctrl+Shift+T` | new tab |
|
||||
| `Ctrl+Shift+E` | new terminal in the current tab |
|
||||
| `Ctrl+Shift+W` | close the focused terminal (closes the tab with its last one) |
|
||||
| `Ctrl+Shift+←/→/↑/↓` | move the focused terminal within its view |
|
||||
| `Ctrl+Shift+V` | paste (bracketed-paste aware, refuses unsafe pastes) |
|
||||
| `Ctrl+Shift+B` | new web view in the current tab |
|
||||
| `Ctrl+Shift+W` | close the focused pane (closes the tab with its last one) |
|
||||
| `Ctrl+Shift+←/→/↑/↓` | move the focused pane within its view |
|
||||
| `Ctrl+Shift+V` | paste into a terminal (bracketed-paste aware, refuses unsafe pastes) |
|
||||
| `Ctrl+PageUp/PageDown` | previous / next tab |
|
||||
| `Alt+1`..`Alt+8` | jump to tab N, `Alt+9` jumps to the last |
|
||||
|
||||
Dragging a pane by its header does the same thing as the arrow shortcuts: the
|
||||
edge you drop against decides both the order and the view's orientation. The
|
||||
header is the drag handle rather than the whole pane so that dragging never
|
||||
competes with the terminal's own mouse handling.
|
||||
competes with the content's own mouse handling.
|
||||
|
||||
Both add-pane shortcuts are also buttons in every pane header, which is how you
|
||||
open a web view without remembering the chord.
|
||||
|
||||
## Not implemented
|
||||
|
||||
@@ -174,6 +221,10 @@ This is a proof of concept, and the following are deliberately absent:
|
||||
look right.
|
||||
- **Moving panes between tabs.** Panes can only be rearranged within their
|
||||
own view.
|
||||
- **Browser furniture.** Web panes get navigation and an address bar and
|
||||
nothing else: no bookmarks, history, downloads, devtools, or find-in-page,
|
||||
and each pane uses WebKit's default context, so nothing is persisted between
|
||||
runs. Links that ask for a new window are ignored rather than opening a pane.
|
||||
- **Saved layouts.** A view's arrangement lives only as long as the tab.
|
||||
- **Kitty graphics, hyperlinks, tab reordering, config file.**
|
||||
- **Custom terminfo.** `TERM` is reported as `xterm-256color` rather than
|
||||
@@ -192,4 +243,17 @@ locked.
|
||||
One caveat when driving it: `wtype` loses the first keystroke of every
|
||||
invocation while the compositor adopts its freshly uploaded keymap, so scripted
|
||||
input should begin with a throwaway key. That is a quirk of the injection tool,
|
||||
not of the terminal.
|
||||
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.
|
||||
|
||||
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
|
||||
paths themselves come from the dev shell and are needed on a real session too —
|
||||
see [Running off-NixOS](#running-off-nixos).
|
||||
|
||||
Reference in New Issue
Block a user