srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/winit
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2025-02-15 14:56:00 +0200
committersrdusr <[email protected]>2025-02-15 14:56:00 +0200
commit0a4c4b5941fe982ccb3d3175e26d9d83f0025ffd (patch)
tree7672d0af277664f457c6c9462925c0005fe35dcf /crates/wayland/src/winit
parent413daa7ba2ea0ebd1424c024fd0566423aaea3f8 (diff)
downloadsrdwm-0a4c4b5941fe982ccb3d3175e26d9d83f0025ffd.tar.gz
srdwm-0a4c4b5941fe982ccb3d3175e26d9d83f0025ffd.zip
Checkpoint: preserve all uncommitted rust-rewrite worktree work
Safety commit before reconciling this worktree with main, which has diverged with its own separate fixes today. Nothing here is reviewed or curated yet - this exists purely so none of this work can be lost to a git operation, disk issue, or worktree cleanup while that reconciliation happens.
Diffstat (limited to 'crates/wayland/src/winit')
-rw-r--r--crates/wayland/src/winit/capture.rs17
-rw-r--r--crates/wayland/src/winit/connect.rs5
-rw-r--r--crates/wayland/src/winit/mod.rs3
-rw-r--r--crates/wayland/src/winit/nested_platform.rs (renamed from crates/wayland/src/winit/platform.rs)8
-rw-r--r--crates/wayland/src/winit/render.rs112
5 files changed, 119 insertions, 26 deletions
diff --git a/crates/wayland/src/winit/capture.rs b/crates/wayland/src/winit/capture.rs
index a96cff9..3b1d38e 100644
--- a/crates/wayland/src/winit/capture.rs
+++ b/crates/wayland/src/winit/capture.rs
@@ -29,7 +29,7 @@ impl WaylandPlatform {
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();
if !hide_top_layers {
- custom_elements.extend(crate::elements::output_layer_elements(renderer, &self.output, (0, 0), |layer| matches!(layer, Layer::Top | Layer::Overlay)));
+ custom_elements.extend(crate::elements::output_layer_elements(renderer, &self.output, |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 };
@@ -41,11 +41,22 @@ impl WaylandPlatform {
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));
+ // `content_offset`: same `xdg_surface.set_window_geometry`
+ // subtraction every other render/capture path in this
+ // codebase already does (`udev/render.rs`, `winit/
+ // render.rs`, `udev/capture.rs`) - missed here
+ // specifically. A CSD client's invisible shadow margin
+ // landed at `w.geometry.x, w.geometry.y + band` instead
+ // of its real visible content, so a screenshot taken on
+ // this backend showed the same content_offset-sized gap
+ // the on-screen render loops already had fixed.
+ let content_offset = dwindow.geometry().loc;
+ let pos = (w.geometry.x - content_offset.x, w.geometry.y + band - content_offset.y);
+ custom_elements.extend(crate::elements::surface_content_elements(renderer, &surface, pos, w.opacity));
}
}
}
- custom_elements.extend(crate::elements::output_layer_elements(renderer, &self.output, (0, 0), |layer| matches!(layer, Layer::Background | Layer::Bottom)));
+ custom_elements.extend(crate::elements::output_layer_elements(renderer, &self.output, |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.
diff --git a/crates/wayland/src/winit/connect.rs b/crates/wayland/src/winit/connect.rs
index d18a489..c748ef4 100644
--- a/crates/wayland/src/winit/connect.rs
+++ b/crates/wayland/src/winit/connect.rs
@@ -113,6 +113,7 @@ impl WaylandPlatform {
_screencopy_state: screencopy::ScreencopyState::new::<CompState>(&dh),
screencopy_pending: Vec::new(),
_appmenu_state: crate::appmenu::AppmenuManagerState::new::<CompState>(&dh),
+ _virtual_keyboard_state: smithay::wayland::virtual_keyboard::VirtualKeyboardManagerState::new::<CompState, _>(&dh, |_client| true),
_foreign_toplevel_state: crate::foreign_toplevel::ForeignToplevelState::new::<CompState>(&dh),
foreign_toplevel_managers: Vec::new(),
foreign_toplevel_handles: HashMap::new(),
@@ -142,6 +143,7 @@ impl WaylandPlatform {
last_broadcast_workspace: None,
lock: SessionLock::default(),
cursor_status: smithay::input::pointer::CursorImageStatus::default_named(),
+ decoration_cursor_active: false,
cursor_buffers: crate::cursor::make_buffers(),
last_titlebar_click: None,
gesture_swipe: None,
@@ -159,12 +161,15 @@ impl WaylandPlatform {
border_top_decorations: HashMap::new(),
border_bottom_decorations: HashMap::new(),
decoration_signatures: HashMap::new(),
+ hovered_titlebar_button: None,
shadow_buffers: HashMap::new(),
rounded_corners_program,
content_epoch: HashMap::new(),
rounded_content_buffers: HashMap::new(),
border_side_buffers: HashMap::new(),
+ color_filter_buffers: HashMap::new(),
last_synced_size: HashMap::new(),
+ pending_size_configure: HashMap::new(),
pending: pending.clone(),
bound_keys: Rc::new(bound_keys.iter().cloned().collect()),
repeat_keys: Rc::new(repeat_keys.iter().cloned().collect()),
diff --git a/crates/wayland/src/winit/mod.rs b/crates/wayland/src/winit/mod.rs
index 707055d..6a7bac0 100644
--- a/crates/wayland/src/winit/mod.rs
+++ b/crates/wayland/src/winit/mod.rs
@@ -19,6 +19,7 @@ use smithay::backend::input::{
};
use smithay::backend::renderer::damage::OutputDamageTracker;
use smithay::backend::renderer::element::memory::MemoryRenderBufferRenderElement;
+use smithay::backend::renderer::element::solid::SolidColorBuffer;
use smithay::backend::renderer::element::Kind;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::backend::renderer::ImportDma;
@@ -94,6 +95,6 @@ const TARGET_FRAME_TIME: Duration = Duration::from_micros(1_000_000 / 60);
mod capture;
mod connect;
mod events;
-mod platform;
+mod nested_platform;
mod render;
mod run;
diff --git a/crates/wayland/src/winit/platform.rs b/crates/wayland/src/winit/nested_platform.rs
index 96adde8..330ef91 100644
--- a/crates/wayland/src/winit/platform.rs
+++ b/crates/wayland/src/winit/nested_platform.rs
@@ -48,9 +48,15 @@ impl Platform for WaylandPlatform {
// `WindowManager`, never `state.space`, so an IPC focus
// change left rendering/hit-testing on the stale topmost
// window until something else happened to raise it.
+ //
+ // `raise_in_space`, not `focus_window` - see that
+ // function's doc comment: the full version re-runs the
+ // workspace-follow side effect on the already-focused
+ // window and silently reverts an `activate_workspace` IPC
+ // dispatch from the same cycle.
let focused = self.wm.borrow().focused_id();
if let Some(id) = focused {
- crate::input::focus_window(&mut self.state, id);
+ crate::input::raise_in_space(&mut self.state, id);
}
}
}
diff --git a/crates/wayland/src/winit/render.rs b/crates/wayland/src/winit/render.rs
index ec4fd69..60cccb4 100644
--- a/crates/wayland/src/winit/render.rs
+++ b/crates/wayland/src/winit/render.rs
@@ -4,6 +4,7 @@ impl WaylandPlatform {
pub(super) fn render_frame(&mut self) -> PlatformResult<()> {
self.state.tick_animations();
+ self.state.tick_hover_glyph_animation();
self.state.tick_dirty_broadcasts();
let size = self.backend.window_size();
let resized = self.output.current_mode().map(|m| m.size) != Some(size);
@@ -61,6 +62,20 @@ impl WaylandPlatform {
// rounded-content push (further down) uses `WinitElement::Rounded`
// directly.
let mut custom_elements: Vec<crate::rounded_corners::WinitElement> = Vec::new();
+ // Night light/reading mode - pushed first (topmost) so it colours
+ // everything else, including the context menu below: this backend
+ // draws no cursor of its own to exempt (unlike udev/render.rs's
+ // matching push), so there's nothing that needs to stay above it.
+ // See `color_filter::render_element` for why this is a translucent
+ // overlay rather than a true per-pixel shader.
+ {
+ let color_filter = self.wm.borrow().color_filter;
+ let output_name = self.output.name();
+ let buf = self.state.color_filter_buffers.entry(output_name).or_insert_with(SolidColorBuffer::default);
+ if let Some(elem) = crate::color_filter::render_element(buf, color_filter, (size.w, size.h)) {
+ custom_elements.push(crate::rounded_corners::WinitElement::Base(crate::elements::OverlayElement::Solid(elem)));
+ }
+ }
// The right-click titlebar menu, if open - pushed first so it's
// topmost over every window (this backend draws no cursor of its
// own, see this module's doc comment, so there's no "stay under
@@ -124,7 +139,7 @@ impl WaylandPlatform {
let rounded_corners_enabled = self.wm.borrow().rounded_corners_enabled.unwrap_or(true);
if !hide_top_layers {
custom_elements.extend(
- crate::elements::output_layer_elements(renderer, &self.output, (0, 0), |layer| matches!(layer, Layer::Top | Layer::Overlay))
+ crate::elements::output_layer_elements(renderer, &self.output, |layer| matches!(layer, Layer::Top | Layer::Overlay))
.into_iter()
.map(crate::rounded_corners::WinitElement::Base),
);
@@ -147,24 +162,18 @@ impl WaylandPlatform {
// (reported live as the border "not flush" with the window
// during an animated maximize/fullscreen/open-slide transition).
let geom = self.state.window_anims.get(&id).map(crate::state::WindowAnim::current_rect).unwrap_or(w.geometry);
- // Same reasoning as udev/render.rs's matching push: positioned from
- // `geom`, not `w.geometry`, and not fragment-clipped against
- // `occluders` - see that comment.
- if let Some(shadow) = self.state.shadow_buffers.get(&id) {
- let rect = decoration::shadow_rect(geom);
- let pos = (rect.x as f64, rect.y as f64);
- match MemoryRenderBufferRenderElement::from_buffer(renderer, pos, shadow, None, None, None, Kind::Unspecified) {
- Ok(elem) => custom_elements.push(crate::rounded_corners::WinitElement::Base(crate::elements::OverlayElement::Memory(elem))),
- Err(e) => log::warn!("failed to import shadow buffer for window {id}: {e}"),
- }
- }
+ // `geom` is this compositor's own request/target; `frame`
+ // corrects its far edge to match what the client's surface
+ // really committed - see `effective_frame`'s own doc comment
+ // and the matching comment in `udev/render.rs`'s render loop.
+ let frame = self.state.effective_frame(id, geom);
if let Some(deco) = self.state.decorations.get(&id) {
// Fragment-clipped, same as udev/render.rs's matching titlebar
// push - see that comment for why all-or-nothing (skip
// only once *fully* covered) wasn't enough: a titlebar
// only partially covered, the common case for cascaded
// windows, still bled through the covered part.
- let titlebar_rect = srdwm_core::Rect::new(geom.x, geom.y, geom.width, srdwm_core::TITLEBAR_HEIGHT);
+ let titlebar_rect = srdwm_core::Rect::new(frame.x, frame.y, frame.width, srdwm_core::TITLEBAR_HEIGHT);
for fragment in crate::elements::visible_border_fragments(titlebar_rect, &occluders) {
let pos = (fragment.x as f64, fragment.y as f64);
let src = Rectangle::new(
@@ -181,9 +190,14 @@ impl WaylandPlatform {
// `decoration::border_strips`), so they never overlap this same
// window's own decoration/content pixels - draw order relative
// to those doesn't matter, only relative to other windows'.
+ // (The left/right strips *do* still need cropping against the
+ // top/bottom strip's own extended curve - see that crop's own
+ // doc comment further down; that's an overlap between two
+ // pieces of this window's own decoration, not with its content,
+ // so it doesn't contradict this paragraph.)
if w.border_width > 0 {
- let color = crate::state::effective_border_color(w.border_color, focused == Some(id));
- let strips = decoration::border_strips(geom, w.border_width);
+ let color = crate::state::effective_border_color(w.border_color, focused == Some(id), self.wm.borrow().theme.border_inactive_dim);
+ let strips = decoration::border_strips(frame, w.border_width);
// Strips 0/1 (top/bottom) are rounded on their own two
// corners - see `render_border_top`/`render_border_bottom`'s
// doc comments - so both are cached bitmaps (rebuilt only in
@@ -199,7 +213,15 @@ impl WaylandPlatform {
// all-or-nothing occlusion check.
if strips[0].width > 0 && strips[0].height > 0 && !strips[0].subtract_all(&occluders).is_empty() {
if let Some(buffer) = self.state.border_top_decorations.get(&id) {
- match MemoryRenderBufferRenderElement::from_buffer(renderer, (strips[0].x as f64, strips[0].y as f64), buffer, None, None, None, Kind::Unspecified) {
+ // See `decoration::border_top_visible_rows`'s own
+ // doc comment: an undecorated window has no
+ // titlebar band to safely absorb this buffer's own
+ // corner-curve-only extra rows, so they're cropped
+ // away instead of landing on real content.
+ let (row0, rows, shift) = decoration::border_top_visible_rows(w.decorated, w.border_width, w.corner_radius);
+ let pos = (strips[0].x as f64, (strips[0].y + shift as i32) as f64);
+ let src = Some(Rectangle::new(Point::from((0.0, row0 as f64)), Size::from((strips[0].width as f64, rows as f64))));
+ match MemoryRenderBufferRenderElement::from_buffer(renderer, pos, buffer, None, src, None, Kind::Unspecified) {
Ok(elem) => custom_elements.push(crate::rounded_corners::WinitElement::Base(crate::elements::OverlayElement::Memory(elem))),
Err(e) => log::warn!("failed to import top border buffer for window {id}: {e}"),
}
@@ -210,15 +232,39 @@ impl WaylandPlatform {
// render_border_bottom`'s doc comment.
if strips[1].width > 0 && strips[1].height > 0 && !strips[1].subtract_all(&occluders).is_empty() {
if let Some(buffer) = self.state.border_bottom_decorations.get(&id) {
- match MemoryRenderBufferRenderElement::from_buffer(renderer, (strips[1].x as f64, strips[1].y as f64), buffer, None, None, None, Kind::Unspecified) {
+ // See `decoration::border_bottom_visible_rows`'s
+ // own doc comment.
+ let (row0, rows, shift) = decoration::border_bottom_visible_rows(w.decorated, w.border_width, w.corner_radius);
+ let pos = (strips[1].x as f64, (strips[1].y - shift as i32) as f64);
+ let src = Some(Rectangle::new(Point::from((0.0, row0 as f64)), Size::from((strips[1].width as f64, rows as f64))));
+ match MemoryRenderBufferRenderElement::from_buffer(renderer, pos, buffer, None, src, None, Kind::Unspecified) {
Ok(elem) => custom_elements.push(crate::rounded_corners::WinitElement::Base(crate::elements::OverlayElement::Memory(elem))),
Err(e) => log::warn!("failed to import bottom border buffer for window {id}: {e}"),
}
}
}
+ // Cropped top and bottom by `extra` - see the matching fix
+ // (and its own doc comment) in `udev/render.rs`'s identical
+ // side-strip loop: the top/bottom strip's own curve extends
+ // `corner_radius - border_width` rows into what would
+ // otherwise be these flat, curve-unaware side strips' own
+ // nominal top/bottom rows, and without this crop their
+ // solid fill bled through the curve's own transparent
+ // cutout as a straight vertical line poking out of an
+ // otherwise correctly-rounded corner - reported live,
+ // confirmed via raw pixel sampling on the udev backend;
+ // this backend shares the identical strip geometry and was
+ // never actually confirmed clean, just never specifically
+ // screenshotted the same way.
+ let extra = if w.decorated { w.border_width.max(w.corner_radius).saturating_sub(w.border_width) } else { 0 };
+ let mut side_strips = [strips[2], strips[3]];
+ for s in &mut side_strips {
+ s.y += extra as i32;
+ s.height = s.height.saturating_sub(2 * extra);
+ }
let pool = self.state.border_side_buffers.entry(id).or_default();
let mut buf_index = 0;
- for strip in &strips[2..] {
+ for strip in &side_strips {
if strip.width == 0 || strip.height == 0 {
continue;
}
@@ -229,6 +275,30 @@ impl WaylandPlatform {
}
}
}
+ // Shadow - pushed *after* the titlebar/border above, not
+ // before. See the matching fix (and its full explanation) in
+ // `udev/render.rs`'s render loop: `custom_elements` treats
+ // earlier-pushed as topmost, so a shadow pushed before this
+ // window's own border rendered on top of it, alpha-blending
+ // black over the configured border colour and muting it into a
+ // hazy smear instead of a crisp line - reported live as
+ // "spacing before the border". Positioned from `geom`, not
+ // `w.geometry`, same reasoning as the border above (a stale-
+ // position shadow during an animated tween looks as detached
+ // as the border did before that fix). Not fragment-clipped
+ // against `occluders` like the titlebar/border above: at
+ // `SHADOW_MAX_ALPHA`'s low opacity, a shadow bleeding slightly
+ // onto a window stacked in front of this one reads as a soft
+ // edge, not the hard-line bleed-through that made the
+ // titlebar/border need it.
+ if let Some(shadow) = self.state.shadow_buffers.get(&id) {
+ let rect = decoration::shadow_rect(frame);
+ let pos = (rect.x as f64, rect.y as f64);
+ match MemoryRenderBufferRenderElement::from_buffer(renderer, pos, shadow, None, None, None, Kind::Unspecified) {
+ Ok(elem) => custom_elements.push(crate::rounded_corners::WinitElement::Base(crate::elements::OverlayElement::Memory(elem))),
+ Err(e) => log::warn!("failed to import shadow buffer for window {id}: {e}"),
+ }
+ }
// The window's own content, at its own `opacity` - see the
// matching push in `udev/render.rs`'s render loop for why. Single
// output at the global origin, so no offset to subtract (see
@@ -266,13 +336,13 @@ impl WaylandPlatform {
}
}
}
- occluders.push(geom);
+ occluders.push(frame);
}
// 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))
+ crate::elements::output_layer_elements(renderer, &self.output, |layer| matches!(layer, Layer::Background | Layer::Bottom))
.into_iter()
.map(crate::rounded_corners::WinitElement::Base),
);
@@ -325,7 +395,7 @@ impl WaylandPlatform {
self.backend.submit(None).map_err(err)?;
let scale = Scale::from(self.output.current_scale().fractional_scale());
let now = self.state.start_time.elapsed();
- for w in crate::elements::windows_touched_by_damage(&self.state.space, &damage_rects, scale) {
+ for w in crate::elements::windows_touched_by_damage(&self.state.space, &damage_rects, (0, 0).into(), scale) {
w.send_frame(&self.output, now, None, |_, _| Some(self.output.clone()));
}
}