diff options
| author | srdusr <[email protected]> | 2025-02-15 14:56:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2025-02-15 14:56:00 +0200 |
| commit | 0a4c4b5941fe982ccb3d3175e26d9d83f0025ffd (patch) | |
| tree | 7672d0af277664f457c6c9462925c0005fe35dcf /crates/wayland/src/winit | |
| parent | 413daa7ba2ea0ebd1424c024fd0566423aaea3f8 (diff) | |
| download | srdwm-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.rs | 17 | ||||
| -rw-r--r-- | crates/wayland/src/winit/connect.rs | 5 | ||||
| -rw-r--r-- | crates/wayland/src/winit/mod.rs | 3 | ||||
| -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.rs | 112 |
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())); } } |