Skip to content

port(linux): 2.0 engine settings and status against the Linux engine (Phase B2) - #86

Merged
kalopsia-space merged 7 commits into
linux-2.0from
port/linux-2.0-engine-ui
Oct 17, 2026
Merged

kalopsia-space merged 7 commits into
linux-2.0from
port/linux-2.0-engine-ui

Conversation

@kalopsia-space

Copy link
Copy Markdown
Collaborator

Phase B2 of the 2.0 catch-up (docs/linux-2.0-catch-up.md), narrowed to the 2.0 UI driving the Linux engine from #82. Windows behaviour is unchanged: every change is either gated to non-Windows or only touches Linux-only code.

What changes

  • Playback device picker. The Settings list ran FTHRclips --list-playback-devices with subprocess.CREATE_NO_WINDOW. That flag does not exist on Linux, so the scan died with an uncaught AttributeError. On top of that, argv[24] was forced to ''. Now the scan runs with the host library environment, the chosen PulseAudio sink goes to argv[24], and the labels say "System default" and "default output". Changing the device restarts capture through the existing audio_capture_changed path.
  • Long buffers. On Linux, argv[6] is the byte budget the engine enforces on its compressed ring. The UI still sent the Windows raw-frame figure, capped at 2 GB, which cut a 900 s buffer at 50 Mbps to about 5 minutes. Linux now gets bitrate × (buffer + 5 s) × 1.25, kept within the engine's 64..16384 MB range and never more than half of RAM.
  • Encoder list. On Linux the AMD entry probed h264_amf, so AMD and Intel machines only saw Automatic and Software. It now probes VA-API (with hwupload on FTHR_VAAPI_DEVICE/renderD128) and then AMF, under the key the engine maps to VA-API (argv[17] = 2). The entry is labelled "VA-API (AMD / Intel)". Probes run with the host environment, and the Linux cache version is bumped so old profiles are probed again.
  • ffmpeg lookup. Restored the Linux executable names (ffmpeg/ffprobe), the frozen layout and FTHRcapture_linux/third_party/ffmpeg. 2.0 only looked for ffmpeg.exe, so it fell back to whatever ffmpeg was on PATH. The UI uses this ffmpeg to mix in the mic track and to probe encoders.
  • Mic mono. core/mic_recorder.py always records one channel and the engine ignores argv[25]. The 2.0 downmix switch is therefore shown checked and disabled, with that reason.
  • Status paths.
    • The engine reports focus pause as ACTIVE|PAUSED, which the UI was relabelling "CAPTURE DEGRADED". It now shows PAUSED — GAME UNFOCUSED.
    • After a terminal failure the engine publishes the reason, then exits 0. ENGINE STOPPED now says it stopped after that failure, instead of "exited unexpectedly (code 0)".
    • ENGINE STOPPED no longer flips to CONNECTING when no engine process is left.

Already handled on linux-2.0 and checked here without code changes: manual recording stays off on Linux (#83), RECONFIGURE_REQUIRED restarts are Windows-only, monitor validation takes the Linux output-name path, focus pause sends STOP/START_RECORDING with an empty resume string, and the custom clip-length actions (save_custom_clip_<n>) route through the hotkey socket.

Verification

Launch: the engine was built from this branch against the pinned FFmpeg. The app ran with a throwaway HOME and XDG_RUNTIME_DIR, HYPRLAND_INSTANCE_SIGNATURE unset and no session bus, on Hyprland with wlr-screencopy and an AMD GPU. A small driver drove the real settings widgets, and saves went through the hotkey socket. ffprobe results:

Step Engine log Clip (ffprobe)
save_clip (normal 10 s) h264_vaapi, encoder_pref=0 h264 10.000 s + aac 2 ch
save_custom_clip_20 same h264 20.000 s + aac 2 ch
Encoder → Software (Apply) restart, encoder_pref=4, Using: libopenh264, UI "Encoding active: libopenh264", no HARDWARE ENCODING UNAVAILABLE h264 10 s + aac
Encoder → VA-API, then Automatic encoder_pref=2 → h264_vaapi; encoder_pref=0 → NVENC tried, h264_vaapi h264 10 s + aac
Playback → Easy Effects Sink, then System default playback=easyeffects_sink, -> easyeffects_sink.monitor; then <default> h264 10 s + aac
Separate tracks on restart h264 + aac 2 ch "System Audio" + aac 1 ch "Microphone"
Audio capture off restart h264 only
900 s custom length at 200 Mbps Buffer=900s, Byte budget 15595 MiB, engine warns ~654 s coverage (half-RAM cap) n/a
encoder_pref=nvenc with no NVENC 2.0 HARDWARE ENCODING UNAVAILABLE, backend reason "No video encoder could be opened", then ENGINE STOPPED "stopped after the capture failure above", status stays ENGINE STOPPED; RESTART back to Auto captures again n/a
Focus pause / resume flags 17 → status PAUSED — GAME UNFOCUSED, then CAPTURING n/a
SIGKILL engine ENGINE STOPPED "exited unexpectedly (code -9)"; restart recovers n/a

Tests, run file by file under an isolated HOME:

  • Before: 1724 passed, 37 failed, 44 skipped.
  • After: 1734 passed, 39 failed, 41 skipped.
  • Same failures as before, except test_capture_backend_detection. Its test_binary_exists now passes because an engine is built in the tree. Its other 3 tests fail only because that binary needs LD_LIBRARY_PATH set to the pinned FFmpeg; with it set, 4/4 pass.
  • test_game_detection_popup segfaults under offscreen Qt both before and after.
  • New tests: tests/test_linux_engine_settings.py, plus Linux cases in test_engine_error_recovery.py (argv[6]/[17]/[23]/[24], no creationflags) and test_capture_settings_policy.py (ring budget).
  • ruff check . passes. tools/verify_exception_handling.py reports 83 silent handlers, baseline 92.

Deferred / out of scope (per maintainer scope change)

  • Screenshots on Wayland. They fail with CAPTURE_UNAVAILABLE: The configured monitor returned an empty screenshot, because QScreen cannot grab on Wayland. Restoring the grim / Screenshot-portal path from linux is a separate PR. This does not affect clip capture or saving.
  • Not started in this PR: the uploader / SFTP / Secret Service port, camera selection, and game detection / blacklist. Uploader work exists on a local branch only.
  • tools/qualify_alpha_stability.py. It passes Windows creationflags on Linux (test_strict_probe_requires_audio_when_requested). It lives in tools/, which PR Phase D: packaging, release gates and CI for the 2.0 catch-up #84 owns.
  • Remaining pre-existing failures, not engine-related:
    • Camera: test_camera_provider_failures, test_linux_camera_refresh.
    • Game detection: test_game_detector, test_foreground_game_detector.
    • Screenshot: test_wayland_screenshot_portal, test_screenshot_save.
    • Settings UI: test_encoder_capabilities expects a Linux capture-card checkbox that 2.0 removed, test_design_overhaul_repairs, test_settings_manager defaults.
    • test_capture_settings_popup: Windows monitor-path cases run without pinning the platform.
    • Release gates: test_asset_license_gate, test_qt_license_gate (PR Phase D: packaging, release gates and CI for the 2.0 catch-up #84).
    • Other: test_clip_viewer_p1_gate, test_alpha_ui_truth legacy crop, and test_windows_troubleshooting_docs (the docs script fails under node).
  • Possible follow-ups:
    • The active-encoder label in Settings only refreshes while the window is foreground. This is a 2.0 background-pause design, the same on Windows.
    • A UI hint when the ring budget cannot cover the whole buffer. Today only the engine logs it.

…gine

2.0's playback-device picker ran `FTHRclips --list-playback-devices` with
subprocess.CREATE_NO_WINDOW, which does not exist on Linux, so the scan
died with an AttributeError the worker did not catch, and the chosen
endpoint was then forced to '' for argv[24] on every non-Windows launch.

The Linux engine (#82) answers the same flag with PulseAudio sinks in the
same schema and captures `<sink>.monitor` from argv[24]. Run the scan with
the host library environment instead of the Windows flag, pass the saved
sink to the engine, and label the default entry and the fallback hint for
what the Linux engine does (default output when the sink is missing at
start). Selecting a device restarts capture through the existing
audio_capture_changed path, as on Windows. Windows text and flags are
unchanged.
argv[6] is a diagnostic raw-frame figure to the Windows engine, which is
why the UI derives it from width x height x fps and caps it at 2 GB. The
Linux engine enforces it as the byte budget of its compressed ring (#82),
so a 2.0 buffer of up to 900 s at a normal bitrate was silently cut to
whatever 2 GB covers: about 5 minutes at 50 Mbps.

On Linux pass a budget from bitrate x (buffer + 5 s) with 25% headroom
for VBR peaks, within the engine's 64..16384 MB range and never above half
of physical memory; the engine logs when that still cannot cover the whole
buffer. Windows keeps the old figure.
The 2.0 encoder list only offers backends whose one-frame probe succeeds.
On Linux the AMD entry probed h264_amf, which needs AMD's proprietary AMF
runtime, so AMD and Intel machines saw only Automatic and Software even
though the engine's AMD preference (argv[17] = 2) opens VA-API first.

On Linux probe VA-API (with a hwupload filter on the engine's render
node, FTHR_VAAPI_DEVICE or renderD128) and AMF under the same key and
label it "VA-API (AMD / Intel)", run probes with the host library
environment so a frozen build's ffmpeg can load the VA-API driver, and
bump the Linux cache version so old device profiles are re-probed.
Windows backends, labels and cache version are unchanged.
2.0 looks only for ffmpeg.exe/ffprobe.exe and dropped the Linux bundle
and source-tree locations, so a Linux build never found its own LGPL
ffmpeg and fell back to whatever ffmpeg is on PATH (or none). The UI
needs it to mix the Python microphone track into saved clips and to
probe encoders. Restore the platform executable names, the
beside-the-executable layout and FTHRcapture_linux/third_party/ffmpeg.
2.0's "Downmix stereo microphone to mono" sets argv[25] for the Windows
engine's native microphone capture. On Linux the engine captures no
microphone (argv[25] is ignored, #82) and the UI records it through
core/mic_recorder.py, which always opens a single channel, so a stereo
microphone is already downmixed and the switch did nothing. Show it
checked and disabled with that reason instead of offering a no-op
setting; Windows keeps the working control.
Three status paths read wrong against the Linux engine:

- The engine answers the focus-pause STOP_RECORDING with ACTIVE|PAUSED,
  which capture health maps to DEGRADED, so one poll after "PAUSED -
  GAME UNFOCUSED" the status bar claimed "CAPTURE DEGRADED". Show the
  pause while the engine reports it.
- After a terminal capture failure the Linux engine publishes
  BACKEND_FAILED with its reason, waits 2 s and exits 0. The 2.0 UI then
  added "exited unexpectedly (code 0)". Say that the engine stopped after
  the failure already shown; crashes and non-zero exits keep the old
  text.
- With no engine process left, the status poll replaced ENGINE STOPPED
  with CONNECTING although nothing would ever be connected to. Keep
  ENGINE STOPPED until the user restarts capture.

All three are gated to non-Windows; Windows status text is unchanged.
Playback-sink listing without CREATE_NO_WINDOW and with the host
environment, sink selection and its unavailable fallback label, the
always-mono microphone control, the VA-API probe (and an unchanged
Windows probe), the focus-pause and engine-exit status helpers, and the
Linux ffmpeg lookup.
@kalopsia-space
kalopsia-space merged commit aa0dc9b into linux-2.0 Oct 17, 2026
5 of 9 checks passed
@kalopsia-space
kalopsia-space deleted the port/linux-2.0-engine-ui branch October 17, 2026 14:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant