feat: keep Clipbin recording in the tray
Add native Linux SNI and Windows notification-area trays, single-instance reopening, background startup and login entries. Preserve recording and discovery after close, with clean Quit and reachable no-tray fallbacks. Track capture-source audio delivery independently of silent output tracks and warn without stopping video. Give clip metadata its own row and shape labels at final bounds to avoid GPUI 0.2.2 truncation cache and text-run bugs. Validated with strict workspace checks, 57 tests, Windows core/OBS/probe cross-clippy, all ten capture scenarios, tray/session/clips/playback rigs and narrow/wide screenshots. Document runtime validation gaps.
This commit is contained in:
+166
-14
@@ -8,9 +8,9 @@ pass/fail results.
|
||||
|
||||
| Where | Script | Status |
|
||||
| --- | --- | --- |
|
||||
| Linux, headless rig (Wayland portal + Xwayland) | `scripts/validate/linux-rig.sh` | All 9 scenarios pass on devbox (Intel UHD 770, VA-API) |
|
||||
| Linux, headless rig (Wayland portal + Xwayland) | `scripts/validate/linux-rig.sh` | All 10 scenarios pass on devbox (Intel UHD 770, VA-API), 2026-10-13 |
|
||||
| Linux, real session (CachyOS, Hyprland, RX 9070 XT, 1440p60) | `scripts/validate/linux-hardware.sh` | Passed 2026-10-08 (run 4, dc9cbd6) |
|
||||
| Linux desktop automatic sessions (headless Wayland) | `scripts/validate/linux-session.sh` | Passed 2026-10-08: Recording sidebar, game exit, library reload, window close and active Quit |
|
||||
| Linux desktop automatic sessions (headless Wayland) | `scripts/validate/linux-session.sh` | Passed 2026-10-13: recording sidebar, close-to-tray, game exit, second-launch reopen and active Quit |
|
||||
| Windows (Game Capture, NVENC/AMF/QSV) | `scripts/validate/windows.ps1` | Not run yet; script untested |
|
||||
|
||||
## Probe commands
|
||||
@@ -174,8 +174,11 @@ Windows status:
|
||||
monitor's own size, set when capture first has a picture (portrait monitors
|
||||
record portrait). If the window is resized mid-session the size stays and
|
||||
the picture is scaled to fit.
|
||||
- **Audio capture failures are not detected yet.** When the PulseAudio server
|
||||
was unreachable, recording continued with a silent track.
|
||||
- **Audio delivery health.** The configured system/microphone capture sources
|
||||
are monitored directly through OBS audio capture callbacks. After five seconds
|
||||
without delivery, recording is degraded with “audio capture lost”; video
|
||||
continues. The silent muxer track cannot mask absent source callbacks. See
|
||||
Background operation below for validation and limits.
|
||||
- **Linux runtime dependencies.** The Linux OBS build links the system's
|
||||
FFmpeg, PipeWire and other libraries, so it is tied to the distribution it
|
||||
was built on. Packaging (for example Flatpak, or bundling FFmpeg) is an open
|
||||
@@ -251,17 +254,19 @@ Real Steam validated 2026-10-08 on CachyOS/Hyprland (native Steam): Escape
|
||||
from Duckov (AppId 3167020, Proton Experimental under SteamLinuxRuntime_4)
|
||||
runs under `reaper SteamLaunch AppId=3167020 -- …/_v2-entry-point … proton
|
||||
waitforexitandrun …/Duckov.exe`. Clipbin detected it, named it from its
|
||||
manifest ("Escape from Duckov") and recorded the session. Audio-source
|
||||
failure detection remains limited as described above.
|
||||
manifest ("Escape from Duckov") and recorded the session. Audio delivery loss
|
||||
is now detected as described below; the new warning still needs real hardware
|
||||
and Windows validation.
|
||||
|
||||
GPUI 0.2.2 ends its Linux event loop when the last window is removed, even
|
||||
without an application close callback. Main keeps the recorder worker alive
|
||||
after that return. Closing the window continues the current game session;
|
||||
when that game exits, it finalises and the process exits. If no session is
|
||||
active, it exits immediately. It stops discovering new games once closed,
|
||||
so there is no indefinitely running invisible app. Reopening the window and
|
||||
persistent background discovery need the future tray integration. Explicit
|
||||
Quit/Ctrl+Q stops and finalises the current recording immediately.
|
||||
GPUI 0.2.2 ends its Linux and Windows event loops when the last window is
|
||||
removed. Clipbin keeps the recorder, tray and single-instance endpoint alive
|
||||
outside each GPUI run, and constructs a fresh Application on the main thread
|
||||
when reopened. The previous route is restored and the library/settings are
|
||||
reloaded; unavailable sessions fall back to the library. This also releases
|
||||
playback resources while the interface is closed. No fake or hidden GPUI window
|
||||
is kept alive: GPUI's Linux backends ignore WindowOptions::show. Window bounds,
|
||||
scroll offsets and unsaved editor selection are not restored. Explicit Quit
|
||||
waits for the existing worker's clean session finish before joining/exiting.
|
||||
|
||||
|
||||
## Clips, bookmarks and storage cleanup
|
||||
@@ -497,3 +502,150 @@ Logs are `/tmp/clipbin-clips-quit-fixed.log` and
|
||||
output directories. `bash -n`, `git diff --check` and `cargo fmt --check` also
|
||||
passed. This follow-up changes only the Linux rig scripts and documentation;
|
||||
Windows runtime and real Hyprland validation limits above remain unchanged.
|
||||
|
||||
## Background operation (2026-10-13)
|
||||
|
||||
Linux uses [ksni 0.3.6](https://github.com/iovxw/ksni) with the blocking,
|
||||
async-io API. It implements SNI and com.canonical.dbusmenu on its own executor,
|
||||
works on Wayland and X11, and requires no GTK main loop. Using tray-icon on
|
||||
Linux would require GTK alongside GPUI's main-thread loop, so it is only a
|
||||
Windows dependency. Windows uses [tray-icon 0.26.1](https://github.com/tauri-apps/tray-icon)
|
||||
with its menu and notification-area icon on a dedicated Win32 message thread;
|
||||
its documented Windows event loop does not need the main thread. GPUI owns the
|
||||
main thread on both platforms. Platform services live in clipbin-core and are
|
||||
included in Windows cross-clippy. The new raw Win32 calls are confined to
|
||||
windows_background.rs, with a module unsafe allowance, SAFETY comments and
|
||||
crate-level unsafe_code=deny; desktop retains the workspace forbid policy.
|
||||
|
||||
The tray has purple idle, red recording and amber failed/degraded variants,
|
||||
with status, game and error detail in its tooltip. Open Clipbin, Save clip,
|
||||
Add bookmark and Quit use the recorder's existing actions. Linux requires an
|
||||
SNI watcher with a registered host: startup failure opens the interface even
|
||||
with --background, logs the reason and makes close finish/quit. A watcher that
|
||||
disappears later requests Open and disables close-to-tray until it returns.
|
||||
This avoids leaving an unreachable process after a bar/watcher disappears.
|
||||
|
||||
Linux owns dev.clipbin.Clipbin on the session bus and exposes Open at
|
||||
/dev/clipbin/Clipbin. A second process requests the same name without queueing,
|
||||
calls Open on the owner and exits before starting OBS or reconciling autostart.
|
||||
A session bus is required; endpoint acquisition errors are logged and startup
|
||||
exits rather than starting a duplicate recorder. Windows holds a per-user
|
||||
Local named mutex and posts a private message to the first instance's hidden
|
||||
endpoint window, with a bounded five-second startup race wait. The hidden
|
||||
endpoint owns its message thread independently of the GPUI windows.
|
||||
|
||||
Start on login reconciles on startup and each settings save. Linux writes or
|
||||
removes $XDG_CONFIG_HOME/autostart/clipbin.desktop (default ~/.config/autostart),
|
||||
with a quoted executable path and --background; Windows writes/removes the
|
||||
Clipbin value in HKCU\Software\Microsoft\Windows\CurrentVersion\Run. Changing
|
||||
login settings does not interrupt capture. The executable must stay at its
|
||||
installed path; development builds point the entry at the build executable.
|
||||
Actual desktop login dispatch remains unvalidated.
|
||||
|
||||
Audio health uses [OBS 32.0.4's source capture callback API](https://github.com/obsproject/obs-studio/blob/32.0.4/libobs/obs-source.c)
|
||||
on every configured system/microphone source, independently of the encoder,
|
||||
mixed audio counters and silent output track. A boxed callback context stores
|
||||
an atomic monotonic delivery timestamp; it performs no allocation or locking.
|
||||
The context is removed before source release: OBS's audio_cb_mutex serialises
|
||||
callback removal with callbacks. Five seconds without delivery (including a
|
||||
source that never starts) is a warning-level RecordingStatus::Degraded,
|
||||
with “audio capture lost” in the sidebar and tray. A new callback clears the
|
||||
warning; video/source/output/disk failures still take precedence and stop
|
||||
recording. Intentionally disabled audio has no callback and no warning. This
|
||||
checks delivery, not amplitude: valid silent samples are healthy. It cannot
|
||||
detect a driver delivering endless synthetic silence, and WASAPI silence or
|
||||
idle-device callback behaviour needs a Windows runtime check. No automatic
|
||||
source restart is added; video continues and audio can recover if OBS resumes
|
||||
its source. Source identity is not displayed separately for system vs mic loss.
|
||||
|
||||
The rig's audio failure injection kills its private pipewire-pulse daemon,
|
||||
leaving PipeWire video and the portal alive. Unloading the null sink and
|
||||
requesting kill-source-output did not reliably stop delivery in PipeWire 1.6.8;
|
||||
killing that private audio server deterministically stops source callbacks.
|
||||
The original nine scenarios are retained alongside this tenth warning case.
|
||||
The rig now uses an explicit private SWAYSOCK and waits for it; discovery by
|
||||
the previous sway-ipc glob did not locate the installed Sway 1.12 endpoint.
|
||||
CLIPBIN_RIG_KEEP_LOGS also preserves startup-failure logs.
|
||||
|
||||
Windows limitations for this step: core/OBS/probe cross-clippy covers the native
|
||||
tray, message pump, named mutex/hidden-window endpoint, HKCU autostart and OBS
|
||||
callback registration/removal. Nothing new has been linked or run on Windows.
|
||||
GPUI desktop and clipbin-media remain excluded from that lint set because the
|
||||
MSVC C toolchain/FFmpeg development import libraries are unavailable here.
|
||||
Unvalidated: tray rendering/menu focus/Explorer restart, icon/tooltips, singleton
|
||||
startup races and multi-user/desktop behaviour, foreground activation, repeated
|
||||
GPUI Application creation after close, clean Quit/file closure, --background,
|
||||
registry writes and real login launch, WASAPI delivery-loss/recovery/idle
|
||||
semantics, and the Clips layout on Windows. Real Linux desktop trays (Hyprland
|
||||
Waybar, KDE, GNOME extensions), login launch, SNI host replacement/re-registration
|
||||
and native X11 tray lifecycle still need runtime validation.
|
||||
|
||||
Clips cards give title/subtitle their own row and keep fixed-size Play/Delete
|
||||
controls below. The pinned GPUI text element caches nowrap widths independently
|
||||
of truncation width and mutates its text runs during repeated measurements;
|
||||
ordinary truncate produced hard clipping, ellipsis-only text, or fallback fonts
|
||||
in the grid. A small private canvas label uses GPUI's line-wrapper and shaping
|
||||
APIs once at the final bounds, retaining Geist Mono, colour, weight and proper
|
||||
ellipsis without patching GPUI. Newlines in game names become spaces. Sample
|
||||
screenshots at 1100x760 and 1600x1000 were inspected: all sample metadata is
|
||||
readable and controls cannot steal its width. The amber audio warning was also
|
||||
inspected in lifecycle-final/audio-lost.png. Screenshot validation exercises
|
||||
the actual rendering path; no unit test can reproduce the GPU/grid text
|
||||
measurement chain without the rig.
|
||||
|
||||
Validation commands, from the background-tray worktree (all passed):
|
||||
|
||||
```sh
|
||||
env PATH=/usr/bin:/bin:$HOME/.cargo/bin cargo fmt --all -- --check
|
||||
env PATH=/usr/bin:/bin:$HOME/.cargo/bin cargo clippy --locked --workspace --all-targets -- -D warnings
|
||||
env PATH=/usr/bin:/bin:$HOME/.cargo/bin cargo test --locked --workspace
|
||||
RUSTUP_HOME=$HOME/.cache/clipbin/rustup CARGO_HOME=$HOME/.cache/clipbin/cargo-home PATH=$HOME/.cache/clipbin/cargo-home/bin:$PATH CARGO_TARGET_DIR=target/windows-check cargo clippy --locked --target x86_64-pc-windows-msvc -p clipbin-core -p clipbin-obs -p clipbin-probe --all-targets -- -D warnings
|
||||
```
|
||||
|
||||
57 workspace tests passed (generated-media tests keep their existing opt-in
|
||||
fixture behaviour). The added status test covers audio degradation, recovery
|
||||
and video-loss precedence. The dependency's existing proc-macro-error2 future
|
||||
incompatibility notice is still printed; strict current clippy passes.
|
||||
|
||||
/tmp is a user-quota tmpfs here: an earlier recording probe failed with
|
||||
Disk quota exceeded despite df reporting free bytes. Successful serial rig
|
||||
runs used repository output storage, with a short absolute TMPDIR so private
|
||||
Unix socket paths fit their limits:
|
||||
|
||||
```sh
|
||||
export PATH=/usr/bin:/bin:$HOME/.cargo/bin
|
||||
export TMPDIR=/home/harry/Dev/clipbin-desktop/target/rig-tmp
|
||||
out=/home/harry/Dev/clipbin-desktop/target/background-validation
|
||||
mkdir -p "$TMPDIR"
|
||||
scripts/validate/linux-rig.sh "$out/capture"
|
||||
scripts/validate/linux-background.sh "$out/lifecycle-final"
|
||||
scripts/validate/linux-session.sh "$out/sessions"
|
||||
scripts/validate/linux-clips.sh "$out/clips"
|
||||
scripts/validate/linux-playback.sh "$out/playback"
|
||||
CLIPBIN_RIG_WIDTH=1100 CLIPBIN_RIG_HEIGHT=760 scripts/rig/screenshot-ui.sh "$out/screens-narrow-labels" clips
|
||||
CLIPBIN_RIG_WIDTH=1600 CLIPBIN_RIG_HEIGHT=1000 scripts/rig/screenshot-ui.sh "$out/screens-wide-labels" clips
|
||||
```
|
||||
|
||||
Capture: all original nine scenarios plus audio loss passed. Lifecycle: actual
|
||||
watcher registration and D-Bus menu Open/Quit, recording tooltip, no-window
|
||||
background start, autostart toggle removal/write and startup reconciliation,
|
||||
segments growing after close, second launch reopening the same process,
|
||||
preserved Settings route, degraded UI/tooltip, clean active-session Quit,
|
||||
watcher-loss reveal and no-host close=Quit fallback passed. Sessions: game exit,
|
||||
exact cumulative segment placement, continued background discovery/library
|
||||
reload, second-launch Open and active Ctrl+Q passed. Clips: H.264 MP4 retained,
|
||||
360 CFR frames, continuous audio, zero decode errors, original press range,
|
||||
bookmarks, tiny-budget cleanup and bookmark re-export passed. Playback: moving
|
||||
preview (6.1% changed pixels), device audio RMS 0.08027, thumbnails, continued
|
||||
recording, gaps, cross-boundary seek, volume/mute and held-key controls passed.
|
||||
Decode median/p95 was 6.335/10.472 ms; GPUI upload median/p95 was 0.208/0.404 ms.
|
||||
Both old UI suites still use explicit Quit and completed successfully.
|
||||
|
||||
The simplify quick self-review covered reuse, clarity and efficiency before
|
||||
commit: removed the obsolete window-close shutdown mode, reused existing clip,
|
||||
bookmark and clean-finish paths, avoided copying the full library for tray
|
||||
polling, limited icon updates to state changes, kept callbacks allocation/lock
|
||||
free, and made native tray/thread cleanup explicit. The text helper is limited
|
||||
to the demonstrated GPUI 0.2.2 rendering bug; no dependency fork or general UI
|
||||
framework was added. Native Windows runtime and real desktop limits above
|
||||
remain unchanged.
|
||||
|
||||
Reference in New Issue
Block a user