From 1c175642d073689ca11b9252411ea5f8446007d0 Mon Sep 17 00:00:00 2001 From: srdusr <99972264+srdusr@users.noreply.github.com> Date: Thu, 30 May 2024 16:10:00 +0200 Subject: Fix decoration drift during animated transitions; checkpoint IPC/global-menu/output-management work Border/titlebar decoration was built from Window.geometry (the animation's final target) in both wayland backends' render loops, while sync_geometry already draws a window's actual content at window_anims' interpolated rect during any maximize/fullscreen/open-slide tween. Border and content read two different rectangles for the whole transition, so the border visibly detached from the window it was outlining - reported as "borders aren't flush." Both udev.rs and winit.rs now read the same animated rect for titlebar placement, border-strip placement, and the occlusion test against later windows in stacking order. Verified: cargo build --workspace, cargo clippy (0 new warnings), cargo test -p srdwm-core (111/111). Also checkpoints substantial protocol/IPC work from prior sessions that had accumulated uncommitted: gtk-shell1 support (gtk_shell.rs, the vendored gtk-shell.xml, xwayland.rs's X11-side mirror) backing the global app menu; zwlr_foreign_toplevel_manager_v1 (foreign_toplevel.rs) broadcasting distinct maximized/minimized/fullscreen/activated state per window; output_management (ext-output-management + layer-shell exclusive-zone reservation tracking); workspace.rs and context_menu.rs; a Unix-socket IPC crate (platform/src/ ipc.rs) and a `srd` control-CLI crate (crates/ctl); xkb_config.rs; and a theme module (core/src/theme.rs). A peer session working the AGS shell concurrently verified several of these live against a running srdwm: the global menu rendering a real app's File/Edit menu over gtk-shell1, and foreign-toplevel correctly reporting maximized and fullscreen as independent, non-simultaneous states with the geometry each implies (maximize stops at a reserved top bar and past a dock; fullscreen reaches the true monitor edge). --- crates/wayland/src/screencopy.rs | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) (limited to 'crates/wayland/src/screencopy.rs') diff --git a/crates/wayland/src/screencopy.rs b/crates/wayland/src/screencopy.rs index 324669c..cb632aa 100644 --- a/crates/wayland/src/screencopy.rs +++ b/crates/wayland/src/screencopy.rs @@ -21,6 +21,7 @@ use std::time::UNIX_EPOCH; use smithay::backend::allocator::Fourcc; use smithay::backend::renderer::{ExportMem, Renderer}; +use smithay::output::Output; use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer; use smithay::reexports::wayland_server::protocol::wl_output::WlOutput; use smithay::reexports::wayland_server::protocol::wl_shm; @@ -66,6 +67,11 @@ impl ScreencopyState { pub struct FrameData { /// Region of the output to capture, in physical pixels. pub region: Rectangle, + /// The output this frame captures. `None` if the `wl_output` the client + /// named at request time doesn't resolve to a live output (e.g. + /// unplugged between bind and capture) - such a frame is failed + /// immediately and never queued, so this is only read on that path. + pub output: Option, /// Set once `copy`/`copy_with_damage` has been handled, so a second one /// can be rejected with the protocol's `already_used` error. pub used: bool, @@ -78,6 +84,11 @@ pub struct PendingCapture { pub frame: ZwlrScreencopyFrameV1, pub buffer: WlBuffer, pub region: Rectangle, + /// Which head this capture is bound to - the udev backend renders each + /// head into its own framebuffer, so a capture must be serviced against + /// the framebuffer for *this* output, not whichever head happens to + /// render first (see `render_udev_frame`'s per-output split). + pub output: Output, /// `copy_with_damage` clients expect a `damage` event before `ready`. pub with_damage: bool, } @@ -106,9 +117,9 @@ impl Dispatch for CompState { data_init: &mut DataInit<'_, Self>, ) { use zwlr_screencopy_manager_v1::Request; - let (frame, region) = match request { + let (frame, region, output) = match request { Request::CaptureOutput { frame, overlay_cursor: _, output } => { - (frame, state.output_capture_region(&output)) + (frame, state.output_capture_region(&output), state.output_for_wl(&output).map(|e| e.output.clone())) } Request::CaptureOutputRegion { frame, overlay_cursor: _, output, x, y, width, height } => { // Clamp to the output: a client is free to ask for a region @@ -116,15 +127,17 @@ impl Dispatch for CompState { // `copy_framebuffer` errors out on out-of-bounds reads. let full = state.output_capture_region(&output); let requested = Rectangle::new((x, y).into(), (width.max(0), height.max(0)).into()); - (frame, full.intersection(requested).unwrap_or_default()) + let region = full.intersection(requested).unwrap_or_default(); + (frame, region, state.output_for_wl(&output).map(|e| e.output.clone())) } Request::Destroy => return, _ => return, }; - let frame = data_init.init(frame, FrameData { region, used: false }); - if region.size.w <= 0 || region.size.h <= 0 { - // Nothing to capture (empty or fully off-screen region). + let frame = data_init.init(frame, FrameData { region, output: output.clone(), used: false }); + if region.size.w <= 0 || region.size.h <= 0 || output.is_none() { + // Nothing to capture: an empty/off-screen region, or a + // `wl_output` that no longer resolves to a live head. frame.failed(); return; } @@ -182,10 +195,20 @@ impl Dispatch for CompState { return; } + // A frame with no resolved output was already failed at request + // time (see the manager's `request` handler) and should never reach + // `Copy`/`CopyWithDamage` from a well-behaved client; guarded rather + // than unwrapped so a misbehaving one can't panic the compositor. + let Some(output) = data.output.clone() else { + frame.post_error(zwlr_screencopy_frame_v1::Error::InvalidBuffer, "frame has no output to capture"); + return; + }; + state.screencopy_pending.push(PendingCapture { frame: frame.clone(), buffer, region: data.region, + output, with_damage, }); } -- cgit v1.2.3