diff --git a/Cargo.lock b/Cargo.lock index 99a2064e..7b6b2dae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -429,6 +429,7 @@ dependencies = [ "png", "puffin", "puffin_egui", + "rand", "regex", "renderdoc", "ron 0.7.1", diff --git a/Cargo.toml b/Cargo.toml index db68d7e1..b3438f44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ tracing-journald = "0.3.0" tracing = { version = "0.1.37", features = ["max_level_debug", "release_max_level_info"] } puffin = { version = "0.14.3", optional = true } puffin_egui = { version = "0.19.2", optional = true } +rand = "0.8.5" [dependencies.smithay] version = "0.3" diff --git a/resources/protocols/ext-foreign-toplevel-list-v1.xml b/resources/protocols/ext-foreign-toplevel-list-v1.xml new file mode 100644 index 00000000..8152e516 --- /dev/null +++ b/resources/protocols/ext-foreign-toplevel-list-v1.xml @@ -0,0 +1,229 @@ + + + + Copyright © 2018 Ilia Bozhinov + Copyright © 2020 Isaac Freund + Copyright © 2022 wb9688 + Copyright © 2023 i509VCB + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + The purpose of this protocol is to provide protocol object handles for + toplevels, possibly originating from another client. + + This protocol is intentionally minimalistic and expects additional + functionality (e.g. creating a screencopy source from a toplevel handle, + getting information about the state of the toplevel) to be implemented + in extension protocols. + + The compositor may choose to restrict this protocol to a special client + launched by the compositor itself or expose it to all clients, + this is compositor policy. + + The key words "must", "must not", "required", "shall", "shall not", + "should", "should not", "recommended", "may", and "optional" in this + document are to be interpreted as described in IETF RFC 2119. + + 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. + + + + + A toplevel is defined as a surface with a role similar to xdg_toplevel. + XWayland surfaces may be treated like toplevels in this protocol. + + After a client binds the ext_foreign_toplevel_list_v1, each mapped + toplevel window will be sent using the ext_foreign_toplevel_list_v1.toplevel + event. + + Clients which only care about the current state can perform a roundtrip after + binding this global. + + For each client instantiation of the ext_foreign_toplevel_list_v1 global, + the compositor must create a new ext_foreign_toplevel_handle_v1 object for + each mapped toplevel for that instance of the ext_foreign_toplevel_list_v1. + Compositors must not send existing ext_foreign_toplevel_handle_v1 objects + that were created from some other global instance. + + If a compositor implementation sends the ext_foreign_toplevel_list_v1.finished + event after the global is bound, the compositor must not send any + ext_foreign_toplevel_list_v1.toplevel events. + + + + + These errors are sent in response to ext_foreign_toplevel_list_v1 requests. + + + + + + + + This event is emitted whenever a new toplevel window is created. It is + emitted for all toplevels, regardless of the app that has created them. + + All initial properties of the toplevel (title, app_id) will be sent + immediately after this event via the corresponding events in + ext_foreign_toplevel_handle_v1. The compositor will use the + ext_foreign_toplevel_handle_v1.done event to indicate when all data has + been sent. + + + + + + + This event indicates that the compositor is done sending events + to this object. The client should should destroy the object. + See ext_foreign_toplevel_list_v1.destroy for more information. + + The compositor must not send any more toplevel events after this event. + + + + + + This request indicates that the client no longer wishes to receive + events for new toplevels. + + The Wayland protocol is asynchronous, meaning the compositor may send + further toplevel events until the stop request is processed. + The client should wait for a ext_foreign_toplevel_list_v1.finished + event before destroying this object. + + + + + + This request should be called either when the client will no longer + use the ext_foreign_toplevel_list_v1 or after the finished event + has been received to allow destruction of the object. + + Destroying a ext_foreign_toplevel_list_v1 while there are toplevels still + alive created by this ext_foreign_toplevel_list_v1 object is illegal and + must result in a defunct_toplevels error. If a client wishes to destroy + this object it should send a ext_foreign_toplevel_list_v1.stop request + and wait for a ext_foreign_toplevel_list_v1.finished event, then destroy + the handles and then this object. + + + + + + + A ext_foreign_toplevel_handle_v1 object represents a mapped toplevel + window. A single app may have multiple mapped toplevels. + + + + + This request should be used when the client will no longer use the handle + or after the closed event has been received to allow destruction of the + object. + + When a handle is destroyed, a new handle may not be created by the server + until the toplevel is unmapped and then remapped. Destroying a toplevel handle + is not recommended unless the client is cleaning up child objects + before destroying the ext_foreign_toplevel_list_v1 object, the toplevel + was closed or the toplevel will not be used in the future. + + Other protocols which extend the ext_foreign_toplevel_handle_v1 + interface should require destructors are called before allowing the + toplevel handle to be destroyed. + + + + + + The server will emit no further events on the ext_foreign_toplevel_handle_v1 + after this event. Any requests received aside from the destroy request must + be ignored. Upon receiving this event, the client should destroy the handle. + + Other protocols which extend the ext_foreign_toplevel_handle_v1 + interface must also ignore requests other than destructors. + + + + + + This event is sent after all changes in the toplevel state have + been sent. + + This allows changes to the ext_foreign_toplevel_handle_v1 properties + to be atomically applied. Other protocols which extend the + ext_foreign_toplevel_handle_v1 interface may use this event to also + atomically apply any pending state. + + This event must not be sent after the ext_foreign_toplevel_handle_v1.closed + event. + + + + + + The title of the toplevel has changed. + + The configured state must not be applied immediately. See + ext_foreign_toplevel_handle_v1.done for details. + + + + + + + The app id of the toplevel has changed. + + The configured state must not be applied immediately. See + ext_foreign_toplevel_handle_v1.done for details. + + + + + + + This identifier is used to check if two or more toplevel handles belong + to the same toplevel. + + The identifier is useful for command line tools or privileged clients + which may need to reference an exact toplevel across processes or + instances of the ext_foreign_toplevel_list_v1 global. + + The compositor must only send this event when the handle is created. + + Compositors must ensure the identifier is the same for all handles of the + toplevel. The identifier is only valid as long as the toplevel is mapped. + If the toplevel is unmapped and then remapped in the future, the identifier + must change. + + The identifier is a string that contains up to 32 printable bytes. + + + + + diff --git a/src/shell/layout/floating/grabs/moving.rs b/src/shell/layout/floating/grabs/moving.rs index 047150bb..da21c43f 100644 --- a/src/shell/layout/floating/grabs/moving.rs +++ b/src/shell/layout/floating/grabs/moving.rs @@ -239,6 +239,16 @@ impl MoveSurfaceGrab { .common .shell .toplevel_info_state + .toplevel_enter_workspace(&window, &workspace_handle); + state + .common + .shell + .toplevel_info_state + .toplevel_enter_output(&window, &output); + state + .common + .shell + .foreign_toplevel_info_state .toplevel_enter_output(&window, &output); } diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 2c3499ba..cfd1442c 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -21,7 +21,7 @@ use crate::{ }, state::State, utils::prelude::*, - wayland::protocols::toplevel_info::ToplevelInfoState, + wayland::protocols::{ext_foreign_toplevel_list, toplevel_info::ToplevelInfoState}, }; mod grabs; @@ -45,6 +45,10 @@ impl FloatingLayout { &mut self, output: &Output, toplevel_info: &mut ToplevelInfoState, + foreign_toplevel_info: &mut ext_foreign_toplevel_list::ToplevelInfoState< + State, + CosmicSurface, + >, ) { let windows = self .space @@ -54,6 +58,7 @@ impl FloatingLayout { for window in &windows { for (toplevel, _) in window.windows() { toplevel_info.toplevel_leave_output(&toplevel, output); + foreign_toplevel_info.toplevel_leave_output(&toplevel, output); } } self.space.unmap_output(output); @@ -62,6 +67,7 @@ impl FloatingLayout { for output in self.space.outputs_for_element(&window) { for (toplevel, _) in window.windows() { toplevel_info.toplevel_enter_output(&toplevel, &output); + foreign_toplevel_info.toplevel_enter_output(&toplevel, &output); } } } diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index f157c310..61776889 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -14,7 +14,8 @@ use crate::{ }, utils::prelude::*, wayland::{ - handlers::xdg_shell::popup::get_popup_toplevel, protocols::toplevel_info::ToplevelInfoState, + handlers::xdg_shell::popup::get_popup_toplevel, + protocols::{ext_foreign_toplevel_list, toplevel_info::ToplevelInfoState}, }, }; @@ -282,6 +283,10 @@ impl TilingLayout { &mut self, output: &Output, toplevel_info: &mut ToplevelInfoState, + foreign_toplevel_info: &mut ext_foreign_toplevel_list::ToplevelInfoState< + State, + CosmicSurface, + >, ) { if let Some(src) = self.trees.remove(output) { // TODO: expects last remaining output @@ -304,6 +309,8 @@ impl TilingLayout { for (toplevel, _) in mapped.windows() { toplevel_info.toplevel_leave_output(&toplevel, output); toplevel_info.toplevel_enter_output(&toplevel, &new_output.output); + foreign_toplevel_info.toplevel_leave_output(&toplevel, &output); + foreign_toplevel_info.toplevel_enter_output(&toplevel, &new_output.output); } mapped.output_leave(output); mapped.output_enter(&new_output.output, mapped.bbox()); diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 0ff4b8e6..9107c31d 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -30,12 +30,15 @@ use smithay::{ use crate::{ config::{Config, OutputConfig, WorkspaceMode as ConfigMode}, utils::prelude::*, - wayland::protocols::{ - toplevel_info::ToplevelInfoState, - toplevel_management::{ManagementCapabilities, ToplevelManagementState}, - workspace::{ - WorkspaceCapabilities, WorkspaceGroupHandle, WorkspaceHandle, WorkspaceState, - WorkspaceUpdateGuard, + wayland::{ + protocols::ext_foreign_toplevel_list, + protocols::{ + toplevel_info::ToplevelInfoState, + toplevel_management::{ManagementCapabilities, ToplevelManagementState}, + workspace::{ + WorkspaceCapabilities, WorkspaceGroupHandle, WorkspaceHandle, WorkspaceState, + WorkspaceUpdateGuard, + }, }, }, }; @@ -66,6 +69,8 @@ pub struct Shell { // wayland_state pub layer_shell_state: WlrLayerShellState, pub toplevel_info_state: ToplevelInfoState, + pub foreign_toplevel_info_state: + ext_foreign_toplevel_list::ToplevelInfoState, pub toplevel_management_state: ToplevelManagementState, pub xdg_shell_state: XdgShellState, pub workspace_state: WorkspaceState, @@ -456,6 +461,13 @@ impl Shell { //|client| client.get_data::().map_or(false, |s| s.privileged), |_| true, ); + + let foreign_toplevel_info_state = ext_foreign_toplevel_list::ToplevelInfoState::new( + dh, + //|client| client.get_data::().map_or(false, |s| s.privileged), + |_| true, + ); + let toplevel_management_state = ToplevelManagementState::new::( dh, vec![ @@ -492,6 +504,7 @@ impl Shell { layer_shell_state, toplevel_info_state, + foreign_toplevel_info_state, toplevel_management_state, xdg_shell_state, workspace_state, @@ -593,7 +606,11 @@ impl Shell { // update mapping workspace.map_output(new_output, (0, 0).into()); - workspace.unmap_output(output, &mut self.toplevel_info_state); + workspace.unmap_output( + output, + &mut self.toplevel_info_state, + &mut self.foreign_toplevel_info_state, + ); workspace.refresh(); new_set.workspaces.push(workspace); @@ -611,7 +628,11 @@ impl Shell { WorkspaceMode::Global(set) => { state.remove_group_output(&set.group, output); for workspace in &mut set.workspaces { - workspace.unmap_output(output, &mut self.toplevel_info_state); + workspace.unmap_output( + output, + &mut self.toplevel_info_state, + &mut self.foreign_toplevel_info_state, + ); workspace.refresh(); } } @@ -731,6 +752,10 @@ impl Shell { .toplevel_leave_workspace(&toplevel, &workspace.handle); self.toplevel_info_state .toplevel_enter_workspace(&toplevel, &new_workspace.handle); + self.foreign_toplevel_info_state + .toplevel_leave_workspace(&toplevel, &workspace.handle); + self.foreign_toplevel_info_state + .toplevel_enter_workspace(&toplevel, &new_workspace.handle); } new_workspace.tiling_layer.merge(workspace.tiling_layer); new_workspace.floating_layer.merge(workspace.floating_layer); @@ -1071,6 +1096,10 @@ impl Shell { self.toplevel_info_state .refresh(Some(&self.workspace_state)); + self.foreign_toplevel_info_state + .refresh(Some(&self.workspace_state)); + self.foreign_toplevel_info_state + .refresh(Some(&self.workspace_state)); } pub fn map_window(state: &mut State, window: &CosmicSurface, output: &Output) { @@ -1086,6 +1115,11 @@ impl Shell { let workspace = state.common.shell.workspaces.active_mut(output); workspace.set_fullscreen(None, output); state.common.shell.toplevel_info_state.new_toplevel(&window); + state + .common + .shell + .foreign_toplevel_info_state + .new_toplevel(&window); state .common .shell @@ -1096,6 +1130,16 @@ impl Shell { .shell .toplevel_info_state .toplevel_enter_workspace(&window, &workspace.handle); + state + .common + .shell + .foreign_toplevel_info_state + .toplevel_enter_output(&window, &output); + state + .common + .shell + .foreign_toplevel_info_state + .toplevel_enter_workspace(&window, &workspace.handle); let mapped = CosmicMapped::from(CosmicWindow::new( window.clone(), @@ -1213,12 +1257,22 @@ impl Shell { .shell .toplevel_info_state .toplevel_leave_workspace(&toplevel, &from_workspace.handle); + state + .common + .shell + .foreign_toplevel_info_state + .toplevel_leave_workspace(&toplevel, &from_workspace.handle); if from_output != to_output { state .common .shell .toplevel_info_state .toplevel_leave_output(&toplevel, from_output); + state + .common + .shell + .foreign_toplevel_info_state + .toplevel_leave_output(&toplevel, from_output); } } let elements = from_workspace.mapped().cloned().collect::>(); @@ -1325,6 +1379,16 @@ impl Shell { .shell .toplevel_info_state .toplevel_leave_output(&window, &output); + state + .common + .shell + .foreign_toplevel_info_state + .toplevel_leave_workspace(&window, &handle); + state + .common + .shell + .foreign_toplevel_info_state + .toplevel_leave_output(&window, &output); seat.get_pointer().unwrap().set_grab( state, grab, diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index d48d38ec..630a2d93 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -9,6 +9,7 @@ use crate::{ wayland::{ handlers::screencopy::{DropableSession, WORKSPACE_OVERVIEW_NAMESPACE}, protocols::{ + ext_foreign_toplevel_list, screencopy::{BufferParams, Session as ScreencopySession}, toplevel_info::ToplevelInfoState, workspace::WorkspaceHandle, @@ -105,12 +106,18 @@ impl Workspace { &mut self, output: &Output, toplevel_info: &mut ToplevelInfoState, + foreign_toplevel_info: &mut ext_foreign_toplevel_list::ToplevelInfoState< + State, + CosmicSurface, + >, ) { if let Some(dead_output_window) = self.fullscreen.remove(output) { self.unfullscreen_request(&dead_output_window); } - self.tiling_layer.unmap_output(output, toplevel_info); - self.floating_layer.unmap_output(output, toplevel_info); + self.tiling_layer + .unmap_output(output, toplevel_info, foreign_toplevel_info); + self.floating_layer + .unmap_output(output, toplevel_info, foreign_toplevel_info); self.refresh(); } diff --git a/src/wayland/handlers/ext_foreign_toplevel_info.rs b/src/wayland/handlers/ext_foreign_toplevel_info.rs new file mode 100644 index 00000000..f9fe299f --- /dev/null +++ b/src/wayland/handlers/ext_foreign_toplevel_info.rs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use smithay::utils::user_data::UserDataMap; + +use crate::{ + shell::CosmicSurface, + state::State, + wayland::protocols::ext_foreign_toplevel_list::{ + delegate_foreign_toplevel_info, ToplevelInfoHandler, ToplevelInfoState, Window, + }, +}; + +impl ToplevelInfoHandler for State { + type Window = CosmicSurface; + + fn toplevel_info_state(&self) -> &ToplevelInfoState { + &self.common.shell.foreign_toplevel_info_state + } + fn toplevel_info_state_mut(&mut self) -> &mut ToplevelInfoState { + &mut self.common.shell.foreign_toplevel_info_state + } +} + +impl Window for CosmicSurface { + fn title(&self) -> String { + CosmicSurface::title(self) + } + + fn app_id(&self) -> String { + CosmicSurface::app_id(self) + } + + fn is_activated(&self) -> bool { + CosmicSurface::is_activated(self) + } + + fn is_maximized(&self) -> bool { + CosmicSurface::is_maximized(self) + } + + fn is_fullscreen(&self) -> bool { + CosmicSurface::is_fullscreen(self) + } + + fn is_minimized(&self) -> bool { + false // TODO + } + + fn user_data(&self) -> &UserDataMap { + CosmicSurface::user_data(self) + } +} + +delegate_foreign_toplevel_info!(State, CosmicSurface); diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index 56e4a9be..5eceb75f 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -5,6 +5,7 @@ pub mod compositor; pub mod data_device; pub mod decoration; pub mod dmabuf; +pub mod ext_foreign_toplevel_info; pub mod keyboard_shortcuts_inhibit; pub mod layer_shell; pub mod output; diff --git a/src/wayland/protocols/ext_foreign_toplevel_list.rs b/src/wayland/protocols/ext_foreign_toplevel_list.rs new file mode 100644 index 00000000..423ff412 --- /dev/null +++ b/src/wayland/protocols/ext_foreign_toplevel_list.rs @@ -0,0 +1,437 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use std::{collections::HashMap, sync::Mutex}; + +// Re-export only the actual code, and then only use this re-export +// The `generated` module below is just some boilerplate to properly isolate stuff +// and avoid exposing internal details. +// +// You can use all the types from my_protocol as if they went from `wayland_client::protocol`. +pub use generated::{ext_foreign_toplevel_handle_v1, ext_foreign_toplevel_list_v1}; + +mod generated { + use smithay::reexports::wayland_server; + + pub mod __interfaces { + use wayland_backend; + wayland_scanner::generate_interfaces!( + "resources/protocols/ext-foreign-toplevel-list-v1.xml" + ); + } + use self::__interfaces::*; + + wayland_scanner::generate_server_code!("resources/protocols/ext-foreign-toplevel-list-v1.xml"); +} + +use rand::distributions::{Alphanumeric, DistString}; +use smithay::{ + output::Output, + reexports::wayland_server::{ + protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, + New, Resource, + }, + utils::{user_data::UserDataMap, IsAlive, Logical, Rectangle}, +}; + +use wayland_backend::server::{ClientId, GlobalId, ObjectId}; + +use self::{ + ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, + generated::ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, +}; + +use super::workspace::{WorkspaceHandle, WorkspaceHandler, WorkspaceState}; + +pub trait Window: IsAlive + Clone + Send { + fn title(&self) -> String; + fn app_id(&self) -> String; + fn is_activated(&self) -> bool; + fn is_maximized(&self) -> bool; + fn is_fullscreen(&self) -> bool; + fn is_minimized(&self) -> bool; + fn user_data(&self) -> &UserDataMap; +} + +pub struct ToplevelInfoState { + dh: DisplayHandle, + pub(super) toplevels: Vec, + instances: Vec, + global: GlobalId, + _dispatch_data: std::marker::PhantomData, +} + +pub trait ToplevelInfoHandler: WorkspaceHandler + Sized { + type Window: Window; + fn toplevel_info_state(&self) -> &ToplevelInfoState; + fn toplevel_info_state_mut(&mut self) -> &mut ToplevelInfoState; +} + +pub struct ToplevelInfoGlobalData { + filter: Box Fn(&'a Client) -> bool + Send + Sync>, +} + +#[derive(Default)] +pub(super) struct ToplevelStateInner { + instances: Vec, + outputs: Vec, + workspaces: Vec, + pub(super) rectangles: HashMap)>, +} +pub(super) type ToplevelState = Mutex; + +pub struct ToplevelHandleStateInner { + info: ExtForeignToplevelListV1, + title: String, + app_id: String, + identifier: String, + pub(super) window: W, +} +pub type ToplevelHandleState = Mutex>; + +impl ToplevelHandleStateInner { + fn from_window(info: ExtForeignToplevelListV1, window: &W) -> ToplevelHandleState { + ToplevelHandleState::new(ToplevelHandleStateInner { + info, + title: String::new(), + app_id: String::new(), + identifier: Alphanumeric.sample_string(&mut rand::thread_rng(), 32), + window: window.clone(), + }) + } +} + +impl GlobalDispatch + for ToplevelInfoState +where + D: GlobalDispatch + + Dispatch + + Dispatch> + + ToplevelInfoHandler + + 'static, + W: Window + 'static, +{ + fn bind( + state: &mut D, + dh: &DisplayHandle, + _client: &Client, + resource: New, + _global_data: &ToplevelInfoGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + let instance = data_init.init(resource, ()); + for window in &state.toplevel_info_state().toplevels { + send_toplevel_to_client::(dh, Some(state.workspace_state()), &instance, window); + } + state.toplevel_info_state_mut().instances.push(instance); + } + + fn can_view(client: Client, global_data: &ToplevelInfoGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +impl Dispatch for ToplevelInfoState +where + D: GlobalDispatch + + Dispatch + + Dispatch> + + ToplevelInfoHandler + + 'static, + W: Window + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + obj: &ExtForeignToplevelListV1, + request: ext_foreign_toplevel_list_v1::Request, + _data: &(), + _dh: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + ext_foreign_toplevel_list_v1::Request::Stop => { + state + .toplevel_info_state_mut() + .instances + .retain(|i| i != obj); + + // cosmic has already removed the instance, this ensures that no further events are sent. + obj.finished(); + } + ext_foreign_toplevel_list_v1::Request::Destroy => { + // Toplevel handles must be destroyed before the global + for state in state + .toplevel_info_state_mut() + .toplevels + .iter() + .flat_map(|t| t.user_data().get::()) + { + let state = state.lock().unwrap(); + for handle in state.instances.iter() { + let state = handle + .data::>() + .unwrap() + .lock() + .unwrap(); + + if &state.info == obj { + obj.post_error( + ext_foreign_toplevel_list_v1::Error::DefunctToplevels, + "ext_foreign_toplevel_info_v1 was destroyed with defunct toplevel handles" + ); + } + } + } + } + _ => {} + } + } + + fn destroyed(state: &mut D, _client: ClientId, resource: ObjectId, _data: &()) { + state + .toplevel_info_state_mut() + .instances + .retain(|i| i.id() != resource); + } +} + +impl Dispatch, D> + for ToplevelInfoState +where + D: GlobalDispatch + + Dispatch + + Dispatch> + + ToplevelInfoHandler + + 'static, + W: Window, +{ + fn request( + _state: &mut D, + _client: &Client, + _obj: &ExtForeignToplevelHandleV1, + request: ext_foreign_toplevel_handle_v1::Request, + _data: &ToplevelHandleState, + _dh: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + ext_foreign_toplevel_handle_v1::Request::Destroy => {} + _ => {} + } + } + + fn destroyed( + state: &mut D, + _client: ClientId, + resource: ObjectId, + _data: &ToplevelHandleState, + ) { + for toplevel in &state.toplevel_info_state_mut().toplevels { + if let Some(state) = toplevel.user_data().get::() { + state + .lock() + .unwrap() + .instances + .retain(|i| i.id() != resource); + } + } + } +} + +impl ToplevelInfoState +where + D: GlobalDispatch + + Dispatch + + Dispatch> + + ToplevelInfoHandler + + 'static, + W: Window + 'static, +{ + pub fn new(dh: &DisplayHandle, client_filter: F) -> ToplevelInfoState + where + F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static, + { + let global = dh.create_global::( + 1, + ToplevelInfoGlobalData { + filter: Box::new(client_filter), + }, + ); + ToplevelInfoState { + dh: dh.clone(), + toplevels: Vec::new(), + instances: Vec::new(), + global, + _dispatch_data: std::marker::PhantomData, + } + } + + pub fn new_toplevel(&mut self, toplevel: &W) { + toplevel + .user_data() + .insert_if_missing(ToplevelState::default); + for instance in &self.instances { + send_toplevel_to_client::(&self.dh, None, instance, toplevel); + } + self.toplevels.push(toplevel.clone()); + } + + pub fn toplevel_enter_output(&mut self, toplevel: &W, output: &Output) { + if let Some(state) = toplevel.user_data().get::() { + state.lock().unwrap().outputs.push(output.clone()); + } + } + + pub fn toplevel_leave_output(&mut self, toplevel: &W, output: &Output) { + if let Some(state) = toplevel.user_data().get::() { + state.lock().unwrap().outputs.retain(|o| o != output); + } + } + + pub fn toplevel_enter_workspace(&mut self, toplevel: &W, workspace: &WorkspaceHandle) { + if let Some(state) = toplevel.user_data().get::() { + state.lock().unwrap().workspaces.push(workspace.clone()); + } + } + + pub fn toplevel_leave_workspace(&mut self, toplevel: &W, workspace: &WorkspaceHandle) { + if let Some(state) = toplevel.user_data().get::() { + state.lock().unwrap().workspaces.retain(|w| w != workspace); + } + } + + pub fn refresh(&mut self, workspace_state: Option<&WorkspaceState>) { + self.toplevels.retain(|window| { + let mut state = window + .user_data() + .get::() + .unwrap() + .lock() + .unwrap(); + state.rectangles.retain(|_, (surface, _)| surface.alive()); + if window.alive() { + std::mem::drop(state); + for instance in &self.instances { + send_toplevel_to_client::(&self.dh, workspace_state, instance, window); + } + true + } else { + for handle in &state.instances { + // don't send events to stopped instances + if self + .instances + .iter() + .any(|i| i.id().same_client_as(&handle.id())) + { + handle.closed(); + } + } + false + } + }); + } + + pub fn global_id(&self) -> GlobalId { + self.global.clone() + } +} + +fn send_toplevel_to_client( + dh: &DisplayHandle, + _workspace_state: Option<&WorkspaceState>, + info: &ExtForeignToplevelListV1, + window: &W, +) where + D: GlobalDispatch + + Dispatch + + Dispatch> + + ToplevelInfoHandler + + 'static, + W: Window, +{ + let mut state = window + .user_data() + .get::() + .unwrap() + .lock() + .unwrap(); + let instance = match state + .instances + .iter() + .find(|i| i.id().same_client_as(&info.id())) + { + Some(i) => i, + None => { + if let Ok(client) = dh.get_client(info.id()) { + if let Ok(toplevel_handle) = client + .create_resource::( + dh, + info.version(), + ToplevelHandleStateInner::from_window(info.clone(), window), + ) + { + info.toplevel(&toplevel_handle); + + let data = toplevel_handle + .data::>() + .unwrap() + .lock() + .unwrap(); + + // The protocol states that the identifier should only be sent when creating a new handle. + toplevel_handle.identifier(data.identifier.clone()); + drop(data); // double borrow occurs unless we explicitly drop the data. + + state.instances.push(toplevel_handle); + state.instances.last().unwrap() + } else { + return; + } + } else { + return; + } + } + }; + + let mut handle_state = instance + .data::>() + .unwrap() + .lock() + .unwrap(); + let mut changed = false; + if handle_state.title != window.title() { + handle_state.title = window.title(); + instance.title(handle_state.title.clone()); + changed = true; + } + if handle_state.app_id != window.app_id() { + handle_state.app_id = window.app_id(); + instance.app_id(handle_state.app_id.clone()); + changed = true; + } + + if changed { + instance.done(); + } +} + +pub fn window_from_handle(handle: ExtForeignToplevelHandleV1) -> Option { + handle + .data::>() + .map(|state| state.lock().unwrap().window.clone()) +} + +macro_rules! delegate_foreign_toplevel_info { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, $window: ty) => { + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::wayland::protocols::ext_foreign_toplevel_list::ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1: $crate::wayland::protocols::ext_foreign_toplevel_list::ToplevelInfoGlobalData + ] => $crate::wayland::protocols::ext_foreign_toplevel_list::ToplevelInfoState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::wayland::protocols::ext_foreign_toplevel_list::ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1: () + ] => $crate::wayland::protocols::ext_foreign_toplevel_list::ToplevelInfoState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::wayland::protocols::ext_foreign_toplevel_list::ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1: $crate::wayland::protocols::ext_foreign_toplevel_list::ToplevelHandleState<$window> + ] => $crate::wayland::protocols::ext_foreign_toplevel_list::ToplevelInfoState); + }; +} +pub(crate) use delegate_foreign_toplevel_info; diff --git a/src/wayland/protocols/mod.rs b/src/wayland/protocols/mod.rs index e84f5ad1..0a64d9b0 100644 --- a/src/wayland/protocols/mod.rs +++ b/src/wayland/protocols/mod.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pub mod drm; +pub mod ext_foreign_toplevel_list; //pub mod export_dmabuf; pub mod output_configuration; pub mod screencopy;