#+TITLE: File-Manager Swallow Pattern #+AUTHOR: Craig Jennings #+DATE: 2026-07-02 #+TODO: TODO | DONE #+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED * CANCELLED Status :PROPERTIES: :ID: d92e0074-f594-4e83-81a0-faf282e15ed0 :END: - [2026-07-02 Thu] CANCELLED — targeted the wrong file manager. Craig's ask is about the dirvish popup (Super+F, an Emacs frame), not nautilus (the Super+Shift+F bind that misled the grounding). For dirvish the right design is elisp-side and strictly better: Emacs is the launcher, so it can spawn the handler directly (=start-process=), hide the popup frame, and restore it from a process sentinel — exact exit tracking plus a failure notify, no window-event heuristics. Reassigned to .emacs.d via its inbox (2026-07-02-2231-from-archsetup-dirvish-popup-swallow-handoff). The gio double-fork finding below stands for any gio-launching file manager; the daemon design is kept for reference only. - [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 | cancelled | |--------+----------------------------------------------------| | 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=, 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.