winit-wayland: use ext-background-effect if available

A more modern protocol compared to the KDE one.
This commit is contained in:
Kirill Chibisov 2026-04-04 23:59:33 +09:00 committed by Ashley Wulber
parent a610ac9c7a
commit 261cda5401
7 changed files with 199 additions and 30 deletions

View file

@ -881,7 +881,8 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - **Android / iOS / X11 / Web / Windows:** Unsupported. /// - **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); fn set_blur(&self, blur: bool);
/// Modifies the window's visibility. /// Modifies the window's visibility.

View file

@ -29,7 +29,7 @@ use crate::seat::{
PointerConstraintsState, PointerGesturesState, RelativePointerState, TextInputState, PointerConstraintsState, PointerGesturesState, RelativePointerState, TextInputState,
WinitPointerData, WinitPointerDataExt, WinitSeatState, 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_fractional_scaling::FractionalScalingManager;
use crate::types::wp_tablet_input_v2::TabletManager; use crate::types::wp_tablet_input_v2::TabletManager;
use crate::types::wp_viewporter::ViewporterState; use crate::types::wp_viewporter::ViewporterState;
@ -116,8 +116,8 @@ pub struct WinitState {
/// Fractional scaling manager. /// Fractional scaling manager.
pub fractional_scaling_manager: Option<FractionalScalingManager>, pub fractional_scaling_manager: Option<FractionalScalingManager>,
/// KWin blur manager. /// Blur manager.
pub kwin_blur_manager: Option<KWinBlurManager>, pub blur_manager: Option<BgrEffectManager>,
/// Loop handle to re-register event sources, such as keyboard repeat. /// Loop handle to re-register event sources, such as keyboard repeat.
pub loop_handle: LoopHandle<'static, Self>, pub loop_handle: LoopHandle<'static, Self>,
@ -192,7 +192,7 @@ impl WinitState {
window_events_sink: Default::default(), window_events_sink: Default::default(),
viewporter_state, viewporter_state,
fractional_scaling_manager, fractional_scaling_manager,
kwin_blur_manager: KWinBlurManager::new(globals, queue_handle).ok(), blur_manager: BgrEffectManager::new(globals, queue_handle).ok(),
seats, seats,
text_input_state: TextInputState::new(globals, queue_handle).ok(), text_input_state: TextInputState::new(globals, queue_handle).ok(),

View file

@ -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<WinitState>,
) -> Result<Self, BindError> {
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<WinitState>,
) -> 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);
},
}
}
}

View file

@ -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<WinitState>,
) -> Result<Self, BindError> {
let manager = globals.bind(queue_handle, 1..=1, GlobalData)?;
Ok(Self { manager })
}
pub fn blur(
&mut self,
surface: &WlSurface,
queue_handle: &QueueHandle<WinitState>,
) -> ExtBackgroundEffectSurfaceV1 {
self.manager.get_background_effect(surface, queue_handle, ())
}
}
impl Dispatch<ExtBackgroundEffectManagerV1, GlobalData, WinitState> for ExtBackgroundEffectManager {
fn event(
_: &mut WinitState,
_: &ExtBackgroundEffectManagerV1,
_: <ExtBackgroundEffectManagerV1 as Proxy>::Event,
_: &GlobalData,
_: &Connection,
_: &QueueHandle<WinitState>,
) {
}
}
impl Dispatch<ExtBackgroundEffectSurfaceV1, (), WinitState> for ExtBackgroundEffectManager {
fn event(
_: &mut WinitState,
_: &ExtBackgroundEffectSurfaceV1,
_: <ExtBackgroundEffectSurfaceV1 as Proxy>::Event,
_: &(),
_: &Connection,
_: &QueueHandle<WinitState>,
) {
// There is no event
}
}
delegate_dispatch!(WinitState: [ExtBackgroundEffectManagerV1: GlobalData] => ExtBackgroundEffectManager);
delegate_dispatch!(WinitState: [ExtBackgroundEffectSurfaceV1: ()] => ExtBackgroundEffectManager);

View file

@ -1,6 +1,8 @@
//! Wayland protocol implementation boilerplate. //! Wayland protocol implementation boilerplate.
pub mod bgr_effects;
pub mod cursor; pub mod cursor;
pub mod ext_background_effect;
pub mod kwin_blur; pub mod kwin_blur;
pub mod wp_fractional_scaling; pub mod wp_fractional_scaling;
pub mod wp_tablet_input_v2; pub mod wp_tablet_input_v2;

View file

@ -128,7 +128,8 @@ impl Window {
// Set transparency hint. // Set transparency hint.
window_state.set_transparent(attributes.transparent); 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. // Set the decorations hint.
window_state.set_decorate(attributes.decorations); window_state.set_decorate(attributes.decorations);
@ -491,7 +492,9 @@ impl CoreWindow for Window {
#[inline] #[inline]
fn set_blur(&self, blur: bool) { 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] #[inline]

View file

@ -29,7 +29,6 @@ use sctk::shm::slot::SlotPool;
use sctk::subcompositor::SubcompositorState; use sctk::subcompositor::SubcompositorState;
use tracing::{info, warn}; use tracing::{info, warn};
use wayland_protocols::xdg::toplevel_icon::v1::client::xdg_toplevel_icon_manager_v1::XdgToplevelIconManagerV1; 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::cursor::{CursorIcon, CustomCursor as CoreCustomCursor};
use winit_core::error::{NotSupportedError, RequestError}; use winit_core::error::{NotSupportedError, RequestError};
use winit_core::window::{ use winit_core::window::{
@ -43,8 +42,8 @@ use crate::seat::{
ZwpTextInputV3Ext, ZwpTextInputV3Ext,
}; };
use crate::state::{WindowCompositorUpdate, WinitState}; use crate::state::{WindowCompositorUpdate, WinitState};
use crate::types::bgr_effects::{BgrEffectManager, SurfaceBlurEffect};
use crate::types::cursor::{CustomCursor, SelectedCursor, WaylandCustomCursor}; use crate::types::cursor::{CustomCursor, SelectedCursor, WaylandCustomCursor};
use crate::types::kwin_blur::KWinBlurManager;
use crate::types::xdg_toplevel_icon_manager::ToplevelIcon; use crate::types::xdg_toplevel_icon_manager::ToplevelIcon;
#[cfg(feature = "sctk-adwaita")] #[cfg(feature = "sctk-adwaita")]
@ -155,8 +154,8 @@ pub struct WindowState {
viewport: Option<WpViewport>, viewport: Option<WpViewport>,
fractional_scale: Option<WpFractionalScaleV1>, fractional_scale: Option<WpFractionalScaleV1>,
blur: Option<OrgKdeKwinBlur>, blur: Option<SurfaceBlurEffect>,
blur_manager: Option<KWinBlurManager>, blur_manager: Option<BgrEffectManager>,
/// Whether the client side decorations have pending move operations. /// Whether the client side decorations have pending move operations.
/// ///
@ -205,7 +204,7 @@ impl WindowState {
toplevel_icon: None, toplevel_icon: None,
xdg_toplevel_icon_manager, xdg_toplevel_icon_manager,
blur: None, blur: None,
blur_manager: winit_state.kwin_blur_manager.clone(), blur_manager: winit_state.blur_manager.clone(),
compositor, compositor,
handle, handle,
csd_fails: false, csd_fails: false,
@ -704,6 +703,13 @@ impl WindowState {
// Set surface size without the borders. // Set surface size without the borders.
viewport.set_destination(self.size.width as _, self.size.height as _); 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. /// Get the scale factor of the window.
@ -1063,20 +1069,37 @@ impl WindowState {
} }
} }
/// Make window background blurred /// Make window background blurred.
#[inline] ///
pub fn set_blur(&mut self, blurred: bool) { /// Returns `true` if redraw is required.
if blurred && self.blur.is_none() { #[must_use]
if let Some(blur_manager) = self.blur_manager.as_ref() { pub fn set_blur(&mut self, blurred: bool) -> bool {
let blur = blur_manager.blur(self.window.wl_surface(), &self.queue_handle); if !blurred {
blur.commit(); self.blur = None;
self.blur = Some(blur); return true;
} else { }
info!("Blur manager unavailable, unable to change blur")
} let mgr = match self.blur_manager.as_mut() {
} else if !blurred && self.blur.is_some() { Some(mgr) => mgr,
self.blur_manager.as_ref().unwrap().unset(self.window.wl_surface()); None => {
self.blur.take().unwrap().release(); 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(&region))
} else {
false
} }
} }
@ -1174,10 +1197,6 @@ impl WindowState {
impl Drop for WindowState { impl Drop for WindowState {
fn drop(&mut self) { fn drop(&mut self) {
if let Some(blur) = self.blur.take() {
blur.release();
}
if let Some(fs) = self.fractional_scale.take() { if let Some(fs) = self.fractional_scale.take() {
fs.destroy(); fs.destroy();
} }