refactor(state): make output probe readiness transform-only
CI / Build + Clippy + Test (pull_request) Failing after 11s
CI / Security audit (RUSTSEC) (pull_request) Failing after 31s

Drops PartialOutputInfo.physical_size and .logical_position. After the
warning cleanup in 6332472 these fields were probe-time-only gates with
no downstream consumer (encoder reads geometry from the dmabuf frame
itself, not from wl_output description).

Readiness simplification (try_finalize_output):
- xdg-output path (Sway/Hyprland): done_count >= 2 + name + transform
- wlr-output-management path (niri): done_count >= 1 + wlr_manager_done + transform

Safe because Wayland protocol guarantees Geometry/Mode/Position events
fire before Done, so done_count >= N implies the prior events arrived.
done_count is the real signal; the per-field .is_none() checks were
redundant belt-and-suspenders.

Cascading cleanup of writers that only fed the deleted fields:
- WlOutput::Geometry handler — drop physical_size write, keep transform
- XdgOutputEvent::LogicalPosition arm — deleted
- WlrHeadEvent::Position arm — deleted (was the only reader of
  wlr_head_proxy_to_name; both maps now write-only markers)
- WlrHeadInfo.position field — deleted; struct becomes empty marker

Behavior change risk: probe may finalize slightly earlier in cases where
a compositor fires Done before physical_size/logical_position events
(protocol violation, but possible). Verified with cargo test --release
(79 unit + 3 integration, 1 ignored). Hardware/Wayland-session test
deferred to user.

Net: -48 lines.
This commit is contained in:
2026-07-09 14:48:10 +08:00
parent 633247201c
commit 9a7b745a0e
+9 -56
View File
@@ -85,20 +85,17 @@ pub struct PartialOutputInfo {
/// Name from wl_output::Name (v4) — used to match wlr-output-management heads
pub wl_name: Option<String>,
pub transform: Option<Transform>,
pub physical_size: Option<(i32, i32)>,
pub logical_position: Option<(i32, i32)>,
// Pixel dimensions from Mode event — preparatory for Phase 2 resolution logic
pub mode_size: Option<(i32, i32)>,
pub done_count: u32,
}
/// Stores head info from wlr-output-management for name-based matching with wl_output.
/// Marker for wlr-output-management heads seen during probing; tracked by name
/// in `EncConstructionStage::ProbingOutputs.wlr_heads`.
// `pub(crate)` (not module-private): exposed via `EncConstructionStage::ProbingOutputs.wlr_heads`
// which is reached from main.rs during the wlr-screencopy probing loop.
pub(crate) struct WlrHeadInfo {
position: Option<(i32, i32)>,
}
pub(crate) struct WlrHeadInfo {}
/// User data for XdgOutput dispatch to identify which WlOutput it belongs to.
pub struct OutputId(pub u32);
@@ -832,22 +829,6 @@ impl<S: CaptureSource> State<S> {
}
fn try_finalize_output(&mut self, _idx: usize) -> bool {
// Merge wlr head position info into outputs (needed for niri path)
if let EncConstructionStage::ProbingOutputs {
outputs, wlr_heads, ..
} = &mut self.stage
{
for info in outputs.iter_mut() {
if info.logical_position.is_none() {
if let Some(ref wl_name) = info.wl_name {
if let Some(head_info) = wlr_heads.get(wl_name) {
info.logical_position = head_info.position;
}
}
}
}
}
let (target_idx, output_count) = match &self.stage {
EncConstructionStage::ProbingOutputs {
outputs,
@@ -890,24 +871,19 @@ impl<S: CaptureSource> State<S> {
Some(i) => {
let info = &outputs[i];
if has_xdg {
// xdg-output path (Sway/Hyprland) — strict checks
// done_count >= 2 implies physical_size and logical_position
// already arrived (Wayland: Geometry/Mode/Position fire before Done).
if info.done_count < 2
|| info.name.is_none()
|| info.transform.is_none()
|| info.physical_size.is_none()
|| info.logical_position.is_none()
{
return false;
}
} else {
// wlr-output-management path (niri) — relaxed checks
if info.done_count < 1 || !wlr_manager_done {
// done_count >= 1 implies transform arrived (Geometry precedes Done).
if info.done_count < 1 || !wlr_manager_done || info.transform.is_none() {
return false;
}
if info.transform.is_none() || info.physical_size.is_none() {
return false;
}
// name and logical_position can use defaults
}
(i, output_count)
}
@@ -1180,8 +1156,6 @@ impl<S: CaptureSource> Dispatch<WlOutput, OutputId> for State<S> {
match event {
OutputEvent::Geometry {
transform,
physical_width,
physical_height,
..
} => {
let t = match transform {
@@ -1198,7 +1172,6 @@ impl<S: CaptureSource> Dispatch<WlOutput, OutputId> for State<S> {
if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage {
if let Some(info) = outputs.get_mut(idx) {
info.transform = Some(t);
info.physical_size = Some((physical_width, physical_height));
}
}
}
@@ -1272,13 +1245,6 @@ impl<S: CaptureSource> Dispatch<ZxdgOutputV1, OutputId> for State<S> {
}
}
}
XdgOutputEvent::LogicalPosition { x, y } => {
if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage {
if let Some(info) = outputs.get_mut(idx) {
info.logical_position = Some((x, y));
}
}
}
XdgOutputEvent::LogicalSize { .. } => {}
XdgOutputEvent::Done => {
if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage {
@@ -1563,24 +1529,11 @@ impl<S: CaptureSource> Dispatch<ZwlrOutputHeadV1, ()> for State<S> {
{
wlr_heads
.entry(name.clone())
.or_insert(WlrHeadInfo { position: None });
.or_insert(WlrHeadInfo {});
wlr_head_proxy_to_name.insert(proxy.id(), name);
}
}
WlrHeadEvent::Position { x, y } => {
if let EncConstructionStage::ProbingOutputs {
wlr_heads,
wlr_head_proxy_to_name,
..
} = &mut state.stage
{
if let Some(name) = wlr_head_proxy_to_name.get(&proxy.id()) {
if let Some(head) = wlr_heads.get_mut(name) {
head.position = Some((x, y));
}
}
}
}
WlrHeadEvent::Position { .. } => {}
WlrHeadEvent::Finished => {
tracing::debug!("zwlr_output_head_v1::Finished received");
}