From fccc675fd327709374f3a7b908d83600e8afc31e Mon Sep 17 00:00:00 2001 From: mlm-games <125530737+mlm-games@users.noreply.github.com> Date: Mon, 8 Jun 2026 06:45:25 +0530 Subject: [PATCH 1/3] wayland: implement null-buffer unmap/remap for set_visible --- .../linux/wayland/event_loop/mod.rs | 9 +++++ src/platform_impl/linux/wayland/state.rs | 22 ++++++----- src/platform_impl/linux/wayland/window/mod.rs | 37 +++++++++++++++++-- .../linux/wayland/window/state.rs | 33 +++++++++++++++++ 4 files changed, 89 insertions(+), 12 deletions(-) diff --git a/src/platform_impl/linux/wayland/event_loop/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs index fef13f4558..66c93afcfe 100644 --- a/src/platform_impl/linux/wayland/event_loop/mod.rs +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -483,6 +483,15 @@ impl EventLoop { let mut window = state.windows.get_mut().get_mut(window_id).unwrap().lock().unwrap(); + if !window.is_visible() { + window_requests.get(window_id).unwrap().take_redraw_requested(); + return None; + } + + if window.is_unmapped() { + return None; + } + if window.frame_callback_state() == FrameCallbackState::Requested { return None; } diff --git a/src/platform_impl/linux/wayland/state.rs b/src/platform_impl/linux/wayland/state.rs index 13ef99c26a..2dd6d0f5ab 100644 --- a/src/platform_impl/linux/wayland/state.rs +++ b/src/platform_impl/linux/wayland/state.rs @@ -282,15 +282,19 @@ impl WindowHandler for WinitState { self.window_compositor_updates.len() - 1 }; - // Populate the configure to the window. - self.window_compositor_updates[pos].resized |= self - .windows - .get_mut() - .get_mut(&window_id) - .expect("got configure for dead window.") - .lock() - .unwrap() - .configure(configure, &self.shm, &self.subcompositor_state); + // Populate the configure to the window and clear unmapped flag. + { + let mut window_state = self + .windows + .get_mut() + .get_mut(&window_id) + .expect("got configure for dead window.") + .lock() + .unwrap(); + self.window_compositor_updates[pos].resized |= + window_state.configure(configure, &self.shm, &self.subcompositor_state); + window_state.clear_unmapped(); + } // NOTE: configure demands wl_surface::commit, however winit doesn't commit on behalf of the // users, since it can break a lot of things, thus it'll ask users to redraw instead. diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index 6d29a5a5f0..54daa61c01 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -142,6 +142,9 @@ impl Window { // Non-resizable implies that the min and max sizes are set to the same value. window_state.set_resizable(attributes.resizable); + // Set initial visibility. + window_state.set_visible(attributes.visible); + // Set startup mode. match attributes.fullscreen.map(Into::into) { Some(Fullscreen::Exclusive(_)) => { @@ -250,13 +253,41 @@ impl Window { } #[inline] - pub fn set_visible(&self, _visible: bool) { - // Not possible on Wayland. + pub fn set_visible(&self, visible: bool) { + if visible { + let mut state = self.window_state.lock().unwrap(); + if state.is_visible() { + return; + } + state.set_visible(true); + state.frame_callback_reset(); + if state.is_unmapped() { + // Re-send xdg_toplevel state to trigger a configure cycle + let title = state.title().to_owned(); + state.set_title(title); + drop(state); + self.surface().commit(); + return; + } + drop(state); + self.request_redraw(); + } else { + { + let mut state = self.window_state.lock().unwrap(); + if !state.is_visible() { + return; + } + state.set_visible(false); + state.set_unmapped(); + } + self.surface().attach(None, 0, 0); + self.surface().commit(); + } } #[inline] pub fn is_visible(&self) -> Option { - None + Some(self.window_state.lock().unwrap().is_visible()) } #[inline] diff --git a/src/platform_impl/linux/wayland/window/state.rs b/src/platform_impl/linux/wayland/window/state.rs index 1ef7a0656c..bb7132fb02 100644 --- a/src/platform_impl/linux/wayland/window/state.rs +++ b/src/platform_impl/linux/wayland/window/state.rs @@ -160,6 +160,12 @@ pub struct WindowState { // field drop order guarantees. /// The window frame, which is created from the configure request. frame: Option, + + /// Whether the window is visible. + visible: bool, + + /// Whether the surface was unmapped via null buffer and needs a configure cycle before the next buffer attach. + unmapped: bool, } impl WindowState { @@ -219,6 +225,8 @@ impl WindowState { title: String::default(), transparent: false, viewport, + visible: true, + unmapped: false, window, } } @@ -584,6 +592,31 @@ impl WindowState { self.last_configure.is_some() } + #[inline] + pub fn is_visible(&self) -> bool { + self.visible + } + + #[inline] + pub fn set_visible(&mut self, visible: bool) { + self.visible = visible; + } + + #[inline] + pub fn is_unmapped(&self) -> bool { + self.unmapped + } + + #[inline] + pub fn set_unmapped(&mut self) { + self.unmapped = true; + } + + #[inline] + pub fn clear_unmapped(&mut self) { + self.unmapped = false; + } + #[inline] pub fn is_decorated(&mut self) -> bool { let csd = self From b0cbe8241c40a326301f0b5e099ea3e0942c3069 Mon Sep 17 00:00:00 2001 From: mlm-games <125530737+mlm-games@users.noreply.github.com> Date: Mon, 8 Jun 2026 07:02:27 +0530 Subject: [PATCH 2/3] Update changelog and example --- examples/window.rs | 12 ++++++++++++ src/changelog/unreleased.md | 4 ++++ src/window.rs | 4 ++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/examples/window.rs b/examples/window.rs index 48afadf9f3..8d831e84e3 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -219,6 +219,7 @@ impl Application { Action::ToggleDecorations => window.toggle_decorations(), Action::ToggleFullscreen => window.toggle_fullscreen(), Action::ToggleMaximize => window.toggle_maximize(), + Action::ToggleVisible => window.toggle_visible(), Action::ToggleImeInput => window.toggle_ime(), Action::Minimize => window.minimize(), Action::NextCursor => window.next_cursor(), @@ -548,6 +549,7 @@ struct WindowState { named_idx: usize, custom_idx: usize, cursor_hidden: bool, + visible: bool, } impl WindowState { @@ -582,6 +584,7 @@ impl WindowState { ime, cursor_position: Default::default(), cursor_hidden: Default::default(), + visible: true, modifiers: Default::default(), occluded: Default::default(), rotated: Default::default(), @@ -605,6 +608,11 @@ impl WindowState { self.window.set_minimized(true); } + pub fn toggle_visible(&mut self) { + self.visible = !self.visible; + self.window.set_visible(self.visible); + } + pub fn cursor_moved(&mut self, position: PhysicalPosition) { self.cursor_position = Some(position); if self.ime { @@ -900,6 +908,7 @@ enum Action { ToggleResizable, ToggleFullscreen, ToggleMaximize, + ToggleVisible, Minimize, NextCursor, NextCustomCursor, @@ -931,6 +940,7 @@ impl Action { Action::ToggleResizable => "Toggle window resizable state", Action::ToggleFullscreen => "Toggle fullscreen", Action::ToggleMaximize => "Maximize", + Action::ToggleVisible => "Toggle visible", Action::Minimize => "Minimize", Action::ToggleResizeIncrements => "Use resize increments when resizing window", Action::NextCursor => "Advance the cursor to the next value", @@ -1072,6 +1082,8 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[ Binding::new("P", ModifiersState::CONTROL, Action::ToggleResizeIncrements), Binding::new("R", ModifiersState::CONTROL, Action::ToggleResizable), Binding::new("R", ModifiersState::ALT, Action::RequestResize), + // V. + Binding::new("V", ModifiersState::CONTROL, Action::ToggleVisible), // M. Binding::new("M", ModifiersState::CONTROL, Action::ToggleMaximize), Binding::new("M", ModifiersState::ALT, Action::Minimize), diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index f3a0f6d2c3..fde59fb9c2 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,3 +39,7 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased + +### Added + +- Implement `Window::set_visible` via null-buffer unmap/remap on wayland. diff --git a/src/window.rs b/src/window.rs index e0158eff52..ab738bb7f8 100644 --- a/src/window.rs +++ b/src/window.rs @@ -967,7 +967,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **Android / Wayland / Web:** Unsupported. + /// - **Android / Web:** Unsupported. /// - **iOS:** Can only be called on the main thread. #[inline] pub fn set_visible(&self, visible: bool) { @@ -983,7 +983,7 @@ impl Window { /// ## Platform-specific /// /// - **X11:** Not implemented. - /// - **Wayland / iOS / Android / Web:** Unsupported. + /// - **iOS / Android / Web:** Unsupported. #[inline] pub fn is_visible(&self) -> Option { let _span = tracing::debug_span!("winit::Window::is_visible",).entered(); From eedfc554678e5644505bebd5d6bf5b2d4a690ba6 Mon Sep 17 00:00:00 2001 From: mlm-games <125530737+mlm-games@users.noreply.github.com> Date: Mon, 8 Jun 2026 07:31:39 +0530 Subject: [PATCH 3/3] fmt check --- src/platform_impl/linux/wayland/window/state.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platform_impl/linux/wayland/window/state.rs b/src/platform_impl/linux/wayland/window/state.rs index bb7132fb02..d74b1ad62a 100644 --- a/src/platform_impl/linux/wayland/window/state.rs +++ b/src/platform_impl/linux/wayland/window/state.rs @@ -164,7 +164,8 @@ pub struct WindowState { /// Whether the window is visible. visible: bool, - /// Whether the surface was unmapped via null buffer and needs a configure cycle before the next buffer attach. + /// Whether the surface was unmapped via null buffer and needs a configure cycle before the + /// next buffer attach. unmapped: bool, }