From 61f8cd7836a16af2f3cc2db235a6e683c97b9e04 Mon Sep 17 00:00:00 2001 From: PolyMeilex Date: Thu, 28 Nov 2024 23:56:24 +0100 Subject: [PATCH] WIP: XdgSessionManager --- Cargo.toml | 1 + examples/window.rs | 6 + src/platform/wayland.rs | 8 + src/platform_impl/linux/mod.rs | 11 +- .../linux/wayland/event_loop/mod.rs | 5 +- src/platform_impl/linux/wayland/mod.rs | 21 ++ .../linux/wayland/session-management-v1.xml | 264 ++++++++++++++++++ src/platform_impl/linux/wayland/state.rs | 5 + src/platform_impl/linux/wayland/types/mod.rs | 1 + .../wayland/types/xdg_session_management.rs | 112 ++++++++ src/platform_impl/linux/wayland/window/mod.rs | 10 + 11 files changed, 438 insertions(+), 6 deletions(-) create mode 100644 src/platform_impl/linux/wayland/session-management-v1.xml create mode 100644 src/platform_impl/linux/wayland/types/xdg_session_management.rs diff --git a/Cargo.toml b/Cargo.toml index bbd6f8d1a1..f2f9db93a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -254,6 +254,7 @@ wayland-backend = { version = "0.3.5", default-features = false, features = [ ], optional = true } wayland-client = { version = "0.31.4", optional = true } wayland-protocols = { version = "0.32.2", features = ["staging"], optional = true } +wayland-scanner = { version = "0.31.5" } wayland-protocols-plasma = { version = "0.3.2", features = ["client"], optional = true } x11-dl = { version = "2.19.1", optional = true } x11rb = { version = "0.13.0", default-features = false, features = [ diff --git a/examples/window.rs b/examples/window.rs index 3d87e2f8d9..e744b4263b 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -151,6 +151,12 @@ impl Application { window_attributes = window_attributes.with_activation_token(token); } + #[cfg(wayland_platform)] + { + window_attributes = + window_attributes.with_name("io.github.rust-windowing.winit", "main-window"); + } + #[cfg(x11_platform)] match std::env::var("X11_VISUAL_ID") { Ok(visual_id_str) => { diff --git a/src/platform/wayland.rs b/src/platform/wayland.rs index 84af00388b..9d180b3336 100644 --- a/src/platform/wayland.rs +++ b/src/platform/wayland.rs @@ -54,6 +54,8 @@ pub trait EventLoopBuilderExtWayland { /// By default, the window is only allowed to be created on the main /// thread, to make platform compatibility easier. fn with_any_thread(&mut self, any_thread: bool) -> &mut Self; + + fn with_session_id(&mut self, session_id: String) -> &mut Self; } impl EventLoopBuilderExtWayland for EventLoopBuilder { @@ -68,6 +70,12 @@ impl EventLoopBuilderExtWayland for EventLoopBuilder { self.platform_specific.any_thread = any_thread; self } + + #[inline] + fn with_session_id(&mut self, session_id: String) -> &mut Self { + self.platform_specific.session_id = Some(session_id); + self + } } /// Additional methods on [`Window`] that are specific to Wayland. diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 55ba7e2e02..86acce5462 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -45,10 +45,11 @@ pub(crate) enum Backend { Wayland, } -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] pub(crate) struct PlatformSpecificEventLoopAttributes { pub(crate) forced_backend: Option, pub(crate) any_thread: bool, + pub(crate) session_id: Option, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -329,15 +330,17 @@ impl EventLoop { // Create the display based on the backend. match backend { #[cfg(wayland_platform)] - Backend::Wayland => EventLoop::new_wayland_any_thread().map_err(Into::into), + Backend::Wayland => { + EventLoop::new_wayland_any_thread(attributes.session_id.clone()).map_err(Into::into) + }, #[cfg(x11_platform)] Backend::X => EventLoop::new_x11_any_thread().map_err(Into::into), } } #[cfg(wayland_platform)] - fn new_wayland_any_thread() -> Result { - wayland::EventLoop::new().map(|evlp| EventLoop::Wayland(Box::new(evlp))) + fn new_wayland_any_thread(session_id: Option) -> Result { + wayland::EventLoop::new(session_id).map(|evlp| EventLoop::Wayland(Box::new(evlp))) } #[cfg(x11_platform)] diff --git a/src/platform_impl/linux/wayland/event_loop/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs index 0ce019f6b1..abb1225182 100644 --- a/src/platform_impl/linux/wayland/event_loop/mod.rs +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -63,7 +63,7 @@ pub struct EventLoop { } impl EventLoop { - pub fn new() -> Result { + pub fn new(session_id: Option) -> Result { let connection = Connection::connect_to_env().map_err(|err| os_error!(err))?; let (globals, mut event_queue) = @@ -73,7 +73,8 @@ impl EventLoop { let event_loop = calloop::EventLoop::::try_new().map_err(|err| os_error!(err))?; - let mut winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle())?; + let mut winit_state = + WinitState::new(&globals, &queue_handle, event_loop.handle(), session_id)?; // NOTE: do a roundtrip after binding the globals to prevent potential // races with the server. diff --git a/src/platform_impl/linux/wayland/mod.rs b/src/platform_impl/linux/wayland/mod.rs index 91aad463c7..f510570fa5 100644 --- a/src/platform_impl/linux/wayland/mod.rs +++ b/src/platform_impl/linux/wayland/mod.rs @@ -29,3 +29,24 @@ fn logical_to_physical_rounded(size: LogicalSize, scale_factor: f64) -> Phy let height = size.height as f64 * scale_factor; (width.round(), height.round()).into() } + +mod xdg_session_management { + #![allow(dead_code, non_camel_case_types, unused_unsafe, unused_variables)] + #![allow(non_upper_case_globals, non_snake_case, unused_imports)] + #![allow(missing_docs, clippy::all)] + + use wayland_client; + use wayland_protocols::xdg::shell::client::*; + + pub mod __interfaces { + use wayland_protocols::xdg::shell::client::__interfaces::*; + wayland_scanner::generate_interfaces!( + "./src/platform_impl/linux/wayland/session-management-v1.xml" + ); + } + use self::__interfaces::*; + + wayland_scanner::generate_client_code!( + "./src/platform_impl/linux/wayland/session-management-v1.xml" + ); +} diff --git a/src/platform_impl/linux/wayland/session-management-v1.xml b/src/platform_impl/linux/wayland/session-management-v1.xml new file mode 100644 index 0000000000..e21694424f --- /dev/null +++ b/src/platform_impl/linux/wayland/session-management-v1.xml @@ -0,0 +1,264 @@ + + + + Copyright 2018 Mike Blumenkrantz + Copyright 2018 Samsung Electronics Co., Ltd + Copyright 2018 Red Hat Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This description provides a high-level overview of the interplay between + the interfaces defined this protocol. For details, see the protocol + specification. + + The xx_session_manager protocol declares interfaces necessary to + allow clients to restore toplevel state from previous executions. The + xx_session_manager_v1.get_session request can be used to obtain a + xx_session_v1 resource representing the state of a set of toplevels. + + Clients may obtain the session string to use in future calls through + the xx_session_v1.created event. Compositors will use this string + as an identifiable token for future runs, possibly storing data about + the related toplevels in persistent storage. + + Toplevels are managed through the xx_session_v1.add_toplevel and + xx_session_toplevel_v1.remove pair of requests. Clients will explicitly + request a toplevel to be restored according to prior state through the + xx_session_v1.restore_toplevel request before the toplevel is mapped. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + The xx_session_manager interface defines base requests for creating and + managing a session for an application. Sessions persist across application + and compositor restarts unless explicitly destroyed. A session is created + for the purpose of maintaining an application's xdg_toplevel surfaces + across compositor or application restarts. The compositor should remember + as many states as possible for surfaces in a given session, but there is + no requirement for which states must be remembered. + + + + + + + + + The reason may determine in what way a session restores the window + management state of associated toplevels. + + For example newly launched applications might be launched on the active + workspace with restored size and position, while a recovered + applications might restore additional state such as active workspace and + stacking order. + + + + A new app instance is launched, for example from an app launcher. + + + + + A app instance is recovering from for example a compositor or app crash. + + + + + A app instance is restored, for example part of a restored session, or + restored from having been temporarily terminated due to resource + constraints. + + + + + + + This has no effect other than to destroy the xx_session_manager object. + + + + + + Create a session object corresponding to either an existing session + identified by the given session identifier string or a new session. + While the session object exists, the session is considered to be "in + use". + + If a identifier string represents a session that is currently actively + in use by the the same client, an 'in_use' error is raised. If some + other client is currently using the same session, the new session will + replace managing the associated state. + + NULL is passed to initiate a new session. If an id is passed which does + not represent a valid session, the compositor treats it as if NULL had + been passed. + + A client is allowed to have any number of in use sessions at the same + time. + + + + + + + + + + A xx_session_v1 object represents a session for an application. While the + object exists, all surfaces which have been added to the session will + have states stored by the compositor which can be reapplied at a later + time. Two sessions cannot exist for the same identifier string. + + States for surfaces added to a session are automatically updated by the + compositor when they are changed. + + Surfaces which have been added to a session are automatically removed from + the session if xdg_toplevel.destroy is called for the surface. + + + + + + + + + + + Destroy a session object, preserving the current state but not continuing + to make further updates if state changes occur. This makes the associated + xx_toplevel_session_v1 objects inert. + + + + + + Remove the session, making it no longer available for restoration. A + compositor should in response to this request remove the data related to + this session from its storage. + + + + + + Attempt to add a given surface to the session. The passed name is used + to identify what window is being restored, and may be used store window + specific state within the session. + + Calling this with a toplevel that is already managed by the session with + the same associated will raise an in_use error. + + + + + + + + + Inform the compositor that the toplevel associated with the passed name + should have its window management state restored. + + Calling this with a toplevel that is already managed by the session with + the same associated will raise an in_use error. + + This request must be called prior to the first commit on the associated + wl_surface, otherwise an already_mapped error is raised. + + As part of the initial configure sequence, if the toplevel was + successfully restored, a xx_toplevel_session_v1.restored event is + emitted. See the xx_toplevel_session_v1.restored event for further + details. + + + + + + + + + Emitted at most once some time after getting a new session object. It + means that no previous state was restored, and a new session was created. + The passed id can be used to restore previous sessions. + + + + + + + Emitted at most once some time after getting a new session object. It + means that previous state was at least partially restored. The same id + can again be used to restore previous sessions. + + + + + + Emitted at most once, if the session was taken over by some other + client. When this happens, the session and all its toplevel session + objects become inert, and should be destroyed. + + + + + + + + Destroy the object. This has no effect window management of the + associated toplevel. + + + + + + Remove a specified surface from the session and render any corresponding + xx_toplevel_session_v1 object inert. The compositor should remove any + data related to the toplevel in the corresponding session from its internal + storage. + + + + + + The "restored" event is emitted prior to the first + xdg_toplevel.configure for the toplevel. It will only be emitted after + xx_session_v1.restore_toplevel, and the initial empty surface state has + been applied, and it indicates that the surface's session is being + restored with this configure event. + + + + + diff --git a/src/platform_impl/linux/wayland/state.rs b/src/platform_impl/linux/wayland/state.rs index bec0a55d3a..7a5ddb78ad 100644 --- a/src/platform_impl/linux/wayland/state.rs +++ b/src/platform_impl/linux/wayland/state.rs @@ -35,6 +35,8 @@ use crate::platform_impl::wayland::types::xdg_activation::XdgActivationState; use crate::platform_impl::wayland::window::{WindowRequests, WindowState}; use crate::platform_impl::wayland::WindowId; +use super::types::xdg_session_management::XdgSessionManager; + /// Winit's Wayland state. pub struct WinitState { /// The WlRegistry. @@ -106,6 +108,7 @@ pub struct WinitState { /// KWin blur manager. pub kwin_blur_manager: Option, + pub xdg_session_manager: Option, /// Loop handle to re-register event sources, such as keyboard repeat. pub loop_handle: LoopHandle<'static, Self>, @@ -123,6 +126,7 @@ impl WinitState { globals: &GlobalList, queue_handle: &QueueHandle, loop_handle: LoopHandle<'static, WinitState>, + session_id: Option, ) -> Result { let registry_state = RegistryState::new(globals); let compositor_state = @@ -178,6 +182,7 @@ impl WinitState { viewporter_state, fractional_scaling_manager, kwin_blur_manager: KWinBlurManager::new(globals, queue_handle).ok(), + xdg_session_manager: XdgSessionManager::new(globals, queue_handle, session_id).ok(), seats, text_input_state: TextInputState::new(globals, queue_handle).ok(), diff --git a/src/platform_impl/linux/wayland/types/mod.rs b/src/platform_impl/linux/wayland/types/mod.rs index 77e67f48be..491fd1b6ed 100644 --- a/src/platform_impl/linux/wayland/types/mod.rs +++ b/src/platform_impl/linux/wayland/types/mod.rs @@ -5,3 +5,4 @@ pub mod kwin_blur; pub mod wp_fractional_scaling; pub mod wp_viewporter; pub mod xdg_activation; +pub mod xdg_session_management; diff --git a/src/platform_impl/linux/wayland/types/xdg_session_management.rs b/src/platform_impl/linux/wayland/types/xdg_session_management.rs new file mode 100644 index 0000000000..9070d2ae58 --- /dev/null +++ b/src/platform_impl/linux/wayland/types/xdg_session_management.rs @@ -0,0 +1,112 @@ +use std::collections::HashSet; + +use sctk::globals::GlobalData; +use sctk::reexports::client::globals::{BindError, GlobalList}; +use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle}; +use wayland_protocols::xdg::shell::client::xdg_toplevel::XdgToplevel; + +use super::super::xdg_session_management::{ + xx_session_manager_v1::{Reason, XxSessionManagerV1}, + xx_session_v1::XxSessionV1, + xx_toplevel_session_v1::XxToplevelSessionV1, +}; + +use crate::platform_impl::wayland::state::WinitState; + +#[derive(Debug, Clone)] +pub struct XdgSessionManager { + session: XxSessionV1, + names_in_use: HashSet, +} + +impl XdgSessionManager { + pub fn new( + globals: &GlobalList, + queue_handle: &QueueHandle, + session_id: Option, + ) -> Result { + let session_id = session_id.or_else(|| std::env::var("REMOVE_THIS").ok()); + + let manager: XxSessionManagerV1 = globals.bind(queue_handle, 1..=1, GlobalData)?; + let default = manager.get_session(Reason::Launch, session_id, queue_handle, ()); + + Ok(Self { session: default, names_in_use: HashSet::new() }) + } + + pub fn add_toplevel( + &mut self, + queue_handle: &QueueHandle, + toplevel: &XdgToplevel, + name: impl Into, + ) { + let name = name.into(); + if self.names_in_use.insert(name.clone()) { + self.session.add_toplevel(toplevel, name, queue_handle, ()); + } + } + + pub fn restore_toplevel( + &mut self, + queue_handle: &QueueHandle, + toplevel: &XdgToplevel, + name: impl Into, + ) { + let name = name.into(); + if self.names_in_use.insert(name.clone()) { + self.session.restore_toplevel(toplevel, name, queue_handle, ()); + } + } + + pub fn remove_toplevel( + &mut self, + _queue_handle: &QueueHandle, + _toplevel: &XdgToplevel, + name: impl Into, + ) { + let name = name.into(); + self.names_in_use.remove(&name); + // self.default.remove_toplevel(toplevel, name.into(), queue_handle, ()); + } +} + +impl Dispatch for XdgSessionManager { + fn event( + _: &mut WinitState, + _: &XxSessionManagerV1, + _: ::Event, + _: &GlobalData, + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for XdgSessionManager { + fn event( + _: &mut WinitState, + _: &XxSessionV1, + event: ::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + dbg!(event); + // TODO: Pass session_id to the user + } +} + +impl Dispatch for XdgSessionManager { + fn event( + _: &mut WinitState, + _: &XxToplevelSessionV1, + _: ::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +delegate_dispatch!(WinitState: [XxSessionManagerV1: GlobalData] => XdgSessionManager); +delegate_dispatch!(WinitState: [XxSessionV1: ()] => XdgSessionManager); +delegate_dispatch!(WinitState: [XxToplevelSessionV1: ()] => XdgSessionManager); diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index cb7317737a..7cc0dc0e67 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -102,6 +102,16 @@ impl Window { let window = state.xdg_shell.create_window(surface.clone(), default_decorations, &queue_handle); + if let Some(session) = state.xdg_session_manager.as_mut() { + if let Some(name) = attributes.platform_specific.name.as_ref() { + session.restore_toplevel( + &queue_handle, + window.xdg_toplevel(), + format!("{}-{}", name.general, name.instance), + ); + } + } + let mut window_state = WindowState::new( event_loop_window_target.handle.clone(), &event_loop_window_target.queue_handle,