From 6b659eb107869716a3eccac7330d8f45518fcc24 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 18 Jul 2022 21:26:02 +0200 Subject: [PATCH] wayland: toplevel management protocol --- Cargo.lock | 42 +--- Cargo.toml | 2 +- src/shell/mod.rs | 22 +- src/shell/workspace.rs | 2 +- src/wayland/handlers/mod.rs | 1 + src/wayland/handlers/toplevel_management.rs | 45 ++++ src/wayland/protocols/mod.rs | 1 + src/wayland/protocols/toplevel_info.rs | 60 +++-- src/wayland/protocols/toplevel_management.rs | 219 +++++++++++++++++++ 9 files changed, 340 insertions(+), 54 deletions(-) create mode 100644 src/wayland/handlers/toplevel_management.rs create mode 100644 src/wayland/protocols/toplevel_management.rs diff --git a/Cargo.lock b/Cargo.lock index baac3f75..95f67e67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -361,11 +361,10 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols?branch=main#55f15e8b05fc983ab36b65b4c027b59f5876a181" +source = "git+https://github.com/pop-os/cosmic-protocols?branch=main#81d6a50bdc91af5968f87785fc19a16cf261c96b" dependencies = [ "bitflags", "wayland-backend", - "wayland-client 0.30.0-beta.8", "wayland-protocols 0.30.0-beta.8", "wayland-scanner 0.30.0-beta.8", "wayland-server", @@ -649,21 +648,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -[[package]] -name = "futures-channel" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" - [[package]] name = "gbm" version = "0.8.0" @@ -1569,7 +1553,7 @@ dependencies = [ "memmap2", "nix 0.22.3", "pkg-config", - "wayland-client 0.29.4", + "wayland-client", "wayland-cursor", "wayland-protocols 0.29.4", ] @@ -1844,21 +1828,6 @@ dependencies = [ "wayland-sys 0.29.4", ] -[[package]] -name = "wayland-client" -version = "0.30.0-beta.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9e0d862c23f07b2c4b49de66b0680948af5dd1d2def17f1ddc16520352bf14" -dependencies = [ - "bitflags", - "futures-channel", - "futures-core", - "nix 0.24.2", - "thiserror", - "wayland-backend", - "wayland-scanner 0.30.0-beta.8", -] - [[package]] name = "wayland-commons" version = "0.29.4" @@ -1878,7 +1847,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c52758f13d5e7861fc83d942d3d99bf270c83269575e52ac29e5b73cb956a6bd" dependencies = [ "nix 0.22.3", - "wayland-client 0.29.4", + "wayland-client", "xcursor", ] @@ -1900,7 +1869,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60147ae23303402e41fe034f74fb2c35ad0780ee88a1c40ac09a3be1e7465741" dependencies = [ "bitflags", - "wayland-client 0.29.4", + "wayland-client", "wayland-commons", "wayland-scanner 0.29.4", ] @@ -1913,7 +1882,6 @@ checksum = "e47c45a60d531d5a513601f47f51a4743901836778ddae208ae9124606be1719" dependencies = [ "bitflags", "wayland-backend", - "wayland-client 0.30.0-beta.8", "wayland-scanner 0.30.0-beta.8", "wayland-server", ] @@ -2102,7 +2070,7 @@ dependencies = [ "raw-window-handle", "smithay-client-toolkit", "wasm-bindgen", - "wayland-client 0.29.4", + "wayland-client", "wayland-protocols 0.29.4", "web-sys", "winapi", diff --git a/Cargo.toml b/Cargo.toml index f42ac2f0..6bae74d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ atomic_float = "0.1" libsystemd = "0.5" wayland-backend = "=0.1.0-beta.8" wayland-scanner = "=0.30.0-beta.8" -cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", branch = "main" } +cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", branch = "main", default-features = false, features = ["server"] } [dependencies.smithay] version = "0.3" diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 44fa32c2..89785fdf 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -22,9 +22,11 @@ use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::State use crate::{ config::{Config, WorkspaceMode as ConfigMode}, + //state::ClientState, utils::prelude::*, wayland::protocols::{ toplevel_info::ToplevelInfoState, + toplevel_management::{ToplevelManagementState, ManagementCapabilities}, workspace::{ WorkspaceCapabilities, WorkspaceGroupHandle, WorkspaceHandle, WorkspaceState, WorkspaceUpdateGuard, @@ -52,6 +54,7 @@ pub struct Shell { // wayland_state pub layer_shell_state: WlrLayerShellState, pub toplevel_info_state: ToplevelInfoState, + pub toplevel_management_state: ToplevelManagementState, pub xdg_shell_state: XdgShellState, pub workspace_state: WorkspaceState, } @@ -81,10 +84,24 @@ const UNINIT_SPACE: MaybeUninit = MaybeUninit::uninit(); impl Shell { pub fn new(config: &Config, dh: &DisplayHandle) -> Self { + // TODO: Privileged protocols let layer_shell_state = WlrLayerShellState::new::(dh, None); - let toplevel_info_state = ToplevelInfoState::new(dh, |_| true); let xdg_shell_state = XdgShellState::new::(dh, None); - let mut workspace_state = WorkspaceState::new(dh, |_| true); + let toplevel_info_state = ToplevelInfoState::new( + dh, + //|client| client.get_data::().unwrap().privileged, + |_| true); + let toplevel_management_state = ToplevelManagementState::new::( + dh, + vec![ManagementCapabilities::Close, ManagementCapabilities::Activate], + //|client| client.get_data::().unwrap().privileged, + |_| true, + ); + let mut workspace_state = WorkspaceState::new( + dh, + //|client| client.get_data::().unwrap().privileged, + |_| true, + ); let mut spaces = unsafe { let mut spaces = [UNINIT_SPACE; MAX_WORKSPACES]; @@ -116,6 +133,7 @@ impl Shell { layer_shell_state, toplevel_info_state, + toplevel_management_state, xdg_shell_state, workspace_state, } diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index bcf91b9b..dfca44f3 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -20,7 +20,7 @@ use smithay::{ use std::collections::HashMap; pub struct Workspace { - pub(super) idx: u8, + pub idx: u8, pub space: Space, pub tiling_layer: TilingLayout, pub floating_layer: FloatingLayout, diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index 588521e9..eed536b0 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -11,6 +11,7 @@ pub mod primary_selection; pub mod seat; pub mod shm; pub mod toplevel_info; +pub mod toplevel_management; pub mod viewporter; pub mod wl_drm; pub mod workspace; diff --git a/src/wayland/handlers/toplevel_management.rs b/src/wayland/handlers/toplevel_management.rs new file mode 100644 index 00000000..9577e5fb --- /dev/null +++ b/src/wayland/handlers/toplevel_management.rs @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use smithay::{ + desktop::{Kind, Window}, + reexports::wayland_server::DisplayHandle, + wayland::seat::Seat, +}; + +use crate::{ + utils::prelude::*, + wayland::protocols::toplevel_management::{ + delegate_toplevel_management, ToplevelManagementHandler, ToplevelManagementState, + }, +}; + +impl ToplevelManagementHandler for State { + fn toplevel_management_state(&mut self) -> &mut ToplevelManagementState { + &mut self.common.shell.toplevel_management_state + } + + fn activate(&mut self, dh: &DisplayHandle, window: &Window, seat: Option>) { + if let Some(idx) = self.common.shell.space_for_window(window.toplevel().wl_surface()).map(|w| w.idx) { + let seat = seat.unwrap_or(self.common.last_active_seat.clone()); + let output = active_output(&seat, &self.common); + if self.common.shell.active_space(&output).idx != idx { + self.common.shell.activate(&seat, &output, idx as usize); + } + self.common.set_focus( + dh, + Some(window.toplevel().wl_surface()), + &seat, + None, + ); + } + } + + fn close(&mut self, _dh: &DisplayHandle, window: &Window) { + #[allow(irrefutable_let_patterns)] + if let Kind::Xdg(xdg) = &window.toplevel() { + xdg.send_close(); + } + } +} + +delegate_toplevel_management!(State); diff --git a/src/wayland/protocols/mod.rs b/src/wayland/protocols/mod.rs index 22046f65..f598db04 100644 --- a/src/wayland/protocols/mod.rs +++ b/src/wayland/protocols/mod.rs @@ -3,4 +3,5 @@ pub mod drm; pub mod output_configuration; pub mod toplevel_info; +pub mod toplevel_management; pub mod workspace; diff --git a/src/wayland/protocols/toplevel_info.rs b/src/wayland/protocols/toplevel_info.rs index f2a74ea2..6367844d 100644 --- a/src/wayland/protocols/toplevel_info.rs +++ b/src/wayland/protocols/toplevel_info.rs @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only -use std::sync::Mutex; +use std::{ + collections::HashMap, + sync::Mutex, +}; use smithay::{ desktop::Window, @@ -8,11 +11,12 @@ use smithay::{ wayland_protocols::xdg::shell::server::xdg_toplevel, wayland_server::{ backend::{ClientId, GlobalId, ObjectId}, + protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, }, }, - utils::IsAlive, + utils::{IsAlive, Rectangle, Logical}, wayland::{ compositor::with_states, output::Output, shell::xdg::XdgToplevelSurfaceRoleAttributes, }, @@ -27,7 +31,7 @@ use cosmic_protocols::toplevel_info::v1::server::{ pub struct ToplevelInfoState { dh: DisplayHandle, - toplevels: Vec, + pub(super) toplevels: Vec, instances: Vec, global: GlobalId, _dispatch_data: std::marker::PhantomData, @@ -43,24 +47,40 @@ pub struct ToplevelInfoGlobalData { } #[derive(Default)] -struct ToplevelStateInner { +pub(super) struct ToplevelStateInner { instances: Vec, outputs: Vec, workspaces: Vec, minimized: bool, + pub(super) rectangles: HashMap)>, } -type ToplevelState = Mutex; +pub(super) type ToplevelState = Mutex; -#[derive(Default)] pub struct ToplevelHandleStateInner { outputs: Vec, workspaces: Vec, title: String, app_id: String, states: Vec, + pub(super) window: Window, } pub type ToplevelHandleState = Mutex; +impl ToplevelHandleStateInner { + fn from_window(window: &Window) -> ToplevelHandleState { + ToplevelHandleState::new( + ToplevelHandleStateInner { + outputs: Vec::new(), + workspaces: Vec::new(), + title: String::new(), + app_id: String::new(), + states: Vec::new(), + window: window.clone(), + } + ) + } +} + impl GlobalDispatch for ToplevelInfoState where @@ -82,6 +102,7 @@ where 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 { @@ -229,18 +250,21 @@ where 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 { - let state = window - .user_data() - .get::() - .unwrap() - .lock() - .unwrap(); for handle in &state.instances { // don't send events to stopped instances if self @@ -291,7 +315,7 @@ fn send_toplevel_to_client( .create_resource::( dh, info.version(), - ToplevelHandleState::default(), + ToplevelHandleStateInner::from_window(window), ) { state.instances.push(toplevel_handle); @@ -448,6 +472,16 @@ fn send_toplevel_to_client( } } +pub(super) fn window_from_handle(handle: ZcosmicToplevelHandleV1) -> Window { + handle + .data::() + .unwrap() + .lock() + .unwrap() + .window + .clone() +} + macro_rules! delegate_toplevel_info { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ diff --git a/src/wayland/protocols/toplevel_management.rs b/src/wayland/protocols/toplevel_management.rs new file mode 100644 index 00000000..f36c7bb1 --- /dev/null +++ b/src/wayland/protocols/toplevel_management.rs @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use smithay::{ + desktop::Window, + reexports::{ + wayland_server::{ + backend::{ClientId, GlobalId, ObjectId}, + protocol::wl_surface::WlSurface, + Client, DataInit, Dispatch, DisplayHandle, + GlobalDispatch, New, Resource, + }, + }, + utils::{Logical, Rectangle}, + wayland::{ + output::Output, + seat::Seat, + }, +}; + + +use cosmic_protocols::toplevel_management::v1::server::{ + zcosmic_toplevel_manager_v1::{self, ZcosmicToplevelManagerV1}, +}; +pub use cosmic_protocols::toplevel_management::v1::server::zcosmic_toplevel_manager_v1::ZcosmicToplelevelManagementCapabilitiesV1 as ManagementCapabilities; + +use super::toplevel_info::{ToplevelInfoHandler, ToplevelHandleState, ToplevelState, window_from_handle}; + + +pub struct ToplevelManagementState { + instances: Vec, + capabilities: Vec, + global: GlobalId, +} + +#[allow(unused_variables)] +pub trait ToplevelManagementHandler: ToplevelInfoHandler { + fn toplevel_management_state(&mut self) -> &mut ToplevelManagementState; + + fn activate(&mut self, dh: &DisplayHandle, window: &Window, seat: Option>) {} + fn close(&mut self, dh: &DisplayHandle, window: &Window) {} + + fn fullscreen(&mut self, dh: &DisplayHandle, window: &Window, output: Option) {} + fn unfullscreen(&mut self, dh: &DisplayHandle, window: &Window) {} + fn maximize(&mut self, dh: &DisplayHandle, window: &Window) {} + fn unmaximize(&mut self, dh: &DisplayHandle, window: &Window) {} + fn minimize(&mut self, dh: &DisplayHandle, window: &Window) {} + fn unminimize(&mut self, dh: &DisplayHandle, window: &Window) {} +} + +pub struct ToplevelManagerGlobalData { + filter: Box Fn(&'a Client) -> bool + Send + Sync>, +} + +impl ToplevelManagementState { + pub fn new(dh: &DisplayHandle, capabilities: Vec, client_filter: F) -> ToplevelManagementState + where + D: GlobalDispatch + + Dispatch + + ToplevelManagementHandler + + 'static, + F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static + { + let global = dh.create_global::( + 1, + ToplevelManagerGlobalData { + filter: Box::new(client_filter), + }, + ); + ToplevelManagementState { + capabilities, + instances: Vec::new(), + global, + } + } + + pub fn rectangle_for(&mut self, window: &Window, client: &ClientId) -> Option<(WlSurface, Rectangle)> { + if let Some(state) = window.user_data().get::() { + state.lock().unwrap().rectangles.get(client).cloned() + } else { + None + } + } + + pub fn global_id(&self) -> GlobalId { + self.global.clone() + } +} + +impl GlobalDispatch for ToplevelManagementState +where + D: GlobalDispatch + + Dispatch + + ToplevelManagementHandler + + 'static +{ + fn bind( + state: &mut D, + _dh: &DisplayHandle, + _client: &Client, + resource: New, + _global_data: &ToplevelManagerGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + let instance = data_init.init(resource, ()); + let capabilities = { + let mut caps = state.toplevel_management_state().capabilities.clone(); + let ratio = std::mem::size_of::() / std::mem::size_of::(); + let ptr = caps.as_mut_ptr() as *mut u8; + let len = caps.len() * ratio; + let cap = caps.capacity() * ratio; + std::mem::forget(caps); + unsafe { Vec::from_raw_parts(ptr, len, cap) } + }; + instance.capabilities(capabilities); + state.toplevel_management_state().instances.push(instance); + } + + fn can_view(client: Client, global_data: &ToplevelManagerGlobalData) -> bool { + (global_data.filter)(&client) + } +} + + +impl Dispatch for ToplevelManagementState +where + D: GlobalDispatch + + Dispatch + + ToplevelManagementHandler + + 'static +{ + fn request( + state: &mut D, + _client: &Client, + _obj: &ZcosmicToplevelManagerV1, + request: zcosmic_toplevel_manager_v1::Request, + _data: &(), + dh: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + zcosmic_toplevel_manager_v1::Request::Activate { toplevel, seat } => { + let window = window_from_handle(toplevel); + state.activate(dh, &window, Seat::from_resource(&seat)); + }, + zcosmic_toplevel_manager_v1::Request::Close { toplevel } => { + let window = window_from_handle(toplevel); + state.close(dh, &window); + }, + zcosmic_toplevel_manager_v1::Request::SetFullscreen { toplevel, output } => { + let window = window_from_handle(toplevel); + state.fullscreen(dh, &window, output.as_ref().and_then(Output::from_resource)) + }, + zcosmic_toplevel_manager_v1::Request::UnsetFullscreen { toplevel } => { + let window = window_from_handle(toplevel); + state.unfullscreen(dh, &window); + }, + zcosmic_toplevel_manager_v1::Request::SetMaximized { toplevel } => { + let window = window_from_handle(toplevel); + state.maximize(dh, &window); + }, + zcosmic_toplevel_manager_v1::Request::UnsetMaximized { toplevel } => { + let window = window_from_handle(toplevel); + state.unmaximize(dh, &window); + }, + zcosmic_toplevel_manager_v1::Request::SetMinimized { toplevel } => { + let window = window_from_handle(toplevel); + state.minimize(dh, &window); + }, + zcosmic_toplevel_manager_v1::Request::UnsetMinimized { toplevel } => { + let window = window_from_handle(toplevel); + state.unminimize(dh, &window); + }, + zcosmic_toplevel_manager_v1::Request::SetRectangle { toplevel, surface, x, y, width, height } => { + let window = toplevel + .data::() + .unwrap() + .lock() + .unwrap() + .window + .clone(); + if let Some(toplevel_state) = window.user_data().get::() { + let mut toplevel_state = toplevel_state.lock().unwrap(); + if let Some(client) = surface.client_id() { + if width == 0 && height == 0 { + toplevel_state.rectangles.remove(&client); + } else { + toplevel_state.rectangles.insert(client, (surface, Rectangle::from_loc_and_size((x, y), (width, height)))); + } + } + } + }, + _ => unreachable!(), + } + } + + fn destroyed(state: &mut D, client: ClientId, resource: ObjectId, _data: &()) { + let mng_state = state.toplevel_management_state(); + mng_state.instances.retain(|i| i.id() != resource); + if !mng_state.instances.iter().any(|i| i.client_id().map(|c| c == client).unwrap_or(false)) { + for toplevel in state.toplevel_info_state_mut().toplevels.iter() { + if let Some(toplevel_state) = toplevel.user_data().get::() { + toplevel_state.lock().unwrap().rectangles.remove(&client); + } + } + } + } +} + +macro_rules! delegate_toplevel_management { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::toplevel_management::v1::server::zcosmic_toplevel_manager_v1::ZcosmicToplevelManagerV1: $crate::wayland::protocols::toplevel_management::ToplevelManagerGlobalData + ] => $crate::wayland::protocols::toplevel_management::ToplevelManagementState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::toplevel_management::v1::server::zcosmic_toplevel_manager_v1::ZcosmicToplevelManagerV1: () + ] => $crate::wayland::protocols::toplevel_management::ToplevelManagementState); + }; +} +pub(crate) use delegate_toplevel_management;