srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/winit/nested_platform.rs
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/nested_platform.rs
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/nested_platform.rs')
-rw-r--r--crates/wayland/src/winit/nested_platform.rs192
1 files changed, 192 insertions, 0 deletions
diff --git a/crates/wayland/src/winit/nested_platform.rs b/crates/wayland/src/winit/nested_platform.rs
new file mode 100644
index 0000000..330ef91
--- /dev/null
+++ b/crates/wayland/src/winit/nested_platform.rs
@@ -0,0 +1,192 @@
+use super::*;
+
+impl Platform for WaylandPlatform {
+ fn kind(&self) -> PlatformKind {
+ PlatformKind::Wayland
+ }
+
+ /// **Self-paced, deliberately**: nothing else in this backend ever
+ /// blocks. `pump_winit`'s underlying `dispatch_new_events` polls
+ /// (returns immediately either way), and smithay's winit backend
+ /// hardcodes `vsync: false` on the EGL surface it creates
+ /// (`init_from_attributes_with_gl_attr` in smithay 0.7.0's own
+ /// `backend/winit/mod.rs` - true of *every* entry point into that
+ /// module, including the one this backend used before it needed custom
+ /// `WindowAttributes`, so this was never introduced by that switch).
+ /// `swap_buffers` therefore returns as soon as the GPU accepts the
+ /// frame, with no wait for the next display refresh at all. Before this
+ /// fix, that meant `poll_events` -> `render_frame` -> full render +
+ /// `swap_buffers` ran back-to-back with nothing pacing the `while
+ /// running.get()` loop in `main.rs` between iterations - confirmed
+ /// live: an idle nested instance, zero windows, sat at a sustained
+ /// ~52% of one core (`ps -o %cpu`), because it was rendering and
+ /// presenting a full frame as fast as the CPU/GPU could physically
+ /// cycle, forever, whether or not anything on screen had changed.
+ /// Fixed by giving `idle_event_loop.dispatch` (already called every
+ /// tick to service `ext_idle_notify_v1`'s timers, see its field doc
+ /// comment) a real timeout instead of always `Duration::ZERO`: the
+ /// remaining budget until `TARGET_FRAME_TIME` has elapsed since the
+ /// last frame, clamped to zero once that budget is already spent. This
+ /// reuses the one blocking wait this backend already has rather than
+ /// adding a second, separate `thread::sleep`, and still services any
+ /// idle-notify timer that comes due sooner than a full frame away.
+ fn poll_events(&mut self) -> PlatformResult<Vec<CoreEvent>> {
+ self.accept_clients()?;
+ let closed = self.pump_winit()?;
+ if closed {
+ return Err(PlatformError::Other("compositor window closed".into()));
+ }
+ // Held bindings that repeat - see `CompState::tick_repeat`.
+ self.state.tick_repeat();
+ self.display.dispatch_clients(&mut self.state).map_err(err)?;
+ self.display.flush_clients().map_err(err)?;
+ if let Some(ipc) = self.ipc.as_mut() {
+ if ipc.poll(&self.wm) {
+ self.pending.borrow_mut().push(CoreEvent::WorkspaceChanged);
+ // Same re-sync as `udev/platform.rs`'s matching block - see
+ // its own comment. `handle_request` only ever touches core's
+ // `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::raise_in_space(&mut self.state, id);
+ }
+ }
+ }
+ // Same lock-request draining as `udev/platform.rs`'s matching
+ // block - see its own comment. Exercised here too (not just on
+ // the real udev backend) specifically so a native lock can be
+ // tested against this nested dev session without ever touching
+ // the live tty1 one.
+ if self.wm.borrow_mut().drain_lock_request() {
+ self.state.begin_native_lock();
+ }
+ self.state.poll_native_lock_auth();
+ let wait = TARGET_FRAME_TIME.saturating_sub(self.last_frame.elapsed());
+ let _ = self.idle_event_loop.dispatch(Some(wait), &mut self.state);
+ self.last_frame = Instant::now();
+ self.render_frame()?;
+ Ok(self.pending.borrow_mut().drain(..).collect())
+ }
+
+ fn monitors(&mut self) -> PlatformResult<Vec<srdwm_core::Monitor>> {
+ // Shrunk by any layer-shell exclusive zone - see the matching
+ // comment in `udev/platform.rs`'s `monitors()`. This backend is always a
+ // single output at the global origin, so the output-local zone
+ // rectangle already is the usable global-space rect.
+ let zone = layer_map_for_output(&self.output).non_exclusive_zone();
+ Ok(vec![{
+ let rect = srdwm_core::Rect::new(zone.loc.x, zone.loc.y, zone.size.w as u32, zone.size.h as u32);
+ let mut m = srdwm_core::Monitor::new(0, "winit", rect);
+ // Same fix as `udev/platform.rs`'s matching function: `Monitor::new`
+ // defaults `full_geometry` to `geometry`, which is already
+ // zone-shrunk here - without this, `toggle_fullscreen` had no
+ // way to actually cover a bar/dock's reserved strip, since the
+ // "true full rect" it targets was silently identical to the
+ // "usable, shrunk rect" `toggle_maximize` targets.
+ let full = self.backend.window_size();
+ m.full_geometry = srdwm_core::Rect::new(0, 0, full.w as u32, full.h as u32);
+ m.maximize_geometry = crate::input::maximize_geometry_for(&self.output, m.full_geometry);
+ m.primary = true;
+ m
+ }])
+ }
+
+ fn apply_geometry(&mut self, window: WindowId, geometry: srdwm_core::Rect) -> PlatformResult<()> {
+ let _ = geometry;
+ self.state.sync_geometry(window);
+ Ok(())
+ }
+
+ fn set_title(&mut self, _window: WindowId, _title: &str) -> PlatformResult<()> {
+ Ok(())
+ }
+
+ /// See `udev/platform.rs`'s matching impl for why this has to go through
+ /// `crate::input::focus_window` (the same path a real mouse click
+ /// already uses) rather than only touching core state.
+ fn focus(&mut self, window: WindowId) -> PlatformResult<()> {
+ crate::input::focus_window(&mut self.state, window);
+ Ok(())
+ }
+
+ fn minimize(&mut self, window: WindowId) -> PlatformResult<()> {
+ if let Some(w) = self.state.id_to_window.get(&window) {
+ self.state.space.unmap_elem(w);
+ }
+ Ok(())
+ }
+
+ fn restore(&mut self, window: WindowId) -> PlatformResult<()> {
+ self.state.sync_geometry(window);
+ Ok(())
+ }
+
+ fn close(&mut self, window: WindowId) -> PlatformResult<()> {
+ let Some(w) = self.state.id_to_window.get(&window) else { return Ok(()) };
+ if let Some(toplevel) = w.toplevel() {
+ toplevel.send_close();
+ } else if let Some(x11) = w.x11_surface() {
+ // Same fix as `udev/platform.rs`'s matching function: `w.toplevel()`
+ // is `None` for an XWayland window, so closing one silently did
+ // nothing at all without this arm.
+ let _ = x11.close();
+ }
+ Ok(())
+ }
+
+ fn set_decorated(&mut self, _window: WindowId, _decorated: bool) -> PlatformResult<()> {
+ Ok(())
+ }
+
+ fn set_border_color(&mut self, _window: WindowId, _rgb: (u8, u8, u8)) -> PlatformResult<()> {
+ Ok(())
+ }
+
+ fn set_border_width(&mut self, _window: WindowId, _width: u32) -> PlatformResult<()> {
+ Ok(())
+ }
+
+ fn redraw_decoration(&mut self, window: WindowId, _win: &CoreWindow, _focused: bool) -> PlatformResult<()> {
+ // Re-renders the title/focus-color band and re-syncs geometry;
+ // `sync_geometry` re-renders the decoration too, but only if one
+ // already exists, so this also covers first paint.
+ self.state.redraw_decoration_buffer(window);
+ self.state.sync_geometry(window);
+ Ok(())
+ }
+
+ fn grab_keyboard(&mut self) -> PlatformResult<()> {
+ Ok(())
+ }
+
+ fn ungrab_keyboard(&mut self) -> PlatformResult<()> {
+ Ok(())
+ }
+
+ fn keyboard_layout(&mut self) -> PlatformResult<String> {
+ let Some(keyboard) = self.state.seat.get_keyboard() else { return Ok(String::new()) };
+ Ok(keyboard.with_xkb_state(&mut self.state, |ctx| {
+ let xkb = ctx.xkb().lock().unwrap();
+ let layout = xkb.active_layout();
+ xkb.layout_name(layout).to_string()
+ }))
+ }
+
+ fn cycle_keyboard_layout(&mut self) -> PlatformResult<String> {
+ let Some(keyboard) = self.state.seat.get_keyboard() else { return Ok(String::new()) };
+ Ok(keyboard.with_xkb_state(&mut self.state, |mut ctx| {
+ ctx.cycle_next_layout();
+ let xkb = ctx.xkb().lock().unwrap();
+ let layout = xkb.active_layout();
+ xkb.layout_name(layout).to_string()
+ }))
+ }
+}