From f98ccf8efc5e3f6dd3e31b705178603f6556d316 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sun, 16 Nov 2025 17:15:45 +0900 Subject: [PATCH 01/10] Winit version 0.31.0-beta.2 --- Cargo.toml | 22 +++++++++++----------- README.md | 2 +- winit-android/src/lib.rs | 2 +- winit/src/changelog/unreleased.md | 15 --------------- winit/src/changelog/v0.31.md | 17 +++++++++++++++++ 5 files changed, 30 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 243c0680..362b9da2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,22 +8,22 @@ edition = "2024" license = "Apache-2.0" repository = "https://github.com/rust-windowing/winit" rust-version = "1.85" -version = "0.31.0-beta.1" +version = "0.31.0-beta.2" [workspace.dependencies] # Workspace dependencies. # `winit` has no version here to allow using it in dev deps for docs. winit = { path = "winit" } -winit-android = { version = "=0.31.0-beta.1", path = "winit-android" } -winit-appkit = { version = "=0.31.0-beta.1", path = "winit-appkit" } -winit-common = { version = "=0.31.0-beta.1", path = "winit-common" } -winit-core = { version = "=0.31.0-beta.1", path = "winit-core" } -winit-orbital = { version = "=0.31.0-beta.1", path = "winit-orbital" } -winit-uikit = { version = "=0.31.0-beta.1", path = "winit-uikit" } -winit-wayland = { version = "=0.31.0-beta.1", path = "winit-wayland", default-features = false } -winit-web = { version = "=0.31.0-beta.1", path = "winit-web" } -winit-win32 = { version = "=0.31.0-beta.1", path = "winit-win32" } -winit-x11 = { version = "=0.31.0-beta.1", path = "winit-x11" } +winit-android = { version = "=0.31.0-beta.2", path = "winit-android" } +winit-appkit = { version = "=0.31.0-beta.2", path = "winit-appkit" } +winit-common = { version = "=0.31.0-beta.2", path = "winit-common" } +winit-core = { version = "=0.31.0-beta.2", path = "winit-core" } +winit-orbital = { version = "=0.31.0-beta.2", path = "winit-orbital" } +winit-uikit = { version = "=0.31.0-beta.2", path = "winit-uikit" } +winit-wayland = { version = "=0.31.0-beta.2", path = "winit-wayland", default-features = false } +winit-web = { version = "=0.31.0-beta.2", path = "winit-web" } +winit-win32 = { version = "=0.31.0-beta.2", path = "winit-win32" } +winit-x11 = { version = "=0.31.0-beta.2", path = "winit-x11" } # Core dependencies. bitflags = "2" diff --git a/README.md b/README.md index 15ea7e90..ac7fa2ae 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ```toml [dependencies] -winit = "0.31.0-beta.1" +winit = "0.31.0-beta.2" ``` ## [Documentation](https://docs.rs/winit) diff --git a/winit-android/src/lib.rs b/winit-android/src/lib.rs index abe97677..989c9716 100644 --- a/winit-android/src/lib.rs +++ b/winit-android/src/lib.rs @@ -63,7 +63,7 @@ //! with `cargo apk`, then the minimal changes would be: //! 1. Remove `ndk-glue` from your `Cargo.toml` //! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = -//! "0.31.0-beta.1", features = [ "android-native-activity" ] }` +//! "0.31.0-beta.2", features = [ "android-native-activity" ] }` //! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc //! macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize //! logging as above). diff --git a/winit/src/changelog/unreleased.md b/winit/src/changelog/unreleased.md index 93491753..f3a0f6d2 100644 --- a/winit/src/changelog/unreleased.md +++ b/winit/src/changelog/unreleased.md @@ -39,18 +39,3 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased - -### Added - -- Add `EventLoopExtRegister::register_app` for being explicit about how the event loop runs on Web. -- Add `EventLoopExtNeverReturn::run_app_never_return` for being explicit about how the event loop runs on iOS. - -### Changed - -- On Web, avoid throwing an exception in `EventLoop::run_app`, instead preferring to return to the caller. - This requires passing a `'static` application to ensure that the application state will live as long as necessary. -- On Web, the event loop can now always be re-created once it has finished running. - -### Fixed - -- Fixed panic when calling `Window::set_ime_allowed`. diff --git a/winit/src/changelog/v0.31.md b/winit/src/changelog/v0.31.md index 109e23f1..f8eb99a2 100644 --- a/winit/src/changelog/v0.31.md +++ b/winit/src/changelog/v0.31.md @@ -1,3 +1,20 @@ +## 0.31.0-beta.2 + +### Added + +- Add `EventLoopExtRegister::register_app` for being explicit about how the event loop runs on Web. +- Add `EventLoopExtNeverReturn::run_app_never_return` for being explicit about how the event loop runs on iOS. + +### Changed + +- On Web, avoid throwing an exception in `EventLoop::run_app`, instead preferring to return to the caller. + This requires passing a `'static` application to ensure that the application state will live as long as necessary. +- On Web, the event loop can now always be re-created once it has finished running. + +### Fixed + +- Fixed panic when calling `Window::set_ime_allowed`. + ## 0.31.0-beta.1 ### Added From b961e4877d0f74d169b21eebf8999bf1441ff598 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 25 Sep 2024 10:03:26 -0400 Subject: [PATCH 02/10] feat: xdg surface handle --- winit-wayland/src/lib.rs | 32 ++++++++++++++++++++++++++++++++ winit-wayland/src/window/mod.rs | 14 +++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/winit-wayland/src/lib.rs b/winit-wayland/src/lib.rs index b34dedad..0c89e972 100644 --- a/winit-wayland/src/lib.rs +++ b/winit-wayland/src/lib.rs @@ -14,10 +14,13 @@ //! * `wayland-csd-adwaita-crossfont`. //! * `wayland-csd-adwaita-notitle`. //! * `wayland-csd-adwaita-notitlebar`. +use std::any::Any; use std::ffi::c_void; +use std::marker::PhantomData; use std::ptr::NonNull; use dpi::{LogicalSize, PhysicalSize}; +use rwh_06::HandleError; use sctk::reexports::client::Proxy; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::shm::slot::{Buffer, CreateBufferError, SlotPool}; @@ -72,12 +75,35 @@ pub trait EventLoopBuilderExtWayland { fn with_any_thread(&mut self, any_thread: bool) -> &mut Self; } +pub struct XdgSurfaceHandle<'a> { + raw: NonNull, + _marker: PhantomData<&'a ()>, +} + +impl<'a> XdgSurfaceHandle<'a> { + /// Create a `XdgSurfaceHandle` from a [`NonNull`] + pub unsafe fn borrow_raw(raw: NonNull) -> Self { + Self { raw, _marker: PhantomData } + } + + /// Get the underlying raw xdg_surface handle. + pub fn as_raw(&self) -> NonNull { + self.raw + } +} + +pub trait HasXdgSurfaceHandle { + fn xdg_surface_handle(&self) -> Result, HandleError>; +} + /// Additional methods on [`Window`] that are specific to Wayland. /// /// [`Window`]: crate::window::Window pub trait WindowExtWayland { /// Returns `xdg_toplevel` of the window or [`None`] if the window is X11 window. fn xdg_toplevel(&self) -> Option>; + + fn xdg_surface_handle<'a>(&'a self) -> Option<&dyn HasXdgSurfaceHandle>; } impl WindowExtWayland for dyn CoreWindow + '_ { @@ -85,6 +111,12 @@ impl WindowExtWayland for dyn CoreWindow + '_ { fn xdg_toplevel(&self) -> Option> { self.cast_ref::()?.xdg_toplevel() } + + #[inline] + + fn xdg_surface_handle(&self) -> Option<&dyn HasXdgSurfaceHandle> { + Some(self.cast_ref::()? as &dyn HasXdgSurfaceHandle) + } } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/winit-wayland/src/window/mod.rs b/winit-wayland/src/window/mod.rs index 37021bb0..44d93011 100644 --- a/winit-wayland/src/window/mod.rs +++ b/winit-wayland/src/window/mod.rs @@ -12,6 +12,7 @@ use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::{Proxy, QueueHandle}; use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::XdgActivationV1; use sctk::shell::WaylandSurface; +use sctk::shell::xdg::XdgSurface; use sctk::shell::xdg::window::{Window as SctkWindow, WindowDecorations}; use tracing::warn; use winit_core::cursor::Cursor; @@ -30,7 +31,7 @@ use super::event_loop::sink::EventSink; use super::output::MonitorHandle; use super::state::WinitState; use super::types::xdg_activation::XdgActivationTokenData; -use crate::{WindowAttributesWayland, output}; +use crate::{HasXdgSurfaceHandle, WindowAttributesWayland, XdgSurfaceHandle, output}; pub(crate) mod state; @@ -655,6 +656,17 @@ impl CoreWindow for Window { } } +impl HasXdgSurfaceHandle for Window { + fn xdg_surface_handle(&self) -> Result, rwh_06::HandleError> { + let raw = { + let ptr = self.window.xdg_surface().id().as_ptr(); + std::ptr::NonNull::new(ptr as *mut _).expect("wl_surface will never be null") + }; + + unsafe { Ok(XdgSurfaceHandle::borrow_raw(raw)) } + } +} + /// The request from the window to the event loop. #[derive(Debug)] pub struct WindowRequests { From 66dc0eab25d0a40bbc2009d5950acff179b587ae Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 15 Oct 2024 19:06:40 -0400 Subject: [PATCH 03/10] feat: SuggestedBounds event --- winit-core/src/event.rs | 7 +++++++ winit-wayland/src/event_loop/mod.rs | 16 ++++++++++++++++ winit-wayland/src/state.rs | 24 +++++++++++++++++++----- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/winit-core/src/event.rs b/winit-core/src/event.rs index 24a80990..9e0540cf 100644 --- a/winit-core/src/event.rs +++ b/winit-core/src/event.rs @@ -368,6 +368,13 @@ pub enum WindowEvent { surface_size_writer: SurfaceSizeWriter, }, + /// The suggested bounds of the window's surface has changed. + /// + /// Contains the new bounds of the surface + /// + /// - **iOS / Android / Web / Orbital / Windows:** Unsupported. + SuggestedBounds(Option>), + /// The system window theme has changed. /// /// Applications might wish to react to this to change the theme of the content of the window diff --git a/winit-wayland/src/event_loop/mod.rs b/winit-wayland/src/event_loop/mod.rs index 3a6ca698..aa6ea201 100644 --- a/winit-wayland/src/event_loop/mod.rs +++ b/winit-wayland/src/event_loop/mod.rs @@ -379,6 +379,22 @@ impl EventLoop { } } + if compositor_update.suggested_bounds { + let suggested_bounds = self.with_state(|state| { + let windows = state.windows.get_mut(); + let window = windows.get(&window_id).unwrap().lock().unwrap(); + + window + .last_configure + .as_ref() + .and_then(|c| c.suggested_bounds) + .map(|b| dpi::PhysicalSize::new(b.0, b.1)) + .clone() + }); + let event = WindowEvent::SuggestedBounds(suggested_bounds); + app.window_event(&self.active_event_loop, window_id, event); + } + // NOTE: Rescale changed the physical size which winit operates in, thus we should // resize. if compositor_update.resized || compositor_update.scale_changed { diff --git a/winit-wayland/src/state.rs b/winit-wayland/src/state.rs index d6090599..68d911d3 100644 --- a/winit-wayland/src/state.rs +++ b/winit-wayland/src/state.rs @@ -301,15 +301,20 @@ impl WindowHandler for WinitState { self.window_compositor_updates.len() - 1 }; - // Populate the configure to the window. - self.window_compositor_updates[pos].resized |= self + let mut winit_window = self .windows .get_mut() .get_mut(&window_id) .expect("got configure for dead window.") .lock() - .unwrap() - .configure(configure, &self.shm, &self.subcompositor_state); + .unwrap(); + // Populate the configure to the window. + + self.window_compositor_updates[pos].suggested_bounds |= configure.suggested_bounds + != winit_window.last_configure.as_ref().and_then(|last| last.suggested_bounds); + + self.window_compositor_updates[pos].resized |= + winit_window.configure(configure, &self.shm, &self.subcompositor_state); // 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. @@ -437,11 +442,20 @@ pub struct WindowCompositorUpdate { /// Close the window. pub close_window: bool, + + /// New suggested bounds. + pub suggested_bounds: bool, } impl WindowCompositorUpdate { fn new(window_id: WindowId) -> Self { - Self { window_id, resized: false, scale_changed: false, close_window: false } + Self { + window_id, + resized: false, + scale_changed: false, + close_window: false, + suggested_bounds: false, + } } } From 43f4760b0e57da55ee09650c1eb8ebe70aa83c2b Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 29 Oct 2024 14:34:19 -0400 Subject: [PATCH 04/10] fix: scaling --- winit-wayland/src/event_loop/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winit-wayland/src/event_loop/mod.rs b/winit-wayland/src/event_loop/mod.rs index aa6ea201..155d2db6 100644 --- a/winit-wayland/src/event_loop/mod.rs +++ b/winit-wayland/src/event_loop/mod.rs @@ -388,7 +388,7 @@ impl EventLoop { .last_configure .as_ref() .and_then(|c| c.suggested_bounds) - .map(|b| dpi::PhysicalSize::new(b.0, b.1)) + .map(|b| dpi::LogicalSize::new(b.0, b.1).to_physical(window.scale_factor())) .clone() }); let event = WindowEvent::SuggestedBounds(suggested_bounds); From ec30b8004f18ca0a2352172fa47ca90bad2f2ad6 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 25 Sep 2025 15:38:16 -0400 Subject: [PATCH 05/10] fix: window state events --- winit-core/src/event.rs | 5 +++++ winit-wayland/src/event_loop/mod.rs | 5 +++++ winit-wayland/src/state.rs | 10 ++++++++++ winit-wayland/src/window/mod.rs | 5 +++++ winit/examples/application.rs | 4 +++- 5 files changed, 28 insertions(+), 1 deletion(-) diff --git a/winit-core/src/event.rs b/winit-core/src/event.rs index 9e0540cf..ade39acf 100644 --- a/winit-core/src/event.rs +++ b/winit-core/src/event.rs @@ -375,6 +375,11 @@ pub enum WindowEvent { /// - **iOS / Android / Web / Orbital / Windows:** Unsupported. SuggestedBounds(Option>), + /// The window state has changed. + /// + /// - **iOS / Android / Web / Orbital / Windows:** Unsupported. + WindowStateChanged, + /// The system window theme has changed. /// /// Applications might wish to react to this to change the theme of the content of the window diff --git a/winit-wayland/src/event_loop/mod.rs b/winit-wayland/src/event_loop/mod.rs index 155d2db6..555ab7d1 100644 --- a/winit-wayland/src/event_loop/mod.rs +++ b/winit-wayland/src/event_loop/mod.rs @@ -395,6 +395,11 @@ impl EventLoop { app.window_event(&self.active_event_loop, window_id, event); } + if compositor_update.xdg_window_state.is_some() { + let event = WindowEvent::WindowStateChanged; + app.window_event(&self.active_event_loop, window_id, event); + } + // NOTE: Rescale changed the physical size which winit operates in, thus we should // resize. if compositor_update.resized || compositor_update.scale_changed { diff --git a/winit-wayland/src/state.rs b/winit-wayland/src/state.rs index 68d911d3..8ff513cc 100644 --- a/winit-wayland/src/state.rs +++ b/winit-wayland/src/state.rs @@ -313,6 +313,12 @@ impl WindowHandler for WinitState { self.window_compositor_updates[pos].suggested_bounds |= configure.suggested_bounds != winit_window.last_configure.as_ref().and_then(|last| last.suggested_bounds); + self.window_compositor_updates[pos].xdg_window_state = winit_window + .last_configure + .as_ref() + .is_none_or(|last| last.state != configure.state) + .then_some(configure.state); + self.window_compositor_updates[pos].resized |= winit_window.configure(configure, &self.shm, &self.subcompositor_state); @@ -445,6 +451,9 @@ pub struct WindowCompositorUpdate { /// New suggested bounds. pub suggested_bounds: bool, + + /// New xdg window state. + pub xdg_window_state: Option, } impl WindowCompositorUpdate { @@ -455,6 +464,7 @@ impl WindowCompositorUpdate { scale_changed: false, close_window: false, suggested_bounds: false, + xdg_window_state: None, } } } diff --git a/winit-wayland/src/window/mod.rs b/winit-wayland/src/window/mod.rs index 44d93011..a2f5ca30 100644 --- a/winit-wayland/src/window/mod.rs +++ b/winit-wayland/src/window/mod.rs @@ -252,6 +252,11 @@ impl Window { pub fn surface(&self) -> &WlSurface { self.window.wl_surface() } + + #[inline] + pub fn xdg_window_state(&self) -> Option { + self.window_state.lock().unwrap().last_configure.as_ref().map(|c| c.state) + } } impl Drop for Window { diff --git a/winit/examples/application.rs b/winit/examples/application.rs index dc432bb0..573f228e 100644 --- a/winit/examples/application.rs +++ b/winit/examples/application.rs @@ -557,7 +557,9 @@ impl ApplicationHandler for Application { | WindowEvent::DragDropped { .. } | WindowEvent::Destroyed | WindowEvent::Ime(_) - | WindowEvent::Moved(_) => (), + | WindowEvent::Moved(_) + | WindowEvent::SuggestedBounds(_) + | WindowEvent::WindowStateChanged => (), } } From 6c9a2d4e9b7192c3b0d85b7bd756b5c27239fd70 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 5 Dec 2025 16:35:22 -0500 Subject: [PATCH 06/10] fix: if the new configure matches, don't overwrite state with None --- winit-wayland/src/event_loop/mod.rs | 2 +- winit-wayland/src/state.rs | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/winit-wayland/src/event_loop/mod.rs b/winit-wayland/src/event_loop/mod.rs index 555ab7d1..65c97412 100644 --- a/winit-wayland/src/event_loop/mod.rs +++ b/winit-wayland/src/event_loop/mod.rs @@ -395,7 +395,7 @@ impl EventLoop { app.window_event(&self.active_event_loop, window_id, event); } - if compositor_update.xdg_window_state.is_some() { + if compositor_update.xdg_window_state.take().is_some() { let event = WindowEvent::WindowStateChanged; app.window_event(&self.active_event_loop, window_id, event); } diff --git a/winit-wayland/src/state.rs b/winit-wayland/src/state.rs index 8ff513cc..dc9b9e35 100644 --- a/winit-wayland/src/state.rs +++ b/winit-wayland/src/state.rs @@ -313,11 +313,9 @@ impl WindowHandler for WinitState { self.window_compositor_updates[pos].suggested_bounds |= configure.suggested_bounds != winit_window.last_configure.as_ref().and_then(|last| last.suggested_bounds); - self.window_compositor_updates[pos].xdg_window_state = winit_window - .last_configure - .as_ref() - .is_none_or(|last| last.state != configure.state) - .then_some(configure.state); + if winit_window.last_configure.as_ref().is_none_or(|last| last.state != configure.state) { + self.window_compositor_updates[pos].xdg_window_state = Some(configure.state); + } self.window_compositor_updates[pos].resized |= winit_window.configure(configure, &self.shm, &self.subcompositor_state); From ed46dd031d2d1eb714416cda8933a73e4c62e710 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Thu, 18 Dec 2025 11:55:32 +1100 Subject: [PATCH 07/10] fix(platform/orbital): handle `EINTR` when reading from `event_socket` Signed-off-by: Anhad Singh --- winit-orbital/src/event_loop.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/winit-orbital/src/event_loop.rs b/winit-orbital/src/event_loop.rs index 8ef97156..7db55f14 100644 --- a/winit-orbital/src/event_loop.rs +++ b/winit-orbital/src/event_loop.rs @@ -639,7 +639,13 @@ impl EventLoop { // Wait for event if needed. let mut event = syscall::Event::default(); - self.window_target.event_socket.read(&mut event).unwrap(); + loop { + match self.window_target.event_socket.read(&mut event) { + Ok(_) => break, + Err(syscall::Error { errno: syscall::EINTR }) => continue, + Err(err) => unreachable!("failed to read event: {}", err), + } + } // TODO: handle spurious wakeups (redraw caused wakeup but redraw already handled) match requested_resume { From 0370cadd6722208b864a64caaaed34387c20ae96 Mon Sep 17 00:00:00 2001 From: "Ibuki.O" Date: Sun, 18 Jan 2026 17:20:10 +0900 Subject: [PATCH 08/10] feat: Use libredox instead of redox_syscall. --- Cargo.toml | 3 ++- winit-orbital/Cargo.toml | 1 + winit-orbital/src/lib.rs | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 362b9da2..1dde01de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,7 +80,8 @@ xkbcommon-dl = "0.4.2" # Orbital dependencies. orbclient = { version = "0.3.47", default-features = false } -redox_syscall = "0.5.7" +redox_syscall = "0.7" +libredox = "0.1.12" # Web dependencies. atomic-waker = "1" diff --git a/winit-orbital/Cargo.toml b/winit-orbital/Cargo.toml index 1d1e20a8..330bd520 100644 --- a/winit-orbital/Cargo.toml +++ b/winit-orbital/Cargo.toml @@ -23,3 +23,4 @@ winit-core.workspace = true # Platform-specific orbclient.workspace = true redox_syscall.workspace = true +libredox.workspace = true diff --git a/winit-orbital/src/lib.rs b/winit-orbital/src/lib.rs index 71efc821..0a41dd0e 100644 --- a/winit-orbital/src/lib.rs +++ b/winit-orbital/src/lib.rs @@ -33,7 +33,7 @@ impl RedoxSocket { // example, the seek would change in a potentially unpredictable way if either read or write // were called at the same time by multiple threads. fn open_raw(path: &str) -> syscall::Result { - let fd = syscall::open(path, syscall::O_RDWR | syscall::O_CLOEXEC)?; + let fd = libredox::call::open(path, libredox::flag::O_RDWR | libredox::flag::O_CLOEXEC, 0)?; Ok(Self { fd }) } @@ -97,7 +97,7 @@ struct WindowProperties<'a> { impl<'a> WindowProperties<'a> { fn new(path: &'a str) -> Self { - // orbital:flags/x/y/w/h/t + // /scheme/orbital/flags/x/y/w/h/t let mut parts = path.splitn(6, '/'); let flags = parts.next().unwrap_or(""); let x = parts.next().map_or(0, |part| part.parse::().unwrap_or(0)); From a610ac9c7a72b39ff102ed4d946291618dc725b6 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 4 Feb 2026 14:32:26 -0500 Subject: [PATCH 09/10] chore: window state --- src/platform_impl/linux/wayland/dnd.rs | 135 +++++++++++++++++++++++++ winit-wayland/src/lib.rs | 8 +- 2 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 src/platform_impl/linux/wayland/dnd.rs diff --git a/src/platform_impl/linux/wayland/dnd.rs b/src/platform_impl/linux/wayland/dnd.rs new file mode 100644 index 00000000..d4405650 --- /dev/null +++ b/src/platform_impl/linux/wayland/dnd.rs @@ -0,0 +1,135 @@ +use sctk::data_device_manager::{ + data_device::DataDeviceHandler, data_offer::DataOfferHandler, data_source::DataSourceHandler, +}; + +use crate::platform_impl::wayland::state::WinitState; + +impl DataDeviceHandler for WinitState { + fn enter( + &mut self, + conn: &wayland_client::Connection, + qh: &wayland_client::QueueHandle, + data_device: &wayland_client::protocol::wl_data_device::WlDataDevice, + x: f64, + y: f64, + wl_surface: &wayland_client::protocol::wl_surface::WlSurface, + ) { + todo!() + } + + fn leave( + &mut self, + conn: &wayland_client::Connection, + qh: &wayland_client::QueueHandle, + data_device: &wayland_client::protocol::wl_data_device::WlDataDevice, + ) { + todo!() + } + + fn motion( + &mut self, + conn: &wayland_client::Connection, + qh: &wayland_client::QueueHandle, + data_device: &wayland_client::protocol::wl_data_device::WlDataDevice, + x: f64, + y: f64, + ) { + todo!() + } + + fn selection( + &mut self, + conn: &wayland_client::Connection, + qh: &wayland_client::QueueHandle, + data_device: &wayland_client::protocol::wl_data_device::WlDataDevice, + ) { + todo!() + } + + fn drop_performed( + &mut self, + conn: &wayland_client::Connection, + qh: &wayland_client::QueueHandle, + data_device: &wayland_client::protocol::wl_data_device::WlDataDevice, + ) { + todo!() + } +} + +impl DataOfferHandler for WinitState { + fn source_actions( + &mut self, + conn: &wayland_client::Connection, + qh: &wayland_client::QueueHandle, + offer: &mut sctk::data_device_manager::data_offer::DragOffer, + actions: wayland_client::protocol::wl_data_device_manager::DndAction, + ) { + todo!() + } + + fn selected_action( + &mut self, + conn: &wayland_client::Connection, + qh: &wayland_client::QueueHandle, + offer: &mut sctk::data_device_manager::data_offer::DragOffer, + actions: wayland_client::protocol::wl_data_device_manager::DndAction, + ) { + todo!() + } +} + +impl DataSourceHandler for WinitState { + fn accept_mime( + &mut self, + conn: &wayland_client::Connection, + qh: &wayland_client::QueueHandle, + source: &wayland_client::protocol::wl_data_source::WlDataSource, + mime: Option, + ) { + } + + fn send_request( + &mut self, + conn: &wayland_client::Connection, + qh: &wayland_client::QueueHandle, + source: &wayland_client::protocol::wl_data_source::WlDataSource, + mime: String, + fd: sctk::data_device_manager::WritePipe, + ) { + } + + fn cancelled( + &mut self, + conn: &wayland_client::Connection, + qh: &wayland_client::QueueHandle, + source: &wayland_client::protocol::wl_data_source::WlDataSource, + ) { + } + + fn dnd_dropped( + &mut self, + conn: &wayland_client::Connection, + qh: &wayland_client::QueueHandle, + source: &wayland_client::protocol::wl_data_source::WlDataSource, + ) { + } + + fn dnd_finished( + &mut self, + conn: &wayland_client::Connection, + qh: &wayland_client::QueueHandle, + source: &wayland_client::protocol::wl_data_source::WlDataSource, + ) { + } + + fn action( + &mut self, + conn: &wayland_client::Connection, + qh: &wayland_client::QueueHandle, + source: &wayland_client::protocol::wl_data_source::WlDataSource, + action: wayland_client::protocol::wl_data_device_manager::DndAction, + ) { + } +} + +sctk::delegate_data_device!(WinitState); diff --git a/winit-wayland/src/lib.rs b/winit-wayland/src/lib.rs index 0c89e972..937a7df7 100644 --- a/winit-wayland/src/lib.rs +++ b/winit-wayland/src/lib.rs @@ -23,6 +23,7 @@ use dpi::{LogicalSize, PhysicalSize}; use rwh_06::HandleError; use sctk::reexports::client::Proxy; use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::csd_frame::WindowState; use sctk::shm::slot::{Buffer, CreateBufferError, SlotPool}; use wayland_client::protocol::wl_shm::Format; use winit_core::event_loop::ActiveEventLoop as CoreActiveEventLoop; @@ -104,6 +105,8 @@ pub trait WindowExtWayland { fn xdg_toplevel(&self) -> Option>; fn xdg_surface_handle<'a>(&'a self) -> Option<&dyn HasXdgSurfaceHandle>; + + fn window_state(&self) -> Option; } impl WindowExtWayland for dyn CoreWindow + '_ { @@ -113,10 +116,13 @@ impl WindowExtWayland for dyn CoreWindow + '_ { } #[inline] - fn xdg_surface_handle(&self) -> Option<&dyn HasXdgSurfaceHandle> { Some(self.cast_ref::()? as &dyn HasXdgSurfaceHandle) } + + fn window_state(&self) -> Option { + self.cast_ref::()?.xdg_window_state() + } } #[derive(Debug, Clone, PartialEq, Eq)] From 261cda54017f98a12dc55569c864430fe6770366 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sat, 4 Apr 2026 23:59:33 +0900 Subject: [PATCH 10/10] winit-wayland: use `ext-background-effect` if available A more modern protocol compared to the KDE one. --- winit-core/src/window.rs | 3 +- winit-wayland/src/state.rs | 8 +- winit-wayland/src/types/bgr_effects.rs | 85 +++++++++++++++++++ .../src/types/ext_background_effect.rs | 59 +++++++++++++ winit-wayland/src/types/mod.rs | 2 + winit-wayland/src/window/mod.rs | 7 +- winit-wayland/src/window/state.rs | 65 +++++++++----- 7 files changed, 199 insertions(+), 30 deletions(-) create mode 100644 winit-wayland/src/types/bgr_effects.rs create mode 100644 winit-wayland/src/types/ext_background_effect.rs diff --git a/winit-core/src/window.rs b/winit-core/src/window.rs index aa7f1710..f22f35e5 100644 --- a/winit-core/src/window.rs +++ b/winit-core/src/window.rs @@ -881,7 +881,8 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug { /// ## Platform-specific /// /// - **Android / iOS / X11 / Web / Windows:** Unsupported. - /// - **Wayland:** Only works with org_kde_kwin_blur_manager protocol. + /// - **Wayland:** Only works with `org_kde_kwin_blur_manager` or + /// `ext_background_effect_manager_v1` protocol. fn set_blur(&self, blur: bool); /// Modifies the window's visibility. diff --git a/winit-wayland/src/state.rs b/winit-wayland/src/state.rs index dc9b9e35..adf32e4b 100644 --- a/winit-wayland/src/state.rs +++ b/winit-wayland/src/state.rs @@ -29,7 +29,7 @@ use crate::seat::{ PointerConstraintsState, PointerGesturesState, RelativePointerState, TextInputState, WinitPointerData, WinitPointerDataExt, WinitSeatState, }; -use crate::types::kwin_blur::KWinBlurManager; +use crate::types::bgr_effects::BgrEffectManager; use crate::types::wp_fractional_scaling::FractionalScalingManager; use crate::types::wp_tablet_input_v2::TabletManager; use crate::types::wp_viewporter::ViewporterState; @@ -116,8 +116,8 @@ pub struct WinitState { /// Fractional scaling manager. pub fractional_scaling_manager: Option, - /// KWin blur manager. - pub kwin_blur_manager: Option, + /// Blur manager. + pub blur_manager: Option, /// Loop handle to re-register event sources, such as keyboard repeat. pub loop_handle: LoopHandle<'static, Self>, @@ -192,7 +192,7 @@ impl WinitState { window_events_sink: Default::default(), viewporter_state, fractional_scaling_manager, - kwin_blur_manager: KWinBlurManager::new(globals, queue_handle).ok(), + blur_manager: BgrEffectManager::new(globals, queue_handle).ok(), seats, text_input_state: TextInputState::new(globals, queue_handle).ok(), diff --git a/winit-wayland/src/types/bgr_effects.rs b/winit-wayland/src/types/bgr_effects.rs new file mode 100644 index 00000000..0607d907 --- /dev/null +++ b/winit-wayland/src/types/bgr_effects.rs @@ -0,0 +1,85 @@ +use sctk::compositor::Region; +use sctk::reexports::client::QueueHandle; +use sctk::reexports::client::globals::{BindError, GlobalList}; +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::protocols::ext::background_effect::v1::client::ext_background_effect_surface_v1::ExtBackgroundEffectSurfaceV1; +use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur; + +use crate::state::WinitState; +use crate::types::ext_background_effect::ExtBackgroundEffectManager; +use crate::types::kwin_blur::KWinBlurManager; + +/// Wrapper around various background effects for [`WlSurface`]. +#[derive(Debug, Clone)] +pub enum BgrEffectManager { + Ext(ExtBackgroundEffectManager), + KWin(KWinBlurManager), +} + +impl BgrEffectManager { + pub fn new( + globals: &GlobalList, + queue_handle: &QueueHandle, + ) -> Result { + ExtBackgroundEffectManager::new(globals, queue_handle) + .map(Self::Ext) + .or_else(|_| KWinBlurManager::new(globals, queue_handle).map(Self::KWin)) + } + + /// Creates a new blur effect for the surface. + pub fn new_blur_effect( + &mut self, + surface: &WlSurface, + queue_handle: &QueueHandle, + ) -> SurfaceBlurEffect { + match self { + BgrEffectManager::Ext(mgr) => SurfaceBlurEffect::Ext(mgr.blur(surface, queue_handle)), + BgrEffectManager::KWin(mgr) => SurfaceBlurEffect::Kwin( + mgr.blur(surface, queue_handle), + mgr.clone(), + surface.clone(), + ), + } + } +} + +#[derive(Debug)] +pub enum SurfaceBlurEffect { + Ext(ExtBackgroundEffectSurfaceV1), + Kwin(OrgKdeKwinBlur, KWinBlurManager, WlSurface), +} + +impl SurfaceBlurEffect { + /// Returns `true` if the main surface commit is required. + /// + /// `None` clears the blur. + #[must_use] + pub fn set_blur(&self, region: Option<&Region>) -> bool { + let region = region.map(|region| region.wl_region()); + match self { + SurfaceBlurEffect::Ext(surface) => { + surface.set_blur_region(region); + true + }, + SurfaceBlurEffect::Kwin(blur, ..) => { + blur.set_region(region); + blur.commit(); + true + }, + } + } +} + +impl Drop for SurfaceBlurEffect { + fn drop(&mut self) { + match self { + SurfaceBlurEffect::Ext(surface) => surface.destroy(), + SurfaceBlurEffect::Kwin(blur, mgr, wl_surface) => { + blur.set_region(None); + blur.commit(); + blur.release(); + mgr.unset(wl_surface); + }, + } + } +} diff --git a/winit-wayland/src/types/ext_background_effect.rs b/winit-wayland/src/types/ext_background_effect.rs new file mode 100644 index 00000000..6ef1e27c --- /dev/null +++ b/winit-wayland/src/types/ext_background_effect.rs @@ -0,0 +1,59 @@ +use sctk::globals::GlobalData; +use sctk::reexports::client::globals::{BindError, GlobalList}; +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, delegate_dispatch}; +use wayland_protocols::ext::background_effect::v1::client::ext_background_effect_manager_v1::ExtBackgroundEffectManagerV1; +use wayland_protocols::ext::background_effect::v1::client::ext_background_effect_surface_v1::ExtBackgroundEffectSurfaceV1; + +use crate::state::WinitState; + +#[derive(Debug, Clone)] +pub struct ExtBackgroundEffectManager { + manager: ExtBackgroundEffectManagerV1, +} + +impl ExtBackgroundEffectManager { + pub fn new( + globals: &GlobalList, + queue_handle: &QueueHandle, + ) -> Result { + let manager = globals.bind(queue_handle, 1..=1, GlobalData)?; + Ok(Self { manager }) + } + + pub fn blur( + &mut self, + surface: &WlSurface, + queue_handle: &QueueHandle, + ) -> ExtBackgroundEffectSurfaceV1 { + self.manager.get_background_effect(surface, queue_handle, ()) + } +} + +impl Dispatch for ExtBackgroundEffectManager { + fn event( + _: &mut WinitState, + _: &ExtBackgroundEffectManagerV1, + _: ::Event, + _: &GlobalData, + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for ExtBackgroundEffectManager { + fn event( + _: &mut WinitState, + _: &ExtBackgroundEffectSurfaceV1, + _: ::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + // There is no event + } +} + +delegate_dispatch!(WinitState: [ExtBackgroundEffectManagerV1: GlobalData] => ExtBackgroundEffectManager); +delegate_dispatch!(WinitState: [ExtBackgroundEffectSurfaceV1: ()] => ExtBackgroundEffectManager); diff --git a/winit-wayland/src/types/mod.rs b/winit-wayland/src/types/mod.rs index 03b31bf5..2758da43 100644 --- a/winit-wayland/src/types/mod.rs +++ b/winit-wayland/src/types/mod.rs @@ -1,6 +1,8 @@ //! Wayland protocol implementation boilerplate. +pub mod bgr_effects; pub mod cursor; +pub mod ext_background_effect; pub mod kwin_blur; pub mod wp_fractional_scaling; pub mod wp_tablet_input_v2; diff --git a/winit-wayland/src/window/mod.rs b/winit-wayland/src/window/mod.rs index a2f5ca30..ad6b01d5 100644 --- a/winit-wayland/src/window/mod.rs +++ b/winit-wayland/src/window/mod.rs @@ -128,7 +128,8 @@ impl Window { // Set transparency hint. window_state.set_transparent(attributes.transparent); - window_state.set_blur(attributes.blur); + // Set blur. + let _ = window_state.set_blur(attributes.blur); // Set the decorations hint. window_state.set_decorate(attributes.decorations); @@ -491,7 +492,9 @@ impl CoreWindow for Window { #[inline] fn set_blur(&self, blur: bool) { - self.window_state.lock().unwrap().set_blur(blur); + if self.window_state.lock().unwrap().set_blur(blur) { + self.request_redraw(); + } } #[inline] diff --git a/winit-wayland/src/window/state.rs b/winit-wayland/src/window/state.rs index 86711bdd..4c072cd9 100644 --- a/winit-wayland/src/window/state.rs +++ b/winit-wayland/src/window/state.rs @@ -29,7 +29,6 @@ use sctk::shm::slot::SlotPool; use sctk::subcompositor::SubcompositorState; use tracing::{info, warn}; use wayland_protocols::xdg::toplevel_icon::v1::client::xdg_toplevel_icon_manager_v1::XdgToplevelIconManagerV1; -use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur; use winit_core::cursor::{CursorIcon, CustomCursor as CoreCustomCursor}; use winit_core::error::{NotSupportedError, RequestError}; use winit_core::window::{ @@ -43,8 +42,8 @@ use crate::seat::{ ZwpTextInputV3Ext, }; use crate::state::{WindowCompositorUpdate, WinitState}; +use crate::types::bgr_effects::{BgrEffectManager, SurfaceBlurEffect}; use crate::types::cursor::{CustomCursor, SelectedCursor, WaylandCustomCursor}; -use crate::types::kwin_blur::KWinBlurManager; use crate::types::xdg_toplevel_icon_manager::ToplevelIcon; #[cfg(feature = "sctk-adwaita")] @@ -155,8 +154,8 @@ pub struct WindowState { viewport: Option, fractional_scale: Option, - blur: Option, - blur_manager: Option, + blur: Option, + blur_manager: Option, /// Whether the client side decorations have pending move operations. /// @@ -205,7 +204,7 @@ impl WindowState { toplevel_icon: None, xdg_toplevel_icon_manager, blur: None, - blur_manager: winit_state.kwin_blur_manager.clone(), + blur_manager: winit_state.blur_manager.clone(), compositor, handle, csd_fails: false, @@ -704,6 +703,13 @@ impl WindowState { // Set surface size without the borders. viewport.set_destination(self.size.width as _, self.size.height as _); } + + // Update blur region with new size. + if self.blur.is_some() { + // NOTE: either user resized or configure, in both cases + // the redraw scheduling is done on the caller side. + let _ = self.set_blur(true); + } } /// Get the scale factor of the window. @@ -1063,20 +1069,37 @@ impl WindowState { } } - /// Make window background blurred - #[inline] - pub fn set_blur(&mut self, blurred: bool) { - if blurred && self.blur.is_none() { - if let Some(blur_manager) = self.blur_manager.as_ref() { - let blur = blur_manager.blur(self.window.wl_surface(), &self.queue_handle); - blur.commit(); - self.blur = Some(blur); - } else { - info!("Blur manager unavailable, unable to change blur") - } - } else if !blurred && self.blur.is_some() { - self.blur_manager.as_ref().unwrap().unset(self.window.wl_surface()); - self.blur.take().unwrap().release(); + /// Make window background blurred. + /// + /// Returns `true` if redraw is required. + #[must_use] + pub fn set_blur(&mut self, blurred: bool) -> bool { + if !blurred { + self.blur = None; + return true; + } + + let mgr = match self.blur_manager.as_mut() { + Some(mgr) => mgr, + None => { + info!("Blur manager unavailable, unable to change blur"); + return false; + }, + }; + + let blur = match self.blur.as_ref() { + Some(blur) => blur, + None => { + self.blur = Some(mgr.new_blur_effect(self.window.wl_surface(), &self.queue_handle)); + self.blur.as_ref().unwrap() + }, + }; + + if let Ok(region) = Region::new(&*self.compositor) { + region.add(0, 0, i32::MAX, i32::MAX); + blur.set_blur(Some(®ion)) + } else { + false } } @@ -1174,10 +1197,6 @@ impl WindowState { impl Drop for WindowState { fn drop(&mut self) { - if let Some(blur) = self.blur.take() { - blur.release(); - } - if let Some(fs) = self.fractional_scale.take() { fs.destroy(); }