diff --git a/CHANGELOG.md b/CHANGELOG.md index 5497dbbf..623cd1ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - On Wayland, load cursor icons `hand2` and `hand1` for `CursorIcon::Hand`. - **Breaking:** On Wayland, Theme trait and its support types are dropped. - On Wayland, bump `smithay-client-toolkit` to 0.15. +- On Wayland, implement `request_user_attention` with `xdg_activation_v1`. # 0.25.0 (2021-05-15) diff --git a/Cargo.toml b/Cargo.toml index fdc7750c..7990dcb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "i686-unknown-linux [features] default = ["x11", "wayland"] x11 = ["x11-dl", "mio", "mio-misc", "percent-encoding", "parking_lot"] -wayland = ["wayland-client", "sctk"] +wayland = ["wayland-client", "wayland-protocols", "sctk"] [dependencies] instant = { version = "0.1", features = ["wasm-bindgen"] } @@ -83,7 +83,8 @@ features = [ ] [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies] -wayland-client = { version = "0.28", features = [ "dlopen"] , optional = true } +wayland-client = { version = "0.29", features = [ "dlopen"], optional = true } +wayland-protocols = { version = "0.29", features = [ "staging_protocols"], optional = true } sctk = { package = "smithay-client-toolkit", version = "0.15.0", optional = true } mio = { version = "0.7", features = ["os-ext"], optional = true } mio-misc = { version = "1.0", optional = true } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index c1d7ef14..15c14582 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -437,12 +437,12 @@ impl Window { _ => (), } } - pub fn request_user_attention(&self, _request_type: Option) { + pub fn request_user_attention(&self, request_type: Option) { match self { #[cfg(feature = "x11")] - Window::X(ref w) => w.request_user_attention(_request_type), + Window::X(ref w) => w.request_user_attention(request_type), #[cfg(feature = "wayland")] - _ => (), + Window::Wayland(ref w) => w.request_user_attention(request_type), } } diff --git a/src/platform_impl/linux/wayland/env.rs b/src/platform_impl/linux/wayland/env.rs index 1cb2745b..ae213cb6 100644 --- a/src/platform_impl/linux/wayland/env.rs +++ b/src/platform_impl/linux/wayland/env.rs @@ -13,6 +13,7 @@ use sctk::reexports::protocols::xdg_shell::client::xdg_wm_base::XdgWmBase; use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1; use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1; use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3; +use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1; use sctk::environment::{Environment, SimpleGlobal}; use sctk::output::{OutputHandler, OutputHandling, OutputInfo, OutputStatusListener}; @@ -24,18 +25,27 @@ use sctk::shm::ShmHandler; #[derive(Debug, Clone, Copy)] pub struct WindowingFeatures { cursor_grab: bool, + xdg_activation: bool, } impl WindowingFeatures { /// Create `WindowingFeatures` based on the presented interfaces. pub fn new(env: &Environment) -> Self { let cursor_grab = env.get_global::().is_some(); - Self { cursor_grab } + let xdg_activation = env.get_global::().is_some(); + Self { + cursor_grab, + xdg_activation, + } } pub fn cursor_grab(&self) -> bool { self.cursor_grab } + + pub fn xdg_activation(&self) -> bool { + self.xdg_activation + } } sctk::environment!(WinitEnv, @@ -50,6 +60,7 @@ sctk::environment!(WinitEnv, ZwpRelativePointerManagerV1 => relative_pointer_manager, ZwpPointerConstraintsV1 => pointer_constraints, ZwpTextInputManagerV3 => text_input_manager, + XdgActivationV1 => xdg_activation, ], multis = [ WlSeat => seats, @@ -78,6 +89,8 @@ pub struct WinitEnv { text_input_manager: SimpleGlobal, decoration_manager: SimpleGlobal, + + xdg_activation: SimpleGlobal, } impl WinitEnv { @@ -109,6 +122,9 @@ impl WinitEnv { // IME handling. let text_input_manager = SimpleGlobal::new(); + // Surface activation. + let xdg_activation = SimpleGlobal::new(); + Self { seats, outputs, @@ -120,6 +136,7 @@ impl WinitEnv { relative_pointer_manager, pointer_constraints, text_input_manager, + xdg_activation, } } } diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index 9ced31fa..2ced24db 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -17,7 +17,7 @@ use crate::platform_impl::{ MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes as PlatformAttributes, }; -use crate::window::{CursorIcon, Fullscreen, WindowAttributes}; +use crate::window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes}; use super::env::WindowingFeatures; use super::event_loop::WinitState; @@ -197,7 +197,12 @@ impl Window { let window_requests = Arc::new(Mutex::new(Vec::with_capacity(64))); // Create a handle that performs all the requests on underlying sctk a window. - let window_handle = WindowHandle::new(window, size.clone(), window_requests.clone()); + let window_handle = WindowHandle::new( + &event_loop_window_target.env, + window, + size.clone(), + window_requests.clone(), + ); let mut winit_state = event_loop_window_target.state.borrow_mut(); @@ -251,9 +256,7 @@ impl Window { #[inline] pub fn set_title(&self, title: &str) { - let title_request = WindowRequest::Title(title.to_owned()); - self.window_requests.lock().unwrap().push(title_request); - self.event_loop_awakener.ping(); + self.send_request(WindowRequest::Title(title.to_owned())); } #[inline] @@ -285,9 +288,7 @@ impl Window { #[inline] pub fn request_redraw(&self) { - let redraw_request = WindowRequest::Redraw; - self.window_requests.lock().unwrap().push(redraw_request); - self.event_loop_awakener.ping(); + self.send_request(WindowRequest::Redraw); } #[inline] @@ -305,12 +306,7 @@ impl Window { let size = size.to_logical::(scale_factor); *self.size.lock().unwrap() = size; - let frame_size_request = WindowRequest::FrameSize(size); - self.window_requests - .lock() - .unwrap() - .push(frame_size_request); - self.event_loop_awakener.ping(); + self.send_request(WindowRequest::FrameSize(size)); } #[inline] @@ -318,9 +314,7 @@ impl Window { let scale_factor = self.scale_factor() as f64; let size = dimensions.map(|size| size.to_logical::(scale_factor)); - let min_size_request = WindowRequest::MinSize(size); - self.window_requests.lock().unwrap().push(min_size_request); - self.event_loop_awakener.ping(); + self.send_request(WindowRequest::MinSize(size)); } #[inline] @@ -328,19 +322,12 @@ impl Window { let scale_factor = self.scale_factor() as f64; let size = dimensions.map(|size| size.to_logical::(scale_factor)); - let max_size_request = WindowRequest::MaxSize(size); - self.window_requests.lock().unwrap().push(max_size_request); - self.event_loop_awakener.ping(); + self.send_request(WindowRequest::MaxSize(size)); } #[inline] pub fn set_resizable(&self, resizable: bool) { - let resizeable_request = WindowRequest::Resizeable(resizable); - self.window_requests - .lock() - .unwrap() - .push(resizeable_request); - self.event_loop_awakener.ping(); + self.send_request(WindowRequest::Resizeable(resizable)); } #[inline] @@ -352,9 +339,7 @@ impl Window { #[inline] pub fn set_decorations(&self, decorate: bool) { - let decorate_request = WindowRequest::Decorate(decorate); - self.window_requests.lock().unwrap().push(decorate_request); - self.event_loop_awakener.ping(); + self.send_request(WindowRequest::Decorate(decorate)); } #[inline] @@ -364,9 +349,7 @@ impl Window { return; } - let minimize_request = WindowRequest::Minimize; - self.window_requests.lock().unwrap().push(minimize_request); - self.event_loop_awakener.ping(); + self.send_request(WindowRequest::Minimize); } #[inline] @@ -376,9 +359,7 @@ impl Window { #[inline] pub fn set_maximized(&self, maximized: bool) { - let maximize_request = WindowRequest::Maximize(maximized); - self.window_requests.lock().unwrap().push(maximize_request); - self.event_loop_awakener.ping(); + self.send_request(WindowRequest::Maximize(maximized)); } #[inline] @@ -414,31 +395,17 @@ impl Window { None => WindowRequest::UnsetFullscreen, }; - self.window_requests - .lock() - .unwrap() - .push(fullscreen_request); - self.event_loop_awakener.ping(); + self.send_request(fullscreen_request); } #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { - let cursor_icon_request = WindowRequest::NewCursorIcon(cursor); - self.window_requests - .lock() - .unwrap() - .push(cursor_icon_request); - self.event_loop_awakener.ping(); + self.send_request(WindowRequest::NewCursorIcon(cursor)); } #[inline] pub fn set_cursor_visible(&self, visible: bool) { - let cursor_visible_request = WindowRequest::ShowCursor(visible); - self.window_requests - .lock() - .unwrap() - .push(cursor_visible_request); - self.event_loop_awakener.ping(); + self.send_request(WindowRequest::ShowCursor(visible)); } #[inline] @@ -447,16 +414,20 @@ impl Window { return Err(ExternalError::NotSupported(NotSupportedError::new())); } - let cursor_grab_request = WindowRequest::GrabCursor(grab); - self.window_requests - .lock() - .unwrap() - .push(cursor_grab_request); - self.event_loop_awakener.ping(); + self.send_request(WindowRequest::GrabCursor(grab)); Ok(()) } + pub fn request_user_attention(&self, request_type: Option) { + if !self.windowing_features.xdg_activation() { + warn!("`request_user_attention` isn't supported"); + return; + } + + self.send_request(WindowRequest::Attention(request_type)); + } + #[inline] pub fn set_cursor_position(&self, _: Position) -> Result<(), ExternalError> { // XXX This is possible if the locked pointer is being used. We don't have any @@ -471,12 +442,7 @@ impl Window { #[inline] pub fn drag_window(&self) -> Result<(), ExternalError> { - let drag_window_request = WindowRequest::DragWindow; - self.window_requests - .lock() - .unwrap() - .push(drag_window_request); - self.event_loop_awakener.ping(); + self.send_request(WindowRequest::DragWindow); Ok(()) } @@ -485,12 +451,7 @@ impl Window { pub fn set_ime_position(&self, position: Position) { let scale_factor = self.scale_factor() as f64; let position = position.to_logical(scale_factor); - let ime_position_request = WindowRequest::IMEPosition(position); - self.window_requests - .lock() - .unwrap() - .push(ime_position_request); - self.event_loop_awakener.ping(); + self.send_request(WindowRequest::IMEPosition(position)); } #[inline] @@ -530,12 +491,16 @@ impl Window { ..WaylandHandle::empty() } } + + #[inline] + fn send_request(&self, request: WindowRequest) { + self.window_requests.lock().unwrap().push(request); + self.event_loop_awakener.ping(); + } } impl Drop for Window { fn drop(&mut self) { - let close_request = WindowRequest::Close; - self.window_requests.lock().unwrap().push(close_request); - self.event_loop_awakener.ping(); + self.send_request(WindowRequest::Close); } } diff --git a/src/platform_impl/linux/wayland/window/shim.rs b/src/platform_impl/linux/wayland/window/shim.rs index a75b1dba..7c76a2fa 100644 --- a/src/platform_impl/linux/wayland/window/shim.rs +++ b/src/platform_impl/linux/wayland/window/shim.rs @@ -2,17 +2,23 @@ use std::cell::Cell; use std::sync::{Arc, Mutex}; use sctk::reexports::client::protocol::wl_output::WlOutput; +use sctk::reexports::client::Attached; +use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_token_v1; +use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1; +use sctk::environment::Environment; use sctk::window::{Decorations, FallbackFrame, Window}; use crate::dpi::{LogicalPosition, LogicalSize}; use crate::event::WindowEvent; +use crate::platform_impl::wayland; +use crate::platform_impl::wayland::env::WinitEnv; use crate::platform_impl::wayland::event_loop::WinitState; use crate::platform_impl::wayland::seat::pointer::WinitPointer; use crate::platform_impl::wayland::seat::text_input::TextInputHandler; use crate::platform_impl::wayland::WindowId; -use crate::window::CursorIcon; +use crate::window::{CursorIcon, UserAttentionType}; /// A request to SCTK window from Winit window. #[derive(Debug, Clone)] @@ -64,6 +70,11 @@ pub enum WindowRequest { /// Set IME window position. IMEPosition(LogicalPosition), + /// Request Attention. + /// + /// `None` unsets the attention request. + Attention(Option), + /// Redraw was requested. Redraw, @@ -150,14 +161,23 @@ pub struct WindowHandle { /// Text inputs on the current surface. text_inputs: Vec, + + /// XdgActivation object. + xdg_activation: Option>, + + /// Indicator whether user attention is requested. + attention_requested: Cell, } impl WindowHandle { pub fn new( + env: &Environment, window: Window, size: Arc>>, pending_window_requests: Arc>>, ) -> Self { + let xdg_activation = env.get_global::(); + Self { window, size, @@ -167,6 +187,8 @@ impl WindowHandle { cursor_visible: Cell::new(true), pointers: Vec::new(), text_inputs: Vec::new(), + xdg_activation, + attention_requested: Cell::new(false), } } @@ -188,6 +210,48 @@ impl WindowHandle { } } + pub fn set_user_attention(&self, request_type: Option) { + let xdg_activation = match self.xdg_activation.as_ref() { + None => return, + Some(xdg_activation) => xdg_activation, + }; + + // Urgency is only removed by the compositor and there's no need to raise urgency when it + // was already raised. + if request_type.is_none() || self.attention_requested.get() { + return; + } + + let xdg_activation_token = xdg_activation.get_activation_token(); + let surface = self.window.surface(); + let window_id = wayland::make_wid(surface); + let xdg_activation = xdg_activation.clone(); + + xdg_activation_token.quick_assign(move |xdg_token, event, mut dispatch_data| { + let token = match event { + xdg_activation_token_v1::Event::Done { token } => token, + _ => return, + }; + + let winit_state = dispatch_data.get::().unwrap(); + let window_handle = match winit_state.window_map.get_mut(&window_id) { + Some(window_handle) => window_handle, + None => return, + }; + + let surface = window_handle.window.surface(); + xdg_activation.activate(token, surface); + + // Mark that attention request was done and drop the token. + window_handle.attention_requested.replace(false); + xdg_token.destroy(); + }); + + xdg_activation_token.set_surface(surface); + xdg_activation_token.commit(); + self.attention_requested.replace(true); + } + /// Pointer appeared over the window. pub fn pointer_entered(&mut self, pointer: WinitPointer) { let position = self.pointers.iter().position(|p| *p == pointer); @@ -361,6 +425,9 @@ pub fn handle_window_requests(winit_state: &mut WinitState) { let window_update = window_updates.get_mut(window_id).unwrap(); window_update.refresh_frame = true; } + WindowRequest::Attention(request_type) => { + window_handle.set_user_attention(request_type); + } WindowRequest::Redraw => { let window_update = window_updates.get_mut(window_id).unwrap(); window_update.redraw_requested = true; diff --git a/src/window.rs b/src/window.rs index 9d326ce1..3edfab63 100644 --- a/src/window.rs +++ b/src/window.rs @@ -755,9 +755,10 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web / Wayland:** Unsupported. + /// - **iOS / Android / Web :** Unsupported. /// - **macOS:** `None` has no effect. /// - **X11:** Requests for user attention must be manually cleared. + /// - **Wayland:** Requires `xdg_activation_v1` protocol, `None` has no effect. #[inline] pub fn request_user_attention(&self, request_type: Option) { self.window.request_user_attention(request_type)