feat(vdisplay): mirror a pinned monitor on sway and Hyprland
P5 of design/per-monitor-portal-capture.md, and the end of a hole worth naming: the pin was accepted, persisted and offered in the console while `create` bailed on these two backends, so a sway or Hyprland host with a monitor selected failed EVERY session. Both already ship the mechanism — a managed portal-chooser config plus a per-session selection file (xdpw's `Monitor: NAME`, xdph's `[SELECTION]screen:NAME`) — so mirroring is that same chooser pointed at a physical connector instead of a headless output we created. The keepalive stops the cast and nothing else; the monitor is the compositor's. The selection write is now serialized against the handshake that reads it (SELECTION_LOCK, applied to the virtual-output paths too). That file is one per-user path: whoever writes last before the portal reads wins, and the loser does not fail — it silently captures the other session's output. `managed` is no longer grounds for refusal everywhere. It is conclusive only where the name is ours by construction (KWin's `Virtual-punktfunk`, Hyprland's `PF-N`); sway names EVERY headless output `HEADLESS-N`, its own included, so the old rule would have refused to mirror any output on a headless sway box — exactly the remote setup this feature serves. Found on-glass, and now a test. Verified on a two-output headless sway: asking for HEADLESS-1 yields 1280x720 frames and HEADLESS-2 yields 1920x1080, so the chooser really does steer per request. Hyprland is compile-only — no Hyprland box exists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -390,7 +390,7 @@ fn session_thread(
|
||||
pub(crate) fn stream_existing_output(
|
||||
connector: &str,
|
||||
hw_cursor: bool,
|
||||
) -> Result<(u32, Box<dyn Send>)> {
|
||||
) -> Result<crate::mirror::MirrorStream> {
|
||||
let (setup_tx, setup_rx) = std::sync::mpsc::channel::<Result<u32, String>>();
|
||||
let stop = Arc::new(AtomicBool::new(false));
|
||||
let stop_thread = stop.clone();
|
||||
@@ -404,7 +404,12 @@ pub(crate) fn stream_existing_output(
|
||||
Ok(Err(e)) => bail!("Mutter monitor mirror failed: {e}"),
|
||||
Err(_) => bail!("timed out recording the Mutter output {connector:?}"),
|
||||
};
|
||||
Ok((node_id, Box::new(MirrorStop(stop)) as Box<dyn Send>))
|
||||
Ok(crate::mirror::MirrorStream {
|
||||
node_id,
|
||||
// Mutter's RecordMonitor node lives on the user's PipeWire daemon (like RecordVirtual).
|
||||
remote_fd: None,
|
||||
keepalive: Box::new(MirrorStop(stop)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Stops the mirror thread — and with it the recording — when the capturer drops it.
|
||||
|
||||
Reference in New Issue
Block a user