srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/winit.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/wayland/src/winit.rs')
-rw-r--r--crates/wayland/src/winit.rs164
1 files changed, 96 insertions, 68 deletions
diff --git a/crates/wayland/src/winit.rs b/crates/wayland/src/winit.rs
index 11c2d1e..2c55dda 100644
--- a/crates/wayland/src/winit.rs
+++ b/crates/wayland/src/winit.rs
@@ -25,8 +25,8 @@ use smithay::backend::renderer::ImportDma;
use smithay::backend::winit::{self, WinitEvent, WinitEventLoop, WinitGraphicsBackend};
use smithay::reexports::winit::dpi::LogicalSize as WinitLogicalSize;
use smithay::reexports::winit::window::Window as WinitWindow;
-use smithay::desktop::space::render_output;
use smithay::desktop::{layer_map_for_output, PopupManager, Space};
+use smithay::wayland::shell::wlr_layer::Layer;
use smithay::input::SeatState;
use smithay::output::{Mode as OutputMode, Output, PhysicalProperties, Subpixel};
use smithay::reexports::calloop::EventLoop as CalloopEventLoop;
@@ -349,46 +349,54 @@ impl WaylandPlatform {
Err(e) => log::warn!("failed to import context menu buffer: {e}"),
}
}
- // Decoration/border built per window, front-to-back (topmost
- // first), so a window's own titlebar/border at least stay ordered
- // consistently relative to *other* windows' decoration/border.
+ // Content now renders here too, one window at a time, not through
+ // `render_output`'s own `spaces` argument - see this function's
+ // own call to `damage_tracker.render_output` further down for why,
+ // and `elements.rs`'s `surface_content_elements` doc comment for
+ // what it's actually for (per-window opacity, impossible through
+ // `spaces`, which takes one `alpha` for the whole frame).
//
- // Content deliberately still goes through `render_output`'s own
- // `spaces` argument below (`self.state.space`), not through this
- // loop: an earlier version of this fix pushed each window's own
- // `Window::render_elements` output into `custom_elements` here too,
- // in per-window stacking order, specifically to let decoration/
- // border interleave correctly with *other* windows' content
- // (custom_elements otherwise draws entirely above every window's
- // content, `spaces` or not - seeing `render_output`'s source is
- // what motivated that attempt). It was reverted: with two or more
- // native Wayland toplevels on screen, whichever was created
- // *first* always painted in front of later ones regardless of
- // real focus/stacking order, reproduced consistently across three
- // separate test windows, independent of push order, forced full
- // redraws (`age = 0`), and buffer age - i.e. a real ordering bug
- // in mixing multiple windows' own `render_elements` output this
- // way, not a damage-tracking artifact. The real root cause (found
- // later, by instrumenting a locally vendored smithay copy
- // directly) turned out to be unrelated to content-vs-decoration
- // mixing at all: `sync_geometry`'s `Space::map_element` call
- // silently re-stacked windows to the top of `Space`'s own order
- // any time position/size synced for *any* reason, independent of
- // which rendering path was used - see `state.rs`'s
+ // An earlier version of this exact change was reverted: with two or
+ // more native Wayland toplevels on screen, whichever was created
+ // *first* always painted in front of later ones regardless of real
+ // focus/stacking order. That bug's real root cause (found by
+ // instrumenting a locally vendored smithay copy directly) turned
+ // out to be `sync_geometry`'s `Space::map_element` call silently
+ // re-stacking windows to the top of `Space`'s *own* internal
+ // order as a side effect of updating position - see `state.rs`'s
// `resync_stacking_order` doc comment for the full story and the
- // actual fix. `self.state.space` stayed the content path here
- // since reverting it was never itself wrong, just insufficient on
- // its own.
+ // fix that landed for it (called after every `map_element` since).
+ // This loop never reads `Space`'s order at all: `ids` below comes
+ // from `WindowManager.order` (`visible_windows_front_to_back`),
+ // srdwm's own stacking model, the same source `hit_test` already
+ // trusts - so the specific bug that sank the earlier attempt
+ // can't recur here regardless of whether `resync_stacking_order`
+ // ever drifts again. `self.state.space` stays mapped and
+ // `resync_stacking_order`-maintained exactly as before; only the
+ // render step stopped reading from it.
let ids: Vec<WindowId> = self.wm.borrow().visible_windows_front_to_back().map(|w| w.id).collect();
let focused = self.wm.borrow().focused_id();
+ // Popups next: always above every window's own content - see the
+ // matching comment in `udev.rs`'s render loop for why this has to
+ // be pushed ahead of both the bar/dock and every window now that
+ // content shares this same list.
+ let popup_targets = crate::elements::popup_targets(&self.state);
+ custom_elements.extend(crate::elements::popup_render_elements(&popup_targets, renderer, (0, 0)));
+ // The bar/dock/launcher, skipped entirely for a fullscreen window --
+ // see `udev.rs`'s matching push for the full reasoning.
+ let hide_top_layers = self.wm.borrow().visible_windows_front_to_back().any(|w| w.fullscreen);
+ if !hide_top_layers {
+ custom_elements.extend(crate::elements::output_layer_elements(renderer, &self.output, (0, 0), |layer| matches!(layer, Layer::Top | Layer::Overlay)));
+ }
// Windows stacked in front of whichever one border/decoration is
// being built right now - `ids` is already front-to-back, so this
- // only ever needs appending to, not recomputing. Only a window's
- // own *content* occludes correctly on its own path (via `space`,
- // real stacking order respected); everything drawn here goes
- // through `custom_elements`, which composites above *all* content
- // unconditionally, so both the border strips and the titlebar
- // bitmap below need this explicit occlusion test against it.
+ // only ever needs appending to, not recomputing. A window's own
+ // *content*, pushed inside this same loop below, needs no separate
+ // occlusion test - see the matching comment in `udev.rs`'s render
+ // loop for why ordinary front-to-back push order already occludes
+ // it correctly. The border strips and titlebar bitmap are
+ // different: outside `geometry`, so they still need `occluders`'
+ // explicit clip against whichever window is stacked in front.
let mut occluders: Vec<srdwm_core::Rect> = Vec::with_capacity(ids.len());
for id in ids {
let Some(w) = self.wm.borrow().window(id).cloned() else { continue };
@@ -468,25 +476,32 @@ impl WaylandPlatform {
}
}
}
+ // The window's own content, at its own `opacity` - see the
+ // matching push in `udev.rs`'s render loop for why. Single
+ // output at the global origin, so no offset to subtract (see
+ // `elements.rs`'s doc comment on why `udev.rs`'s per-head call
+ // does).
+ if let Some(dwindow) = self.state.id_to_window.get(&id) {
+ if let Some(surface) = crate::elements::window_wl_surface(dwindow) {
+ let band = if w.decorated { srdwm_core::TITLEBAR_HEIGHT as i32 } else { 0 };
+ custom_elements.extend(crate::elements::surface_content_elements(renderer, &surface, (geom.x, geom.y + band), w.opacity));
+ }
+ }
occluders.push(geom);
}
- // Single output at the global origin - no offset to subtract, see
- // `elements.rs`'s doc comment on why udev.rs's per-head call does.
- let popup_targets = crate::elements::popup_targets(&self.state);
- custom_elements.extend(crate::elements::popup_render_elements(&popup_targets, renderer, (0, 0)));
-
- let result = render_output(
- &self.output,
- renderer,
- &mut framebuffer,
- 1.0,
- age,
- [&self.state.space],
- &custom_elements,
- &mut self.damage_tracker,
- [0.05, 0.05, 0.08, 1.0],
- )
- .map_err(err)?;
+ // Background/bottom layer-shell (wallpaper engines) last --
+ // bottommost, matching smithay's own `space_render_elements`
+ // ordering, which this whole custom loop now replaces.
+ custom_elements.extend(crate::elements::output_layer_elements(renderer, &self.output, (0, 0), |layer| matches!(layer, Layer::Background | Layer::Bottom)));
+
+ // Not `smithay::desktop::space::render_output`: see `udev.rs`'s
+ // matching call site for why (per-window opacity, fullscreen-aware
+ // layer-shell inclusion - `custom_elements` above already carries
+ // everything that wrapper would have built).
+ let result = self
+ .damage_tracker
+ .render_output(renderer, &mut framebuffer, age, &custom_elements, [0.05, 0.05, 0.08, 1.0])
+ .map_err(err)?;
let damage_rects: Vec<Rectangle<i32, Physical>> = result.damage.cloned().unwrap_or_default();
let has_damage = !damage_rects.is_empty();
drop(framebuffer);
@@ -588,29 +603,42 @@ impl WaylandPlatform {
renderer.create_buffer(Fourcc::Abgr8888, (size.w, size.h).into()).map_err(err)?;
let mut framebuffer = renderer.bind(&mut target).map_err(err)?;
+ // Not full parity with the on-screen render loop above (no border/
+ // shadow strips here, same as before this function's content/opacity
+ // fix) - a real, pre-existing gap in what a screenshot shows on
+ // this backend, flagged rather than grown further in this pass.
+ // Content (with each window's own `opacity`, unlike the
+ // `self.state.space`-based single-alpha call this replaced) and the
+ // bar/dock now render into the capture, at least: a screenshot used
+ // to only ever show titlebars for windows that had one, and never
+ // any layer-shell surface at all.
+ let hide_top_layers = self.wm.borrow().visible_windows_front_to_back().any(|w| w.fullscreen);
let mut custom_elements: Vec<crate::elements::OverlayElement<GlesRenderer>> = Vec::new();
- for (&id, deco) in self.state.decorations.iter() {
- let Some(geom) = self.wm.borrow().visible_windows().find(|w| w.id == id).map(|w| w.geometry) else { continue };
- if let Ok(elem) = MemoryRenderBufferRenderElement::from_buffer(renderer, (geom.x as f64, geom.y as f64), deco, None, None, None, Kind::Unspecified) {
- custom_elements.push(crate::elements::OverlayElement::Memory(elem));
+ if !hide_top_layers {
+ custom_elements.extend(crate::elements::output_layer_elements(renderer, &self.output, (0, 0), |layer| matches!(layer, Layer::Top | Layer::Overlay)));
+ }
+ for id in self.wm.borrow().visible_windows_front_to_back().map(|w| w.id).collect::<Vec<_>>() {
+ let Some(w) = self.wm.borrow().window(id).cloned() else { continue };
+ if let Some(deco) = self.state.decorations.get(&id) {
+ if let Ok(elem) = MemoryRenderBufferRenderElement::from_buffer(renderer, (w.geometry.x as f64, w.geometry.y as f64), deco, None, None, None, Kind::Unspecified) {
+ custom_elements.push(crate::elements::OverlayElement::Memory(elem));
+ }
+ }
+ if let Some(dwindow) = self.state.id_to_window.get(&id) {
+ if let Some(surface) = crate::elements::window_wl_surface(dwindow) {
+ let band = if w.decorated { srdwm_core::TITLEBAR_HEIGHT as i32 } else { 0 };
+ custom_elements.extend(crate::elements::surface_content_elements(renderer, &surface, (w.geometry.x, w.geometry.y + band), w.opacity));
+ }
}
}
+ custom_elements.extend(crate::elements::output_layer_elements(renderer, &self.output, (0, 0), |layer| matches!(layer, Layer::Background | Layer::Bottom)));
// A throwaway damage tracker, so this pass always draws the whole
// scene (age 0) and never perturbs the on-screen tracker's history.
let mut tracker = OutputDamageTracker::from_output(&self.output);
- render_output(
- &self.output,
- renderer,
- &mut framebuffer,
- 1.0,
- 0,
- [&self.state.space],
- &custom_elements,
- &mut tracker,
- [0.05, 0.05, 0.08, 1.0],
- )
- .map_err(err)?;
+ tracker
+ .render_output(renderer, &mut framebuffer, 0, &custom_elements, [0.05, 0.05, 0.08, 1.0])
+ .map_err(err)?;
screencopy::service_pending(captures, renderer, &framebuffer);
Ok(())