fix: run Cap desktop + CLI on Linux (Studio Mode + CLI E2E)#1923
Conversation
- Add Linux branches for occluder/in-progress-recording window builders, intersects(), and is_system_dark_mode() in windows.rs - Add Linux SystemDiagnostics + collect_diagnostics() to cap-recording - Add Linux thumbnail capture reusing cap_recording::screenshot
…entry case - Gate tauri-plugin-single-instance off on Linux (blocking zbus init panics inside the Tokio runtime) - Rename src/App.tsx -> src/app.tsx so SolidStart's #start/app resolves on case-sensitive filesystems (worked on macOS only due to case-insensitivity)
… Linux - Drop WM decorations on Linux and draw macOS-style HTML traffic lights in the window-chrome, editor, and screenshot-editor headers (removes the native titlebar and stray window-manager cog button) - Size/position the target-select overlay and capture-area window to the full target display on Linux (were defaulting to a small partial window)
…inux set_ignore_cursor_events(true) on a not-yet-realized window aborted the app (non-unwinding panic in the GTK event loop) during window-capture recording. Skip applying the input shape until the GDK window exists.
d07f778 to
9db6c54
Compare
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
| #[cfg(target_os = "linux")] | ||
| { | ||
| use tauri::{LogicalSize, PhysicalPosition}; | ||
| let position = display.raw_handle().physical_position().unwrap(); | ||
| let size = display.physical_size().unwrap(); | ||
| let _ = window.set_position(PhysicalPosition::new(position.x(), position.y())); | ||
| let _ = window.set_size(LogicalSize::new(size.width(), size.height())); | ||
| } |
There was a problem hiding this comment.
Physical values passed as
LogicalSize
Both TargetSelectOverlay and CaptureTarget Linux post-build blocks derive size from physical_size() / physical_bounds() but pass those values into LogicalSize::new(...). On a 2× HiDPI display (fractional scaling via X11 or Wayland XWayland), the window will be rendered twice as large as the display. The Windows branch correctly obtains logical_size from display.logical_size() and uses that. The same fix should apply to both Linux post-build blocks (lines 1610–1617 and the analogous block around line 2330).
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/windows.rs
Line: 1610-1617
Comment:
**Physical values passed as `LogicalSize`**
Both `TargetSelectOverlay` and `CaptureTarget` Linux post-build blocks derive `size` from `physical_size()` / `physical_bounds()` but pass those values into `LogicalSize::new(...)`. On a 2× HiDPI display (fractional scaling via X11 or Wayland XWayland), the window will be rendered twice as large as the display. The Windows branch correctly obtains `logical_size` from `display.logical_size()` and uses that. The same fix should apply to both Linux post-build blocks (lines 1610–1617 and the analogous block around line 2330).
How can I resolve this? If you propose a fix, please make it concise.| fn is_system_dark_mode() -> bool { | ||
| let output = std::process::Command::new("gsettings") | ||
| .args(["get", "org.gnome.desktop.interface", "color-scheme"]) | ||
| .output(); | ||
| if let Ok(output) = output | ||
| && output.status.success() | ||
| { | ||
| return String::from_utf8_lossy(&output.stdout).contains("dark"); | ||
| } | ||
| false | ||
| } |
There was a problem hiding this comment.
Dark-mode detection is GNOME-only
gsettings queries org.gnome.desktop.interface which only exists on GNOME/libgnome-based desktops. On KDE Plasma, XFCE, Cinnamon, and bare window-manager setups the command will fail and the function silently returns false, forcing light mode for all non-GNOME Linux users. A fallback via XDG_CURRENT_DESKTOP or the cross-DE org.freedesktop.portal.Settings D-Bus portal would widen coverage.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/windows.rs
Line: 91-101
Comment:
**Dark-mode detection is GNOME-only**
`gsettings` queries `org.gnome.desktop.interface` which only exists on GNOME/libgnome-based desktops. On KDE Plasma, XFCE, Cinnamon, and bare window-manager setups the command will fail and the function silently returns `false`, forcing light mode for all non-GNOME Linux users. A fallback via `XDG_CURRENT_DESKTOP` or the cross-DE `org.freedesktop.portal.Settings` D-Bus portal would widen coverage.
How can I resolve this? If you propose a fix, please make it concise.| screen_capture_supported: true, | ||
| gpu_name, | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
screen_capture_supported is hardcoded true
The Linux SystemDiagnostics struct always sets screen_capture_supported: true without probing capture availability. On a pure Wayland compositor without XWayland or a headless CI container, cap doctor will incorrectly report captureReady: true. Checking the DISPLAY env var or attempting a trial capture would make this accurate.
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/recording/src/diagnostics.rs
Line: 569-573
Comment:
**`screen_capture_supported` is hardcoded `true`**
The Linux `SystemDiagnostics` struct always sets `screen_capture_supported: true` without probing capture availability. On a pure Wayland compositor without XWayland or a headless CI container, `cap doctor` will incorrectly report `captureReady: true`. Checking the `DISPLAY` env var or attempting a trial capture would make this accurate.
How can I resolve this? If you propose a fix, please make it concise.| let position = display.raw_handle().physical_position().unwrap(); | ||
| let size = display.physical_size().unwrap(); | ||
| let _ = window.set_position(PhysicalPosition::new(position.x(), position.y())); | ||
| let _ = window.set_size(LogicalSize::new(size.width(), size.height())); |
There was a problem hiding this comment.
Minor nit: this is using PhysicalPosition but then sets size via LogicalSize with physical dimensions. If you intend physical coordinates here, consider pairing it with PhysicalSize for clarity/consistency.
| let _ = window.set_size(LogicalSize::new(size.width(), size.height())); | |
| use tauri::{PhysicalPosition, PhysicalSize}; | |
| let position = display.raw_handle().physical_position().unwrap(); | |
| let size = display.physical_size().unwrap(); | |
| let _ = window.set_position(PhysicalPosition::new(position.x(), position.y())); | |
| let _ = window.set_size(PhysicalSize::new(size.width(), size.height())); |
| } | ||
|
|
||
| fn get_gpu_name() -> Option<String> { | ||
| let output = std::process::Command::new("glxinfo") |
There was a problem hiding this comment.
glxinfo -B can be surprisingly slow / noisy in headless contexts (no session/display). Might be worth short-circuiting if there’s no display env so cap doctor stays snappy.
| let output = std::process::Command::new("glxinfo") | |
| if std::env::var_os("DISPLAY").is_none() && std::env::var_os("WAYLAND_DISPLAY").is_none() { | |
| return None; | |
| } | |
| let output = std::process::Command::new("glxinfo") | |
| .arg("-B") | |
| .output() | |
| .ok()?; |
Overview
Gets the Linux build of Cap Desktop and the
capCLI compiling and running end-to-end, and validates Studio Mode recording → editor → export plus the full CLI record/export flow on Linux (X11 capture viax11grab, encode via system libav, rendering via wgpu onllvmpipe).The Linux target had several gaps that prevented
cap-desktopfrom compiling/running (it had only ever been built on macOS/Windows). This PR fills those gaps with platform-gated code only — no behavior change on macOS/Windows.Changes
crates/recording/src/diagnostics.rs— add a LinuxSystemDiagnostics+collect_diagnostics()(kernel version, available ffmpeg encoders, GPU name) so the desktop's diagnostics command compiles and works on Linux.apps/desktop/src-tauri/src/windows.rs— add Linux branches for the window-capture occluder position, the in-progress-recording window builder,MonitorExt::intersects, andis_system_dark_mode()(viagsettings).apps/desktop/src-tauri/src/thumbnails/— add a Linux thumbnail module that reuses the existing publiccap_recording::screenshot::capture_screenshot(X11) to produce display/window thumbnails.apps/desktop/src-tauri/src/lib.rs— skiptauri-plugin-single-instanceon Linux; its blockingzbusinit panics ("Cannot start a runtime from within a runtime") when initialized inside the Tokio runtime.apps/desktop/src/App.tsx→src/app.tsx— rename so SolidStart's#start/appimport resolves on case-sensitive filesystems. It only ever worked on macOS due to case-insensitive matching; on Linux the dev server failed withFailed to resolve import "#start/app".Testing
Cap CLI — record + export (X11)
✅
cap doctor→captureReady: true; ✅cap targets; ✅cap record start --detach/record stop(recordingMetaExists: true); ✅cap project validate(valid, studio); ✅cap export→ valid 18.5s H.264+AAC MP4; ✅cap screenshot.CLI studio export rendered frame
Cap Desktop — Studio Mode end-to-end
Record → editor opens automatically → export to a valid MP4.
cap_linux_studio_mode_record_to_editor.mp4
Main recording window and the editor (preview composited via wgpu/llvmpipe, timeline, background sidebar):
Main recording window
Cap editor with studio recording
Settings (all tabs render; Feedback tab shows the new Linux diagnostics encoders) and the desktop export completing:
Settings - General
Editor export complete
✅ Desktop editor export produced
/home/ubuntu/Cap 2026-06-18 at 21.53.54.mp4(H.264 1152x720 + AAC, 21.2s), confirmed withffprobe.To show artifacts inline, enable in settings.
Greptile Summary
This PR brings the Cap desktop app and CLI to Linux end-to-end by adding platform-gated code for X11 capture, window management, thumbnails, and diagnostics — with no behavior change on macOS or Windows.
windows.rs): Adds Linux branches for overlay positioning, in-progress-recording window builder,MonitorExt::intersects, and dark-mode detection viagsettings; the post-build size-setting blocks pass physical pixel values intoLogicalSize, which will produce over-sized windows on HiDPI displays. Dark-mode detection is also GNOME-only and falls back silently to light mode on other DEs.thumbnails/linux.rsreusescapture_screenshotwith graceful error handling;diagnostics.rsadds alinux_implfor kernel/GPU/encoder info withscreen_capture_supportedhardcodedtrue.zbuspanic;App.tsx→app.tsxrename for case-sensitive filesystems; vendoredtaounrealized-GDK-window crash replaced with a safeNonecheck.Confidence Score: 4/5
Safe to merge as a platform enablement PR; all non-Linux paths are unchanged and the Linux-specific code is cfg-gated throughout.
The window sizing code passes physical pixel values as LogicalSize in the Linux overlay and capture-target blocks, producing incorrectly-sized windows on HiDPI Linux setups. At 1x X11 scale (the tested configuration) this is invisible, but it is a latent issue for the next HiDPI user. Everything else is clean and well-tested.
apps/desktop/src-tauri/src/windows.rs — the two Linux post-build blocks that call set_size(LogicalSize::new(physical_width, physical_height))
Important Files Changed
Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "style: rustfmt windows.rs Linux capture-..." | Re-trigger Greptile