aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-02 22:13:26 -0400
committerCraig Jennings <c@cjennings.net>2026-07-02 22:13:26 -0400
commit703f106dfc30e2b10ab9eec1ceb6c64434d2f7d6 (patch)
tree18556fae2b6870d6f20397f291f2c4afb4e7cec5
parent7f876206c3374d9627cd134b8dae3878df803a5f (diff)
downloadarchsetup-703f106dfc30e2b10ab9eec1ceb6c64434d2f7d6.tar.gz
archsetup-703f106dfc30e2b10ab9eec1ceb6c64434d2f7d6.zip
docs(spec): file-manager swallow pattern, native swallow ruled out by test
Hyprland's enable_swallow matches by PID ancestry, and nautilus's gio launch path orphans the handler process (verified live: the spawned viewer reparents to init while the launcher still runs). The spec designs an event-listener daemon on the IPC socket instead — swallow nautilus when a window opens while it's active, restore on that window's close. DRAFT with four decisions pending review.
-rw-r--r--docs/design/2026-07-02-file-manager-swallow-spec.org131
-rw-r--r--todo.org5
2 files changed, 136 insertions, 0 deletions
diff --git a/docs/design/2026-07-02-file-manager-swallow-spec.org b/docs/design/2026-07-02-file-manager-swallow-spec.org
new file mode 100644
index 0000000..0b51165
--- /dev/null
+++ b/docs/design/2026-07-02-file-manager-swallow-spec.org
@@ -0,0 +1,131 @@
+#+TITLE: File-Manager Swallow Pattern
+#+AUTHOR: Craig Jennings
+#+DATE: 2026-07-02
+#+TODO: TODO | DONE
+#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED
+
+* DRAFT Status
+:PROPERTIES:
+:ID: d92e0074-f594-4e83-81a0-faf282e15ed0
+:END:
+- [2026-07-02 Thu] DRAFT — initial spec from Craig's roam capture ("when the
+ file manager launches another app, it should hide and return when that
+ process ends"). Feasibility ground truth sampled live on velox same
+ evening: Hyprland's native swallow cannot work here (see Problem), so the
+ design is an event-listener daemon.
+
+* Metadata
+
+| Field | Value |
+|--------+----------------------------------------------------|
+| Status | draft |
+|--------+----------------------------------------------------|
+| Owner | Craig Jennings |
+|--------+----------------------------------------------------|
+| Repo | dotfiles (daemon + config); archsetup (none) |
+|--------+----------------------------------------------------|
+| Kin | touchpad-auto (socket-listener donor), |
+| | hypr-refocus-scratchpad (event-daemon sibling) |
+|--------+----------------------------------------------------|
+
+* Problem
+
+Opening a file from nautilus (Super+Shift+F, tiled, class
+=org.gnome.Nautilus=) spawns a viewer window while nautilus stays in the
+layout. The wanted behavior is the swallow pattern: the file manager hides
+while the app it launched runs, and returns when that app exits. Today
+there's no signal connecting the two windows — the viewer lands wherever
+the layout puts it, nautilus lingers, and quitting is manual.
+
+*Hyprland's native swallow is ruled out — measured, not assumed.*
+=misc:enable_swallow= + =swallow_regex= would be exactly this feature in two
+config lines, but it matches by walking the new window's PID ancestry to
+the swallow candidate's PID. Nautilus launches handlers through GLib
+(=g_app_info_launch_default_for_uri=), and that path orphans the child:
+reproduced live on velox 2026-07-02 with a python-gi launcher — feh came up
+with PPID 1 (reparented to init) while the launcher was still alive. The
+ancestry walk hits init before it hits nautilus, every time, for every
+handler. Any design that depends on PID parentage is dead on arrival; the
+signal has to come from window events instead.
+
+Ground truth on handlers (velox, 2026-07-02): pdf → zathura, image → feh,
+video → mpv, text/code → emacsclient (window belongs to the emacs daemon).
+Side-note, out of scope here: feh is X11 — an XWayland viewer on a
+no-XWayland-by-preference setup; a default-handler review is its own task.
+
+* Goals
+
+- Double-click a file in nautilus → the viewer takes its place; nautilus is
+ gone (special workspace, not killed — state and tabs survive).
+- Quit the viewer → nautilus returns and has focus.
+- Nothing else changes: terminals, scratchpads, and every other window keep
+ their current behavior.
+- Config-driven, testable logic, one small daemon — the touchpad-auto shape.
+
+* Design sketch
+
+A =hypr-swallow= daemon (dotfiles, =hyprland/.local/bin/=) listening on the
+Hyprland IPC event socket (socket2), same as =touchpad-auto=:
+
+- Track the active window (=activewindow>>= events carry class + title;
+ =activewindowv2>>= carries the address).
+- On =openwindow>>= (address, workspace, class, title) while the active
+ window's class is a configured *parent* (nautilus): dispatch
+ =movetoworkspacesilent special:swallow,address:0x<parent>=, record
+ child-address → {parent-address, origin workspace}.
+- On =closewindow>>= of a recorded child: bring the parent back
+ (=movetoworkspace=) and focus it; drop the record.
+- On =closewindow>>= of a hidden parent (nautilus quit while hidden): drop
+ the record, nothing to restore.
+- Exception classes (fuzzel, dunst, scratchpad classes, the panels) never
+ trigger a swallow even when they open over nautilus.
+- Pure event-machine core (parse lines → state transitions → dispatch list),
+ unit-tested against recorded event streams; a thin socket loop around it.
+
+Known edge, handled: Super+Shift+F while nautilus is hidden re-runs
+=nautilus=, which activates the existing (hidden) instance instead of
+opening a window. The daemon (or the bind) must restore-and-untrack in that
+case so the bind never appears dead.
+
+Known limitation, accepted: the emacsclient case never swallows — the
+window belongs to the long-running emacs daemon and =closewindow= for it
+means a frame closed, not "the file is done." The parent-class trigger plus
+exception list naturally leaves it alone only if we exclude it explicitly —
+see decision 2.
+
+* Decisions (Craig)
+
+** TODO Trigger breadth: any new window while nautilus is active, or an allowlist of viewer classes?
+"Any window" is simple and catches every handler, but a false positive
+exists: an app you launched seconds earlier from elsewhere finishes starting
+while you're focused on nautilus → nautilus gets swallowed by an unrelated
+window. An allowlist (zathura, mpv, imv, feh, …) can't be surprised but
+needs maintaining. Recommendation: any-window + exception list — the false
+positive is rare and self-healing (close the window or refocus).
+
+** TODO The emacs frame case: swallow or exempt?
+Opening a text file from nautilus raises/creates an emacs frame. Swallowing
+nautilus under it "works" going in, but the restore fires when *any* frame
+closes, which may be much later or never. Recommendation: exempt =emacs= —
+text files just open, nautilus stays.
+
+** TODO Restore destination: the workspace nautilus came from, or the one you're on when the viewer closes?
+If you move the viewer to another workspace and quit it there, "origin"
+teleports you back; "current" brings nautilus to you. Recommendation:
+current workspace — the restore should land where your attention is.
+
+** TODO Multiple children: refcount or single-slot?
+You can only launch a second file after restoring nautilus manually, so
+overlap is rare — but a fast double-launch can record two children.
+Recommendation: refcount — restore when the last tracked child closes.
+
+* Implementation phases
+
+1. =hypr-swallow= core: pure event-machine (TDD over recorded event
+ streams; fake hyprctl for dispatch assertions), config block at the top
+ (parent classes, exception classes), unittest suite in =tests/=.
+2. Socket loop + wiring: exec-once in hyprland.conf, the Super+Shift+F
+ restore-if-hidden interplay, daemon single-instance guard.
+3. Live verification on velox (zathura + mpv round-trips, the emacs case,
+ the false-positive probe) + manual-testing entries; ratio rides the
+ dotfiles pull.
diff --git a/todo.org b/todo.org
index 2404be6..9ffa1a2 100644
--- a/todo.org
+++ b/todo.org
@@ -77,8 +77,13 @@ Probe suites over fake tailscale/nmcli/protonvpn (19, phase 1), panel-model Tunn
:PROPERTIES:
:LAST_REVIEWED: 2026-07-02
:END:
+Initial spec written 2026-07-02: [[file:docs/design/2026-07-02-file-manager-swallow-spec.org]] (DRAFT — four decisions await Craig's review before build; event-listener daemon in dotfiles, touchpad-auto kin).
+
When the file manager launches another app, it should hide to a special workspace (the "swallow" pattern) and return when that process ends, rather than vanishing. Today it disappears with no signal of whether it's coming back, so the user can't tell success from failure — they should quit explicitly instead. Origin: roam inbox capture.
+*** 2026-07-02 Thu @ 22:20:00 -0400 Feasibility ground truth: Hyprland native swallow ruled out
+=misc:enable_swallow= would be the whole feature in two config lines, but it matches by PID ancestry, and nautilus's launch path (GLib =g_app_info_launch_default_for_uri=) orphans the handler — reproduced live on velox with a python-gi launcher: feh came up with PPID 1 while the launcher was still running. The spec's design is therefore an event-listener daemon (socket2 =openwindow=/=closewindow= while nautilus is active), the touchpad-auto shape. Handlers sampled: pdf → zathura, image → feh (X11 — flagged as a side task), video → mpv, text → emacsclient (exempt candidate, decision 2).
+
** DONE [#C] Open meeting links in the browser instead of the Zoom app :feature:
CLOSED: [2026-07-02 Thu]
Shipped 2026-07-02, mechanism per Craig ("the Linux zoom app is really terrible — one less dependency"): a =zoommtg://= URL handler, and the native app retired outright. =zoom-web= (dotfiles 187414a, 10 tests) registers as the xdg default for x-scheme-handler/zoommtg via =zoom-web.desktop=; Zoom's launch-page bounce rewrites deterministically to =https://<host>/wc/join/<confno>?pwd=…= in the default browser (subdomain hosts preserved, tracking params dropped, start action mapped, malformed URIs notify + exit 2). The registration landed in the stowed mimeapps.list, so it ships with dotfiles. Zoom uninstalled from velox (=pacman -Rns=), its windowrules removed from hyprland.conf, =aur_install zoom= dropped from archsetup, and the VM retired-package assertion now covers blueman + zoom. Known limit, accepted: a host who disabled join-from-browser blocks the web client — that meeting needs the native app installed ad hoc. Ratio trip: =pacman -Rns zoom= + the pull brings the handler; run =xdg-mime default zoom-web.desktop x-scheme-handler/zoommtg= if the stowed mimeapps.list doesn't take effect.