fix(vdisplay/mutter): make the virtual output the SOLE display, not primary + secondary
ci / web (push) Failing after 38s
ci / rust (push) Successful in 55s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 3s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3s
ci / docs-site (push) Failing after 43s
docker / deploy-docs (push) Successful in 16s
apple / swift (push) Successful in 1m13s
ci / web (push) Failing after 38s
ci / rust (push) Successful in 55s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 3s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3s
ci / docs-site (push) Failing after 43s
docker / deploy-docs (push) Successful in 16s
apple / swift (push) Successful in 1m13s
Keeping the physical monitor enabled as a secondary let the cursor, windows, and keyboard focus land on it — relative pointer motion wandered off the streamed surface, so on the client the cursor "disappeared" and clicks/keys went nowhere visible. Omit the physical outputs from ApplyMonitorsConfig so Mutter disables them for the session; everything is confined to the streamed virtual output. Restored on teardown. Validated on-box: mid-session DisplayConfig shows only the virtual output (Meta-0) as the sole primary; the physical (HDMI-1) is restored after the session ends. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -382,7 +382,9 @@ fn current_mode(state: &CurrentState, connector: &str) -> Option<(String, i32, i
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Wait for the virtual output to appear in DisplayConfig (its size follows PipeWire negotiation,
|
/// Wait for the virtual output to appear in DisplayConfig (its size follows PipeWire negotiation,
|
||||||
/// which lands shortly after the node id), then promote it to primary with the physicals kept on.
|
/// which lands shortly after the node id), then make it the SOLE primary output (physicals
|
||||||
|
/// disabled for the session) so the cursor, windows, and keyboard focus stay on the streamed
|
||||||
|
/// surface. Restored on teardown.
|
||||||
async fn make_virtual_primary(dc: &zbus::Proxy<'_>, mode: Mode, pre: &CurrentState) -> Result<()> {
|
async fn make_virtual_primary(dc: &zbus::Proxy<'_>, mode: Mode, pre: &CurrentState) -> Result<()> {
|
||||||
let pre_conns = connectors(pre);
|
let pre_conns = connectors(pre);
|
||||||
let deadline = Instant::now() + Duration::from_secs(6);
|
let deadline = Instant::now() + Duration::from_secs(6);
|
||||||
@@ -409,7 +411,7 @@ async fn make_virtual_primary(dc: &zbus::Proxy<'_>, mode: Mode, pre: &CurrentSta
|
|||||||
let Some(vmode) = vmode else {
|
let Some(vmode) = vmode else {
|
||||||
bail!("virtual monitor {vconn} has no usable mode yet");
|
bail!("virtual monitor {vconn} has no usable mode yet");
|
||||||
};
|
};
|
||||||
let config = build_primary_config(&state, &vconn, &vmode, mode.width as i32);
|
let config = build_primary_config(&vconn, &vmode);
|
||||||
let _: () = dc
|
let _: () = dc
|
||||||
.call(
|
.call(
|
||||||
"ApplyMonitorsConfig",
|
"ApplyMonitorsConfig",
|
||||||
@@ -431,46 +433,20 @@ async fn make_virtual_primary(dc: &zbus::Proxy<'_>, mode: Mode, pre: &CurrentSta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Virtual output = primary at the top-left; physical monitors kept enabled but secondary, laid
|
/// The virtual output as the SOLE, primary monitor — physical outputs are omitted, so Mutter
|
||||||
/// out adjacently to its right (so the local screen never blanks).
|
/// disables them for the session. This confines the cursor, windows, and keyboard focus to the
|
||||||
fn build_primary_config(
|
/// streamed surface; keeping the physical enabled as a *secondary* monitor instead lets relative
|
||||||
state: &CurrentState,
|
/// pointer motion and window focus wander onto it (invisible to the client — the cursor seems to
|
||||||
vconn: &str,
|
/// vanish). The physical layout is restored on teardown.
|
||||||
vmode: &str,
|
fn build_primary_config(vconn: &str, vmode: &str) -> Vec<ApplyLogical> {
|
||||||
virt_width: i32,
|
vec![(
|
||||||
) -> Vec<ApplyLogical> {
|
|
||||||
let mut logicals: Vec<ApplyLogical> = Vec::new();
|
|
||||||
logicals.push((
|
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
1.0,
|
1.0,
|
||||||
0,
|
0,
|
||||||
true,
|
true,
|
||||||
vec![(vconn.to_string(), vmode.to_string(), HashMap::new())],
|
vec![(vconn.to_string(), vmode.to_string(), HashMap::new())],
|
||||||
));
|
)]
|
||||||
let mut x = virt_width;
|
|
||||||
for lm in &state.2 {
|
|
||||||
if lm.5.iter().any(|s| s.0 == vconn) {
|
|
||||||
continue; // skip the virtual output's own logical monitor
|
|
||||||
}
|
|
||||||
let mons: Vec<ApplyMon> =
|
|
||||||
lm.5.iter()
|
|
||||||
.filter_map(|s| {
|
|
||||||
current_mode(state, &s.0).map(|(id, _, _)| (s.0.clone(), id, HashMap::new()))
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
if mons.is_empty() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let width =
|
|
||||||
lm.5.first()
|
|
||||||
.and_then(|s| current_mode(state, &s.0))
|
|
||||||
.map(|(_, w, _)| w)
|
|
||||||
.unwrap_or(1920);
|
|
||||||
logicals.push((x, 0, lm.2, lm.3, false, mons));
|
|
||||||
x += width;
|
|
||||||
}
|
|
||||||
logicals
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a captured `GetCurrentState` layout back into an `ApplyMonitorsConfig` argument (used
|
/// Convert a captured `GetCurrentState` layout back into an `ApplyMonitorsConfig` argument (used
|
||||||
|
|||||||
Reference in New Issue
Block a user