From 2834f26e16b2e37b62b4947705a575570f257669 Mon Sep 17 00:00:00 2001 From: Nova Date: Thu, 15 Feb 2024 12:42:34 -0500 Subject: [PATCH] refactor(wayland): put everything in wl_surface user data --- Cargo.lock | 58 +- Cargo.toml | 4 +- src/main.rs | 4 +- src/nodes/items/panel.rs | 16 +- src/wayland/drm.rs | 9 +- src/wayland/mod.rs | 16 +- src/wayland/state.rs | 9 +- src/wayland/surface.rs | 41 +- src/wayland/utils.rs | 14 + src/wayland/xdg_shell.rs | 1046 --------------------------- src/wayland/xdg_shell/backend.rs | 310 ++++++++ src/wayland/xdg_shell/mod.rs | 69 ++ src/wayland/xdg_shell/popup.rs | 119 +++ src/wayland/xdg_shell/positioner.rs | 226 ++++++ src/wayland/xdg_shell/surface.rs | 236 ++++++ src/wayland/xdg_shell/toplevel.rs | 239 ++++++ 16 files changed, 1298 insertions(+), 1118 deletions(-) create mode 100644 src/wayland/utils.rs delete mode 100644 src/wayland/xdg_shell.rs create mode 100644 src/wayland/xdg_shell/backend.rs create mode 100644 src/wayland/xdg_shell/mod.rs create mode 100644 src/wayland/xdg_shell/popup.rs create mode 100644 src/wayland/xdg_shell/positioner.rs create mode 100644 src/wayland/xdg_shell/surface.rs create mode 100644 src/wayland/xdg_shell/toplevel.rs diff --git a/Cargo.lock b/Cargo.lock index e1c8c2a3..e9c1adb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -602,9 +602,9 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "drm" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e58eefd79f5173683872c0c82d0f05c2dc3c583d631259f60bb7a323756b7ff2" +checksum = "a0f8a69e60d75ae7dab4ef26a59ca99f2a89d4c142089b537775ae0c198bdcde" dependencies = [ "bitflags 2.4.0", "bytemuck", @@ -615,9 +615,9 @@ dependencies = [ [[package]] name = "drm-ffi" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "220dd8c12ebf2b0cbaffa19e00de02f5f090d363fb900f16ea012c077eea1174" +checksum = "41334f8405792483e32ad05fbb9c5680ff4e84491883d2947a4757dc54cb2ac6" dependencies = [ "drm-sys", "rustix", @@ -631,9 +631,9 @@ checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" [[package]] name = "drm-sys" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5115283ec60c99da8a9e5dc3c55f27680211e974c948cb6f3b51f0373190503b" +checksum = "2d09ff881f92f118b11105ba5e34ff8f4adf27b30dae8f12e28c193af1c83176" dependencies = [ "libc", "linux-raw-sys 0.6.1", @@ -819,12 +819,12 @@ dependencies = [ [[package]] name = "gethostname" -version = "0.3.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb65d4ba3173c56a500b555b532f72c42e8d1fe64962b518897f8959fae2c177" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" dependencies = [ "libc", - "winapi", + "windows-targets", ] [[package]] @@ -1737,9 +1737,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" dependencies = [ "memchr", ] @@ -2016,7 +2016,7 @@ checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "smithay" version = "0.3.0" -source = "git+https://github.com/smithay/smithay.git#5c688b89fc97ade8c60c45d4a319311b7ec5292f" +source = "git+https://github.com/smithay/smithay.git#91e61f13501f21d66803efac947bfafed43080c5" dependencies = [ "appendlist", "bitflags 2.4.0", @@ -2575,13 +2575,13 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wayland-backend" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19152ddd73f45f024ed4534d9ca2594e0ef252c1847695255dae47f34df9fbe4" +checksum = "9d50fa61ce90d76474c87f5fc002828d81b32677340112b4ef08079a9d459a40" dependencies = [ "cc", "downcast-rs", - "nix 0.26.4", + "rustix", "scoped-tls", "smallvec", "wayland-sys", @@ -2627,9 +2627,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb8e28403665c9f9513202b7e1ed71ec56fde5c107816843fb14057910b2c09c" +checksum = "63b3a62929287001986fb58c789dce9b67604a397c15c611ad9f747300b6c283" dependencies = [ "proc-macro2", "quick-xml", @@ -2689,15 +2689,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-wsapoll" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2790,25 +2781,20 @@ dependencies = [ [[package]] name = "x11rb" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a" +checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a" dependencies = [ "gethostname", - "nix 0.26.4", - "winapi", - "winapi-wsapoll", + "rustix", "x11rb-protocol", ] [[package]] name = "x11rb-protocol" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d6c3f9a0fb6701fab8f6cea9b0c0bd5d6876f1f89f7fada07e558077c344bc" -dependencies = [ - "nix 0.26.4", -] +checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34" [[package]] name = "xkbcommon" diff --git a/Cargo.toml b/Cargo.toml index c8aafbae..f4aa8fbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,8 +68,8 @@ ctrlc = "3.4.1" libc = "0.2.148" input-event-codes = "5.16.8" nix = "0.27.1" -wayland-scanner = "0.31.0" -wayland-backend = "0.3.2" +wayland-scanner = "0.31.1" +wayland-backend = "0.3.3" cluFlock = "1.2.7" fxtypemap = "0.2.0" diff --git a/src/main.rs b/src/main.rs index 82422235..e9308be5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -349,8 +349,8 @@ fn adaptive_sleep( }); } -#[tokio::main] -// #[tokio::main(flavor = "current_thread")] +// #[tokio::main] +#[tokio::main(flavor = "current_thread")] async fn event_loop(info_sender: oneshot::Sender) -> color_eyre::eyre::Result<()> { let socket_path = server::get_free_socket_path().expect("Unable to find a free stardust socket path"); diff --git a/src/nodes/items/panel.rs b/src/nodes/items/panel.rs index bede0c04..e31edbdc 100644 --- a/src/nodes/items/panel.rs +++ b/src/nodes/items/panel.rs @@ -23,7 +23,7 @@ use serde::{ }; use stardust_xr::schemas::flex::{deserialize, serialize}; use std::sync::{Arc, Weak}; -use tracing::debug; +use tracing::{debug, info}; lazy_static! { pub static ref ITEM_TYPE_INFO_PANEL: TypeInfo = TypeInfo { @@ -220,7 +220,7 @@ pub struct PanelItem { pub backend: Box, } impl PanelItem { - pub fn create(backend: Box, pid: Option) -> Arc> { + pub fn create(backend: Box, pid: Option) -> (Arc, Arc>) { debug!(?pid, "Create panel item"); let startup_settings = pid @@ -228,9 +228,12 @@ impl PanelItem { .and_then(|env| state(&env)); let uid = nanoid!(); - let node = Node::create_parent_name(&INTERNAL_CLIENT, "/item/panel/item", &uid, true) - .add_to_scenegraph() - .unwrap(); + let node = Arc::new(Node::create_parent_name( + &INTERNAL_CLIENT, + "/item/panel/item", + &uid, + true, + )); let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false); if let Some(startup_settings) = &startup_settings { spatial.set_local_transform(startup_settings.root); @@ -266,7 +269,7 @@ impl PanelItem { node.add_local_signal("touch_up", Self::touch_up_flex); node.add_local_signal("reset_touches", Self::reset_touches_flex); - panel_item + (node, panel_item) } pub fn drop_toplevel(&self) { let Some(node) = self.node.upgrade() else { @@ -593,5 +596,6 @@ impl Backend for PanelItem { impl Drop for PanelItem { fn drop(&mut self) { // Dropped panel item, basically just a debug breakpoint place + info!("Dropped panel item {}", self.uid); } } diff --git a/src/wayland/drm.rs b/src/wayland/drm.rs index 90b6ae31..65a4c3a4 100644 --- a/src/wayland/drm.rs +++ b/src/wayland/drm.rs @@ -117,8 +117,13 @@ impl Dispatch for WaylandState { return; } - let mut dma = Dmabuf::builder((width, height), format, DmabufFlags::empty()); - dma.add_plane(name, 0, offset0 as u32, stride0 as u32, Modifier::Invalid); + let mut dma = Dmabuf::builder( + (width, height), + format, + Modifier::Invalid, + DmabufFlags::empty(), + ); + dma.add_plane(name, 0, offset0 as u32, stride0 as u32); match dma.build() { Some(dmabuf) => { state.dmabuf_tx.send((dmabuf.clone(), None)).unwrap(); diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index 48b18158..ea6fc7c5 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -6,6 +6,7 @@ mod state; mod surface; // mod xdg_activation; mod drm; +mod utils; mod xdg_shell; #[cfg(feature = "xwayland_rootful")] pub mod xwayland_rootful; @@ -30,6 +31,7 @@ use smithay::backend::allocator::dmabuf::Dmabuf; use smithay::backend::egl::EGLContext; use smithay::backend::renderer::gles::GlesRenderer; use smithay::backend::renderer::ImportDma; +use smithay::output::Output; use smithay::reexports::wayland_server::backend::ClientId; use smithay::reexports::wayland_server::DisplayHandle; use smithay::reexports::wayland_server::{Display, ListeningSocket}; @@ -97,8 +99,8 @@ pub struct Wayland { pub socket_name: Option, join_handle: JoinHandle>, renderer: GlesRenderer, + output: Output, dmabuf_rx: UnboundedReceiver<(Dmabuf, Option)>, - wayland_state: Arc>, #[cfg(feature = "xwayland_rootful")] pub x_lock: X11Lock, #[cfg(feature = "xwayland_rootless")] @@ -124,6 +126,7 @@ impl Wayland { #[cfg(feature = "xwayland_rootless")] let xwayland_state = XWaylandState::create(&display_handle)?; let wayland_state = WaylandState::new(display_handle, &renderer, dmabuf_tx); + let output = wayland_state.lock().output.clone(); let socket = ListeningSocket::bind_auto("wayland", 0..33)?; let socket_name = socket @@ -137,15 +140,14 @@ impl Wayland { let x_display = start_xwayland(socket.as_raw_fd())?; info!(socket_name, "Wayland active"); - let join_handle = Wayland::start_loop(display.clone(), socket, wayland_state.clone())?; - + let join_handle = Wayland::start_loop(display.clone(), socket, wayland_state)?; Ok(Wayland { display, socket_name, join_handle, renderer, + output, dmabuf_rx, - wayland_state, #[cfg(feature = "xwayland_rootful")] x_lock: x_display, #[cfg(feature = "xwayland_rootless")] @@ -185,7 +187,7 @@ impl Wayland { e = dispatch_poll_listener.readable() => { // Dispatch let mut guard = e?; debug_span!("Dispatch wayland event").in_scope(|| -> Result<(), color_eyre::Report> { - display.dispatch_clients(&mut *state.lock())?; + display.dispatch_clients(&mut state.lock())?; display.flush_clients(None); Ok(()) })?; @@ -213,10 +215,8 @@ impl Wayland { } pub fn frame_event(&self, sk: &impl StereoKitDraw) { - let output = self.wayland_state.lock().output.clone(); - for core_surface in CORE_SURFACES.get_valid_contents() { - core_surface.frame(sk, output.clone()); + core_surface.frame(sk, self.output.clone()); } } diff --git a/src/wayland/state.rs b/src/wayland/state.rs index 236d6237..e24f70df 100644 --- a/src/wayland/state.rs +++ b/src/wayland/state.rs @@ -18,7 +18,10 @@ use smithay::{ wayland_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as DecorationMode, wayland_server::{ backend::{ClientData, ClientId, DisconnectReason}, - protocol::{wl_buffer::WlBuffer, wl_data_device_manager::WlDataDeviceManager}, + protocol::{ + wl_buffer::WlBuffer, wl_data_device_manager::WlDataDeviceManager, + wl_output::WlOutput, + }, DisplayHandle, }, }, @@ -29,6 +32,7 @@ use smithay::{ dmabuf::{ self, DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState, }, + output::OutputHandler, shell::kde::decoration::KdeDecorationState, shm::{ShmHandler, ShmState}, }, @@ -201,6 +205,9 @@ impl DmabufHandler for WaylandState { self.dmabuf_tx.send((dmabuf, Some(notifier))).unwrap(); } } +impl OutputHandler for WaylandState { + fn output_bound(&mut self, _output: Output, _wl_output: WlOutput) {} +} delegate_dmabuf!(WaylandState); delegate_shm!(WaylandState); delegate_output!(WaylandState); diff --git a/src/wayland/surface.rs b/src/wayland/surface.rs index b4249d2a..937229b8 100644 --- a/src/wayland/surface.rs +++ b/src/wayland/surface.rs @@ -1,4 +1,4 @@ -use super::state::WaylandState; +use super::{state::WaylandState, utils::get_data}; use crate::{ core::{delta::Delta, destroy_queue, registry::Registry}, nodes::drawable::{model::ModelPart, shaders::PANEL_SHADER_BYTES}, @@ -77,13 +77,13 @@ impl CoreSurface { } pub fn from_wl_surface(surf: &WlSurface) -> Option> { - compositor::with_states(surf, |data| { - data.data_map.get::>().cloned() - }) + get_data(surf) } pub fn process(&self, sk: &impl StereoKitDraw, renderer: &mut GlesRenderer) { - let Some(wl_surface) = self.wl_surface() else {return}; + let Some(wl_surface) = self.wl_surface() else { + return; + }; let sk_tex = self .sk_tex @@ -124,13 +124,23 @@ impl CoreSurface { let Some(renderer_surface_state) = data .data_map .get::() - .map(RefCell::borrow) else {return}; + .map(RefCell::borrow) + else { + return; + }; let Some(smithay_tex) = renderer_surface_state .texture::(renderer.id()) - .cloned() else {return}; + .cloned() + else { + return; + }; - let Some(sk_tex) = self.sk_tex.get() else {return}; - let Some(sk_mat) = self.sk_mat.get() else {return}; + let Some(sk_tex) = self.sk_tex.get() else { + return; + }; + let Some(sk_mat) = self.sk_mat.get() else { + return; + }; unsafe { sk.tex_set_surface( sk_tex.as_ref(), @@ -149,7 +159,9 @@ impl CoreSurface { sk.material_set_queue_offset(sk_mat.as_ref().as_ref(), *material_offset as i32); } - let Some(surface_size) = renderer_surface_state.surface_size() else {return}; + let Some(surface_size) = renderer_surface_state.surface_size() else { + return; + }; let new_mapped_data = CoreSurfaceData { size: Vector2::from([surface_size.w as u32, surface_size.h as u32]), wl_tex: Some(SendWrapper::new(smithay_tex)), @@ -164,7 +176,9 @@ impl CoreSurface { } pub fn frame(&self, sk: &impl StereoKitDraw, output: Output) { - let Some(wl_surface) = self.wl_surface() else {return}; + let Some(wl_surface) = self.wl_surface() else { + return; + }; send_frames_surface_tree( &wl_surface, @@ -196,10 +210,7 @@ impl CoreSurface { self.weak_surface.upgrade().ok() } - pub fn with_states(&self, f: F) -> Option - where - F: FnOnce(&SurfaceData) -> T, - { + pub fn with_states T>(&self, f: F) -> Option { self.wl_surface() .map(|wl_surface| compositor::with_states(&wl_surface, f)) } diff --git a/src/wayland/utils.rs b/src/wayland/utils.rs new file mode 100644 index 00000000..dbb5f1ea --- /dev/null +++ b/src/wayland/utils.rs @@ -0,0 +1,14 @@ +use smithay::{reexports::wayland_server::protocol::wl_surface::WlSurface, wayland::compositor}; +use std::sync::Arc; + +pub fn insert_data(wl_surface: &WlSurface, data: T) { + insert_data_raw(wl_surface, Arc::new(data)) +} +pub fn insert_data_raw(wl_surface: &WlSurface, data: Arc) { + compositor::with_states(wl_surface, |d| { + d.data_map.insert_if_missing_threadsafe(move || data) + }); +} +pub fn get_data(wl_surface: &WlSurface) -> Option> { + compositor::with_states(wl_surface, |d| d.data_map.get::>().cloned()) +} diff --git a/src/wayland/xdg_shell.rs b/src/wayland/xdg_shell.rs deleted file mode 100644 index 64448259..00000000 --- a/src/wayland/xdg_shell.rs +++ /dev/null @@ -1,1046 +0,0 @@ -use super::{ - seat::{CursorInfo, KeyboardEvent, PointerEvent, SeatData}, - state::{ClientState, WaylandState}, - surface::CoreSurface, - SERIAL_COUNTER, -}; -use crate::{ - nodes::{ - data::KEYMAPS, - drawable::model::ModelPart, - items::panel::{ - Backend, ChildInfo, Geometry, PanelItem, PanelItemInitData, SurfaceID, ToplevelInfo, - }, - }, - wayland::seat::handle_cursor, -}; -use color_eyre::eyre::{bail, eyre, Result}; -use mint::Vector2; -use nanoid::nanoid; -use once_cell::sync::OnceCell; -use parking_lot::Mutex; -use rustc_hash::FxHashMap; -use smithay::reexports::{ - wayland_protocols::xdg::shell::server::{ - xdg_popup::{self, XdgPopup}, - xdg_positioner::{self, Anchor, ConstraintAdjustment, Gravity, XdgPositioner}, - xdg_surface::{self, XdgSurface}, - xdg_toplevel::{self, ResizeEdge, XdgToplevel, EVT_WM_CAPABILITIES_SINCE}, - xdg_wm_base::{self, XdgWmBase}, - }, - wayland_server::{ - backend::ClientId, protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, - DisplayHandle, GlobalDispatch, New, Resource, WEnum, Weak as WlWeak, - }, -}; -use std::{ - fmt::Debug, - sync::{Arc, Weak}, -}; -use tokio::sync::watch; -use tracing::{debug, warn}; - -impl GlobalDispatch for WaylandState { - fn bind( - _state: &mut WaylandState, - _handle: &DisplayHandle, - _client: &Client, - resource: New, - _global_data: &(), - data_init: &mut DataInit<'_, WaylandState>, - ) { - data_init.init(resource, ()); - } -} - -impl Dispatch for WaylandState { - fn request( - _state: &mut WaylandState, - _client: &Client, - _resource: &XdgWmBase, - request: xdg_wm_base::Request, - _data: &(), - _dhandle: &DisplayHandle, - data_init: &mut DataInit<'_, WaylandState>, - ) { - match request { - xdg_wm_base::Request::CreatePositioner { id } => { - let positioner = data_init.init(id, Mutex::new(PositionerData::default())); - debug!(?positioner, "Create XDG positioner"); - } - xdg_wm_base::Request::GetXdgSurface { id, surface } => { - let xdg_surface = data_init.init(id, Mutex::new(XdgSurfaceData::new(&surface))); - debug!(?xdg_surface, "Create XDG surface"); - } - xdg_wm_base::Request::Pong { serial } => { - debug!(serial, "Client pong"); - } - xdg_wm_base::Request::Destroy => { - debug!("Destroy XDG WM base"); - } - _ => unreachable!(), - } - } -} - -#[derive(Debug, Clone, Copy)] -pub struct PositionerData { - size: Vector2, - anchor_rect_pos: Vector2, - anchor_rect_size: Vector2, - anchor: Anchor, - gravity: Gravity, - constraint_adjustment: ConstraintAdjustment, - offset: Vector2, - reactive: bool, -} -impl Default for PositionerData { - fn default() -> Self { - Self { - size: Vector2::from([0; 2]), - anchor_rect_pos: Vector2::from([0; 2]), - anchor_rect_size: Vector2::from([0; 2]), - anchor: Anchor::None, - gravity: Gravity::None, - constraint_adjustment: ConstraintAdjustment::None, - offset: Vector2::from([0; 2]), - reactive: false, - } - } -} - -impl PositionerData { - fn anchor_has_edge(&self, edge: Anchor) -> bool { - match edge { - Anchor::Top => { - self.anchor == Anchor::Top - || self.anchor == Anchor::TopLeft - || self.anchor == Anchor::TopRight - } - Anchor::Bottom => { - self.anchor == Anchor::Bottom - || self.anchor == Anchor::BottomLeft - || self.anchor == Anchor::BottomRight - } - Anchor::Left => { - self.anchor == Anchor::Left - || self.anchor == Anchor::TopLeft - || self.anchor == Anchor::BottomLeft - } - Anchor::Right => { - self.anchor == Anchor::Right - || self.anchor == Anchor::TopRight - || self.anchor == Anchor::BottomRight - } - _ => unreachable!(), - } - } - - fn gravity_has_edge(&self, edge: Gravity) -> bool { - match edge { - Gravity::Top => { - self.gravity == Gravity::Top - || self.gravity == Gravity::TopLeft - || self.gravity == Gravity::TopRight - } - Gravity::Bottom => { - self.gravity == Gravity::Bottom - || self.gravity == Gravity::BottomLeft - || self.gravity == Gravity::BottomRight - } - Gravity::Left => { - self.gravity == Gravity::Left - || self.gravity == Gravity::TopLeft - || self.gravity == Gravity::BottomLeft - } - Gravity::Right => { - self.gravity == Gravity::Right - || self.gravity == Gravity::TopRight - || self.gravity == Gravity::BottomRight - } - _ => unreachable!(), - } - } - - pub fn get_pos(&self) -> Vector2 { - let mut pos = self.offset; - - if self.anchor_has_edge(Anchor::Top) { - pos.y += self.anchor_rect_pos.y; - } else if self.anchor_has_edge(Anchor::Bottom) { - pos.y += self.anchor_rect_pos.y + self.anchor_rect_size.y as i32; - } else { - pos.y += self.anchor_rect_pos.y + self.anchor_rect_size.y as i32 / 2; - } - - if self.anchor_has_edge(Anchor::Left) { - pos.x += self.anchor_rect_pos.x; - } else if self.anchor_has_edge(Anchor::Right) { - pos.x += self.anchor_rect_pos.x + self.anchor_rect_size.x as i32; - } else { - pos.x += self.anchor_rect_pos.x + self.anchor_rect_size.x as i32 / 2; - } - - if self.gravity_has_edge(Gravity::Top) { - pos.y -= self.size.y as i32; - } else if !self.gravity_has_edge(Gravity::Bottom) { - pos.y -= self.size.y as i32 / 2; - } - - if self.gravity_has_edge(Gravity::Left) { - pos.x -= self.size.x as i32; - } else if !self.gravity_has_edge(Gravity::Right) { - pos.x -= self.size.x as i32 / 2; - } - - pos - } -} -impl From for Geometry { - fn from(value: PositionerData) -> Self { - Geometry { - origin: value.get_pos(), - size: value.size, - } - } -} - -impl Dispatch, WaylandState> for WaylandState { - fn request( - _state: &mut WaylandState, - _client: &Client, - positioner: &XdgPositioner, - request: xdg_positioner::Request, - data: &Mutex, - _dhandle: &DisplayHandle, - _data_init: &mut DataInit<'_, WaylandState>, - ) { - match request { - xdg_positioner::Request::SetSize { width, height } => { - debug!(?positioner, width, height, "Set positioner size"); - data.lock().size = Vector2::from([width as u32, height as u32]); - } - xdg_positioner::Request::SetAnchorRect { - x, - y, - width, - height, - } => { - if width < 1 || height < 1 { - positioner.post_error( - xdg_positioner::Error::InvalidInput, - "Invalid size for positioner's anchor rectangle.", - ); - warn!( - ?positioner, - width, height, "Invalid size for positioner's anchor rectangle" - ); - return; - } - - debug!( - ?positioner, - x, y, width, height, "Set positioner anchor rectangle" - ); - let mut data = data.lock(); - data.anchor_rect_pos = [x, y].into(); - data.anchor_rect_size = [width as u32, height as u32].into(); - } - xdg_positioner::Request::SetAnchor { anchor } => { - if let WEnum::Value(anchor) = anchor { - debug!(?positioner, ?anchor, "Set positioner anchor"); - data.lock().anchor = anchor; - } - } - xdg_positioner::Request::SetGravity { gravity } => { - if let WEnum::Value(gravity) = gravity { - debug!(?positioner, ?gravity, "Set positioner gravity"); - data.lock().gravity = gravity; - } - } - xdg_positioner::Request::SetConstraintAdjustment { - constraint_adjustment, - } => { - debug!( - ?positioner, - constraint_adjustment, "Set positioner constraint adjustment" - ); - let Some(constraint_adjustment) = ConstraintAdjustment::from_bits(constraint_adjustment) else {return}; - data.lock().constraint_adjustment = constraint_adjustment; - } - xdg_positioner::Request::SetOffset { x, y } => { - debug!(?positioner, x, y, "Set positioner offset"); - data.lock().offset = [x, y].into(); - } - xdg_positioner::Request::SetReactive => { - debug!(?positioner, "Set positioner reactive"); - data.lock().reactive = true; - } - xdg_positioner::Request::SetParentSize { - parent_width, - parent_height, - } => { - debug!( - ?positioner, - parent_width, parent_height, "Set positioner parent size" - ); - } - xdg_positioner::Request::SetParentConfigure { serial } => { - debug!(?positioner, serial, "Set positioner parent size"); - } - xdg_positioner::Request::Destroy => (), - _ => unreachable!(), - } - } -} - -pub struct XdgSurfaceData { - wl_surface: WlWeak, - surface_id: SurfaceID, - panel_item: Weak>, - geometry: Option, -} -impl XdgSurfaceData { - pub fn new(wl_surface: &WlSurface) -> Self { - XdgSurfaceData { - wl_surface: wl_surface.downgrade(), - surface_id: SurfaceID::Toplevel, - panel_item: Weak::new(), - geometry: None, - } - } - pub fn get(xdg_surface: &XdgSurface) -> Option<&Mutex> { - xdg_surface.data::>() - } - pub fn wl_surface(&self) -> Option { - self.wl_surface.upgrade().ok() - } - pub fn panel_item(&self) -> Option>> { - self.panel_item.upgrade() - } -} -// impl Clone for XdgSurfaceData { -// fn clone(&self) -> Self { -// Self { -// wl_surface: self.wl_surface.clone(), -// geometry: self.geometry.clone(), -// surface_type: Mutex::new(self.surface_type.lock().clone()), -// } -// } -// } -impl Dispatch, WaylandState> for WaylandState { - fn request( - state: &mut WaylandState, - client: &Client, - xdg_surface: &XdgSurface, - request: xdg_surface::Request, - xdg_surface_data: &Mutex, - _dhandle: &DisplayHandle, - data_init: &mut DataInit<'_, WaylandState>, - ) { - match request { - xdg_surface::Request::GetToplevel { id } => { - let toplevel_state = Mutex::new(ToplevelData::new(xdg_surface)); - let toplevel = data_init.init(id, toplevel_state); - debug!(?toplevel, ?xdg_surface, "Create XDG toplevel"); - - if toplevel.version() >= EVT_WM_CAPABILITIES_SINCE { - toplevel.wm_capabilities(vec![3]); - } - toplevel.configure( - 0, - 0, - if toplevel.version() >= 2 { - vec![1, 5, 6, 7, 8] - .into_iter() - .flat_map(u32::to_ne_bytes) - .collect() - } else { - vec![] - }, - ); - xdg_surface.configure(SERIAL_COUNTER.inc()); - - let client_credentials = client.get_credentials(&state.display_handle).ok(); - let Some(seat_data) = client.get_data::().map(|s| s.seat.clone()) else {return}; - let Some(wl_surface) = xdg_surface_data.lock().wl_surface() else {return}; - CoreSurface::add_to( - state.display_handle.clone(), - &wl_surface, - { - let toplevel = toplevel.downgrade(); - move || { - let Ok(toplevel) = toplevel.upgrade() else {return}; - let toplevel_data = ToplevelData::get(&toplevel); - let Some(xdg_surface) = toplevel_data.lock().xdg_surface() else {return}; - let Some(xdg_surface_data) = XdgSurfaceData::get(&xdg_surface) else {return}; - - xdg_surface_data.lock().surface_id = SurfaceID::Toplevel; - let Some(backend) = XDGBackend::create(toplevel.clone(), seat_data.clone()) else {return}; - let panel_item = PanelItem::create( - Box::new(backend), - client_credentials.map(|c| c.pid), - ); - xdg_surface_data.lock().panel_item = Arc::downgrade(&panel_item); - let _ = toplevel_data - .lock() - .panel_item - .set(Arc::downgrade(&panel_item)); - handle_cursor(&panel_item, panel_item.backend.cursor.clone()); - } - }, - { - let toplevel = toplevel.downgrade(); - move |_| { - let Ok(toplevel) = toplevel.upgrade() else {return}; - let toplevel_data = ToplevelData::get(&toplevel); - let Some(panel_item) = toplevel_data.lock().panel_item() else { - let Some(xdg_surface) = toplevel_data.lock().xdg_surface() else {return}; - // if the wayland toplevel isn't mapped, hammer it again with a configure until it cooperates - toplevel.configure( - 0, - 0, - if toplevel.version() >= 2 { - vec![5, 6, 7, 8].into_iter().flat_map(u32::to_ne_bytes).collect() - } else { - vec![] - }, - ); - xdg_surface.configure(SERIAL_COUNTER.inc()); - return - }; - let Some(xdg_surface) = toplevel_data.lock().xdg_surface() else {return}; - let Some(xdg_surface_data) = XdgSurfaceData::get(&xdg_surface) else {return}; - let Some(wl_surface) = xdg_surface_data.lock().wl_surface() else {return}; - let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else {return}; - let Some(size) = core_surface.size() else {return}; - panel_item.toplevel_size_changed(size); - } - }, - ); - } - xdg_surface::Request::GetPopup { - id, - parent, - positioner, - } => { - let Some(parent) = parent else {return}; - let Some(parent_data) = parent.data::>() else {return}; - let parent_data = parent_data.lock(); - - let uid = nanoid!(); - let popup_data = Mutex::new(PopupData::new( - uid.clone(), - xdg_surface, - parent_data.surface_id.clone(), - positioner, - )); - let Some(panel_item) = parent_data.panel_item() else {return}; - let Some(popup_wl_surface) = popup_data.lock().wl_surface() else {return}; - handle_cursor( - &panel_item, - panel_item.backend.seat.new_surface(&popup_wl_surface), - ); - let xdg_popup = data_init.init(id, popup_data); - xdg_surface_data.lock().surface_id = SurfaceID::Child(uid); - - xdg_surface_data.lock().panel_item = Arc::downgrade(&panel_item); - debug!(?xdg_popup, ?xdg_surface, "Create XDG popup"); - - let xdg_surface = xdg_surface.downgrade(); - let xdg_popup: WlWeak = xdg_popup.downgrade(); - let Ok(wl_surface) = xdg_surface_data.lock().wl_surface.upgrade() else {return}; - CoreSurface::add_to( - state.display_handle.clone(), - &wl_surface, - move || { - let Ok(xdg_popup) = xdg_popup.upgrade() else {return}; - let Some(popup_data) = PopupData::get(&xdg_popup) else {return}; - let popup_data = popup_data.lock(); - panel_item - .backend - .new_popup(&panel_item, &xdg_popup, &*popup_data); - }, - move |commit_count| { - if commit_count == 0 { - if let Ok(xdg_surface) = xdg_surface.upgrade() { - xdg_surface.configure(SERIAL_COUNTER.inc()) - } - } - }, - ); - } - xdg_surface::Request::SetWindowGeometry { - x, - y, - width, - height, - } => { - debug!( - ?xdg_surface, - x, y, width, height, "Set XDG surface geometry" - ); - let geometry = Geometry { - origin: [x, y].into(), - size: [width as u32, height as u32].into(), - }; - xdg_surface_data.lock().geometry.replace(geometry); - } - xdg_surface::Request::AckConfigure { serial } => { - debug!(?xdg_surface, serial, "Acknowledge XDG surface configure"); - } - xdg_surface::Request::Destroy => { - debug!(?xdg_surface, "Destroy XDG surface"); - } - _ => unreachable!(), - } - } -} - -#[derive(Debug)] -pub struct ToplevelData { - panel_item: OnceCell>>, - xdg_surface: WlWeak, - parent: Option>, - title: Option, - app_id: Option, - max_size: Option>, - min_size: Option>, -} -impl ToplevelData { - fn new(xdg_surface: &XdgSurface) -> Self { - ToplevelData { - panel_item: OnceCell::new(), - xdg_surface: xdg_surface.downgrade(), - parent: None, - title: None, - app_id: None, - max_size: None, - min_size: None, - } - } - - pub fn get(toplevel: &XdgToplevel) -> &Mutex { - toplevel.data::>().unwrap() - } - - pub fn xdg_surface(&self) -> Option { - self.xdg_surface.upgrade().ok() - } - fn panel_item(&self) -> Option>> { - self.panel_item.get()?.upgrade() - } -} -impl Drop for ToplevelData { - fn drop(&mut self) { - let Some(panel_item) = self.panel_item() else {return}; - panel_item.drop_toplevel(); - } -} -impl Dispatch, WaylandState> for WaylandState { - fn request( - _state: &mut WaylandState, - _client: &Client, - xdg_toplevel: &XdgToplevel, - request: xdg_toplevel::Request, - data: &Mutex, - _dhandle: &DisplayHandle, - _data_init: &mut DataInit<'_, WaylandState>, - ) { - match request { - xdg_toplevel::Request::SetParent { parent } => { - debug!(?xdg_toplevel, ?parent, "Set XDG Toplevel parent"); - data.lock().parent = parent.clone().map(|toplevel| toplevel.downgrade()); - let Some(panel_item) = data.lock().panel_item() else {return}; - let Some(parent) = parent else {return}; - let Some(parent_panel_item) = ToplevelData::get(&parent).lock().panel_item() else {return}; - panel_item.toplevel_parent_changed(&parent_panel_item.uid); - } - xdg_toplevel::Request::SetTitle { title } => { - debug!(?xdg_toplevel, ?title, "Set XDG Toplevel title"); - data.lock().title = (!title.is_empty()).then_some(title.clone()); - - let Some(panel_item) = data.lock().panel_item() else {return}; - panel_item.toplevel_title_changed(&title); - } - xdg_toplevel::Request::SetAppId { app_id } => { - debug!(?xdg_toplevel, ?app_id, "Set XDG Toplevel app ID"); - data.lock().app_id = (!app_id.is_empty()).then_some(app_id.clone()); - - let Some(panel_item) = data.lock().panel_item() else {return}; - panel_item.toplevel_app_id_changed(&app_id); - } - xdg_toplevel::Request::Move { seat, serial } => { - debug!(?xdg_toplevel, ?seat, serial, "XDG Toplevel move request"); - let Some(panel_item) = data.lock().panel_item() else {return}; - panel_item.toplevel_move_request(); - } - xdg_toplevel::Request::Resize { - seat, - serial, - edges, - } => { - let WEnum::Value(edges) = edges else {return}; - debug!( - ?xdg_toplevel, - ?seat, - serial, - ?edges, - "XDG Toplevel resize request" - ); - let Some(panel_item) = data.lock().panel_item() else {return}; - let (up, down, left, right) = match edges { - ResizeEdge::Top => (true, false, false, false), - ResizeEdge::Bottom => (false, true, false, false), - ResizeEdge::Left => (false, false, true, false), - ResizeEdge::TopLeft => (true, false, true, false), - ResizeEdge::BottomLeft => (false, true, true, false), - ResizeEdge::Right => (false, false, false, true), - ResizeEdge::TopRight => (true, false, false, true), - ResizeEdge::BottomRight => (false, true, false, true), - _ => (false, false, false, false), - }; - panel_item.toplevel_resize_request(up, down, left, right) - } - xdg_toplevel::Request::SetMaxSize { width, height } => { - debug!(?xdg_toplevel, width, height, "Set XDG Toplevel max size"); - data.lock().max_size = (width > 1 || height > 1) - .then_some(Vector2::from([width as u32, height as u32])); - } - xdg_toplevel::Request::SetMinSize { width, height } => { - debug!(?xdg_toplevel, width, height, "Set XDG Toplevel min size"); - data.lock().min_size = (width > 1 || height > 1) - .then_some(Vector2::from([width as u32, height as u32])); - } - xdg_toplevel::Request::SetFullscreen { output: _ } => { - let Some(panel_item) = data.lock().panel_item() else {return}; - panel_item.backend.toplevel_state.lock().fullscreen = true; - panel_item.backend.configure(None); - panel_item.toplevel_fullscreen_active(true); - } - xdg_toplevel::Request::UnsetFullscreen => { - let Some(panel_item) = data.lock().panel_item() else {return}; - panel_item.backend.toplevel_state.lock().fullscreen = false; - panel_item.backend.configure(None); - panel_item.toplevel_fullscreen_active(false); - } - xdg_toplevel::Request::Destroy => { - debug!(?xdg_toplevel, "Destroy XDG Toplevel"); - let Some(panel_item) = data.lock().panel_item() else {return}; - panel_item.drop_toplevel(); - } - _ => {} - } - } -} - -#[derive(Clone)] -pub struct PopupData { - pub uid: String, - grabbed: bool, - parent_id: SurfaceID, - positioner: XdgPositioner, - xdg_surface: WlWeak, -} -impl PopupData { - fn new( - uid: impl ToString, - xdg_surface: &XdgSurface, - parent_id: SurfaceID, - positioner: XdgPositioner, - ) -> Self { - PopupData { - uid: uid.to_string(), - grabbed: false, - parent_id, - positioner, - xdg_surface: xdg_surface.downgrade(), - } - } - pub fn get(popup: &XdgPopup) -> Option<&Mutex> { - popup.data::>() - } - pub fn xdg_surface(&self) -> Option { - self.xdg_surface.upgrade().ok() - } - - fn panel_item(&self) -> Option>> { - XdgSurfaceData::get(&self.xdg_surface()?)? - .lock() - .panel_item() - } - // fn get_parent(&self) -> Option { - // self.parent.as_ref()?.upgrade().ok() - // } - pub fn wl_surface(&self) -> Option { - XdgSurfaceData::get(&self.xdg_surface()?)? - .lock() - .wl_surface() - } - - pub fn positioner_data(&self) -> Option { - Some( - self.positioner - .data::>()? - .lock() - .clone(), - ) - } -} - -impl Debug for PopupData { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("XdgPopupData") - .field("uid", &self.uid) - .field("positioner", &self.positioner) - .field("xdg_surface", &self.xdg_surface) - .finish() - } -} -impl Dispatch, WaylandState> for WaylandState { - fn request( - _state: &mut WaylandState, - _client: &Client, - xdg_popup: &XdgPopup, - request: xdg_popup::Request, - data: &Mutex, - _dhandle: &DisplayHandle, - _data_init: &mut DataInit<'_, WaylandState>, - ) { - match request { - xdg_popup::Request::Grab { seat, serial } => { - let mut data = data.lock(); - data.grabbed = true; - debug!(?xdg_popup, ?seat, serial, "XDG popup grab"); - let Some(panel_item) = data.panel_item() else {return}; - panel_item.grab_keyboard(Some(SurfaceID::Child(data.uid.clone()))); - } - xdg_popup::Request::Reposition { positioner, token } => { - let mut data = data.lock(); - debug!(?xdg_popup, ?positioner, token, "XDG popup reposition"); - data.positioner = positioner; - let Some(panel_item) = data.panel_item() else {return}; - - panel_item.backend.reposition_popup(&panel_item, &*data) - } - xdg_popup::Request::Destroy => { - let data = data.lock(); - debug!(?xdg_popup, "Destroy XDG popup"); - if data.grabbed { - let Some(panel_item) = data.panel_item() else {return}; - panel_item.grab_keyboard(None); - } - } - _ => unreachable!(), - } - } - - fn destroyed( - _state: &mut WaylandState, - _client: ClientId, - _popup: &XdgPopup, - data: &Mutex, - ) { - let data = data.lock(); - let Some(panel_item) = data.panel_item() else {return}; - panel_item.backend.drop_popup(&panel_item, &data.uid); - } -} - -struct XdgToplevelState { - fullscreen: bool, - activated: bool, -} - -pub struct XDGBackend { - toplevel: WlWeak, - toplevel_wl_surface: WlWeak, - toplevel_state: Mutex, - popups: Mutex>>, - cursor: watch::Receiver>, - seat: Arc, - pointer_grab: Mutex>, - keyboard_grab: Mutex>, -} -impl XDGBackend { - pub fn create(toplevel: XdgToplevel, seat: Arc) -> Option { - let toplevel_wl_surface = - XdgSurfaceData::get(&ToplevelData::get(&toplevel).lock().xdg_surface()?)? - .lock() - .wl_surface()? - .downgrade(); - - let cursor = seat.new_surface(&toplevel_wl_surface.upgrade().ok()?); - Some(XDGBackend { - toplevel: toplevel.downgrade(), - toplevel_wl_surface, - toplevel_state: Mutex::new(XdgToplevelState { - fullscreen: false, - activated: false, - }), - popups: Mutex::new(FxHashMap::default()), - cursor, - seat, - pointer_grab: Mutex::new(None), - keyboard_grab: Mutex::new(None), - }) - } - fn wl_surface_from_id(&self, id: &SurfaceID) -> Option { - match id { - SurfaceID::Cursor => self.cursor.borrow().as_ref()?.surface.upgrade().ok(), - SurfaceID::Toplevel => self.toplevel_wl_surface(), - SurfaceID::Child(popup) => { - let popups = self.popups.lock(); - let popup = popups.get(popup)?.upgrade().ok()?; - let wl_surface = PopupData::get(&popup)?.lock().wl_surface(); - wl_surface - } - } - } - fn toplevel(&self) -> Option { - self.toplevel.upgrade().ok() - } - fn toplevel_xdg_surface(&self) -> Option { - let toplevel = self.toplevel()?; - let data = ToplevelData::get(&toplevel).lock(); - data.xdg_surface() - } - fn toplevel_wl_surface(&self) -> Option { - self.toplevel_wl_surface.upgrade().ok() - } - - fn configure(&self, size: Option>) { - let Ok(xdg_toplevel) = self.toplevel.upgrade() else {return}; - let Some(xdg_surface) = self.toplevel_xdg_surface() else {return}; - let Some(wl_surface) = self.toplevel_wl_surface() else {return}; - let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else {return}; - let Some(surface_size) = core_surface.size() else {return}; - - xdg_toplevel.configure( - size.unwrap_or(surface_size).x as i32, - size.unwrap_or(surface_size).y as i32, - self.states() - .into_iter() - .flat_map(|state| state.to_ne_bytes()) - .collect(), - ); - xdg_surface.configure(SERIAL_COUNTER.inc()); - self.flush_client(); - } - fn states(&self) -> Vec { - let mut states = vec![1, 5, 6, 7, 8]; // maximized always and tiled - let toplevel_state = self.toplevel_state.lock(); - if toplevel_state.fullscreen { - states.push(2); - } - if toplevel_state.activated { - states.push(4); - } - states - } - - pub fn new_popup( - &self, - panel_item: &PanelItem, - popup: &XdgPopup, - data: &PopupData, - ) { - self.popups - .lock() - .insert(data.uid.clone(), popup.downgrade()); - - let Some(positioner_data) = data.positioner_data() else {return}; - panel_item.new_child( - &data.uid, - ChildInfo { - parent: data.parent_id.clone(), - geometry: positioner_data.into(), - }, - ) - } - pub fn reposition_popup(&self, panel_item: &PanelItem, popup_state: &PopupData) { - let Some(positioner_data) = popup_state.positioner_data() else {return}; - panel_item.reposition_child(&popup_state.uid, positioner_data.into()) - } - pub fn drop_popup(&self, panel_item: &PanelItem, uid: &str) { - panel_item.drop_child(uid); - let Some(popup) = self - .popups - .lock() - .remove(uid) else {return}; - let Some(popup) = popup.upgrade().ok() else {return}; - let Some(popup) = popup.data::>().cloned() else {return}; - let Some(wl_surface) = popup.wl_surface() else {return}; - self.seat.drop_surface(&wl_surface); - } - - fn child_data(&self) -> FxHashMap { - FxHashMap::from_iter(self.popups.lock().values().filter_map(|v| { - let popup = v.upgrade().ok()?; - let data = PopupData::get(&popup)?; - let data_lock = data.lock(); - Some(( - data_lock.uid.clone(), - ChildInfo { - parent: data_lock.parent_id.clone(), - geometry: data_lock.positioner_data()?.into(), - }, - )) - })) - } - - fn flush_client(&self) { - let Some(client) = self.toplevel_wl_surface().and_then(|s| s.client()) else {return}; - if let Some(client_state) = client.get_data::() { - client_state.flush(); - } - } -} -impl Drop for XDGBackend { - fn drop(&mut self) { - let Some(toplevel) = self.toplevel_wl_surface() else {return}; - self.seat.drop_surface(&toplevel); - debug!("Dropped panel item gracefully"); - } -} -impl Backend for XDGBackend { - fn start_data(&self) -> Result { - let toplevel = self.toplevel(); - let toplevel_data = toplevel - .as_ref() - .map(|t| ToplevelData::get(t).lock()) - .ok_or_else(|| eyre!("Could not get toplevel"))?; - - let parent = toplevel_data - .parent - .as_ref() - .and_then(|p| p.upgrade().ok()) - .and_then(|p| ToplevelData::get(&p).lock().panel_item()) - .map(|p| p.uid.clone()); - let title = toplevel_data.title.clone(); - let app_id = toplevel_data.app_id.clone(); - let min_size = toplevel_data.min_size.clone(); - let max_size = toplevel_data.max_size.clone(); - drop(toplevel_data); - - let pointer_grab = self.pointer_grab.lock().clone(); - let keyboard_grab = self.keyboard_grab.lock().clone(); - - let Some(wl_surface) = self.toplevel_wl_surface() else {bail!("Wayland surface not found")}; - let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else {bail!("Core surface not found")}; - let Some(size) = core_surface.size() else {bail!("Surface size not found")}; - - let logical_rectangle = self - .toplevel_xdg_surface() - .as_ref() - .and_then(XdgSurfaceData::get) - .and_then(|d| d.lock().geometry.clone()) - .unwrap_or_else(|| Geometry { - origin: [0, 0].into(), - size, - }); - - let toplevel = ToplevelInfo { - parent, - title, - app_id, - size, - min_size, - max_size, - logical_rectangle, - }; - - Ok(PanelItemInitData { - cursor: self.cursor.borrow().as_ref().and_then(|c| c.cursor_data()), - toplevel, - children: self.child_data(), - pointer_grab, - keyboard_grab, - }) - } - - fn apply_surface_material(&self, surface: SurfaceID, model_part: &Arc) { - let Some(wl_surface) = self.wl_surface_from_id(&surface) else {return}; - let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else {return}; - - core_surface.apply_material(model_part); - } - - fn close_toplevel(&self) { - let Ok(xdg_toplevel) = self.toplevel.upgrade() else {return}; - xdg_toplevel.close(); - } - fn auto_size_toplevel(&self) { - self.configure(Some([0, 0].into())); - } - fn set_toplevel_size(&self, size: Vector2) { - self.configure(Some(size)); - } - fn set_toplevel_focused_visuals(&self, focused: bool) { - self.toplevel_state.lock().activated = focused; - self.configure(None); - } - - fn pointer_motion(&self, surface: &SurfaceID, position: Vector2) { - let Some(surface) = self.wl_surface_from_id(surface) else {return}; - self.seat - .pointer_event(&surface, PointerEvent::Motion(position)); - } - fn pointer_button(&self, surface: &SurfaceID, button: u32, pressed: bool) { - let Some(surface) = self.wl_surface_from_id(surface) else {return}; - self.seat.pointer_event( - &surface, - PointerEvent::Button { - button, - state: if pressed { 1 } else { 0 }, - }, - ) - } - fn pointer_scroll( - &self, - surface: &SurfaceID, - scroll_distance: Option>, - scroll_steps: Option>, - ) { - let Some(surface) = self.wl_surface_from_id(surface) else {return}; - self.seat.pointer_event( - &surface, - PointerEvent::Scroll { - axis_continuous: scroll_distance, - axis_discrete: scroll_steps, - }, - ) - } - - fn keyboard_keys(&self, surface: &SurfaceID, keymap_id: &str, keys: Vec) { - let Some(surface) = self.wl_surface_from_id(surface) else {return}; - let keymaps = KEYMAPS.lock(); - let Some(keymap) = keymaps.get(keymap_id).cloned() else {return}; - if self.seat.set_keymap(keymap, vec![surface.clone()]).is_err() { - return; - } - for key in keys { - self.seat.keyboard_event( - &surface, - KeyboardEvent::Key { - key: key.abs() as u32, - state: key > 0, - }, - ); - } - } - - fn touch_down(&self, surface: &SurfaceID, id: u32, position: Vector2) { - let Some(surface) = self.wl_surface_from_id(surface) else {return}; - self.seat.touch_down(&surface, id, position) - } - fn touch_move(&self, id: u32, position: Vector2) { - self.seat.touch_move(id, position) - } - fn touch_up(&self, id: u32) { - self.seat.touch_up(id) - } - fn reset_touches(&self) { - self.seat.reset_touches() - } -} diff --git a/src/wayland/xdg_shell/backend.rs b/src/wayland/xdg_shell/backend.rs new file mode 100644 index 00000000..cd324d7e --- /dev/null +++ b/src/wayland/xdg_shell/backend.rs @@ -0,0 +1,310 @@ +use super::{popup::PopupData, surface::XdgSurfaceData, ToplevelData}; +use crate::{ + nodes::{ + data::KEYMAPS, + drawable::model::ModelPart, + items::panel::{Backend, ChildInfo, PanelItem, PanelItemInitData, SurfaceID}, + }, + wayland::{ + seat::{CursorInfo, KeyboardEvent, PointerEvent, SeatData}, + state::ClientState, + surface::CoreSurface, + utils, SERIAL_COUNTER, + }, +}; +use color_eyre::eyre::{eyre, Result}; +use mint::Vector2; +use parking_lot::Mutex; +use rustc_hash::FxHashMap; +use smithay::reexports::{ + wayland_protocols::xdg::shell::server::xdg_toplevel::XdgToplevel, + wayland_server::{protocol::wl_surface::WlSurface, Resource, Weak}, +}; +use std::sync::Arc; +use tokio::sync::watch; +use tracing::debug; + +pub struct XdgToplevelState { + pub fullscreen: bool, + pub activated: bool, +} + +pub struct XdgBackend { + toplevel: Weak, + toplevel_wl_surface: Weak, + pub toplevel_state: Mutex, + popups: Mutex>>, + pub cursor: watch::Receiver>, + pub seat: Arc, + pointer_grab: Mutex>, + keyboard_grab: Mutex>, +} +impl XdgBackend { + pub fn create( + toplevel_wl_surface: WlSurface, + toplevel: XdgToplevel, + seat: Arc, + ) -> Self { + let cursor = seat.new_surface(&toplevel_wl_surface); + XdgBackend { + toplevel: toplevel.downgrade(), + toplevel_wl_surface: toplevel_wl_surface.downgrade(), + toplevel_state: Mutex::new(XdgToplevelState { + fullscreen: false, + activated: false, + }), + popups: Mutex::new(FxHashMap::default()), + cursor, + seat, + pointer_grab: Mutex::new(None), + keyboard_grab: Mutex::new(None), + } + } + fn wl_surface_from_id(&self, id: &SurfaceID) -> Option { + match id { + SurfaceID::Cursor => self.cursor.borrow().as_ref()?.surface.upgrade().ok(), + SurfaceID::Toplevel => self.toplevel_wl_surface(), + SurfaceID::Child(popup) => { + let popups = self.popups.lock(); + popups.get(popup)?.upgrade().ok() + } + } + } + fn toplevel_wl_surface(&self) -> Option { + self.toplevel_wl_surface.upgrade().ok() + } + + pub fn configure(&self, size: Option>) { + let Ok(xdg_toplevel) = self.toplevel.upgrade() else { + return; + }; + let Some(wl_surface) = self.toplevel_wl_surface() else { + return; + }; + let Some(xdg_surface_data) = wl_surface.data::() else { + return; + }; + let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else { + return; + }; + let Some(surface_size) = core_surface.size() else { + return; + }; + + xdg_toplevel.configure( + size.unwrap_or(surface_size).x as i32, + size.unwrap_or(surface_size).y as i32, + self.states() + .into_iter() + .flat_map(|state| state.to_ne_bytes()) + .collect(), + ); + xdg_surface_data.xdg_surface.configure(SERIAL_COUNTER.inc()); + self.flush_client(); + } + fn states(&self) -> Vec { + let mut states = vec![1, 5, 6, 7, 8]; // maximized always and tiled + let toplevel_state = self.toplevel_state.lock(); + if toplevel_state.fullscreen { + states.push(2); + } + if toplevel_state.activated { + states.push(4); + } + states + } + + pub fn new_popup( + &self, + panel_item: &PanelItem, + popup_wl_surface: &WlSurface, + data: &PopupData, + ) { + self.popups + .lock() + .insert(data.uid.clone(), popup_wl_surface.downgrade()); + + let Some(geometry) = data.geometry() else { + return; + }; + panel_item.new_child( + &data.uid, + ChildInfo { + parent: utils::get_data::(&data.parent()) + .unwrap() + .as_ref() + .clone(), + geometry, + }, + ) + } + pub fn reposition_popup(&self, panel_item: &PanelItem, popup_state: &PopupData) { + let Some(geometry) = popup_state.geometry() else { + return; + }; + panel_item.reposition_child(&popup_state.uid, geometry) + } + pub fn drop_popup(&self, panel_item: &PanelItem, uid: &str) { + panel_item.drop_child(uid); + let Some(popup) = self.popups.lock().remove(uid) else { + return; + }; + let Some(wl_surface) = popup.upgrade().ok() else { + return; + }; + self.seat.drop_surface(&wl_surface); + } + + fn child_data(&self) -> FxHashMap { + FxHashMap::from_iter(self.popups.lock().iter().filter_map(|(uid, v)| { + let wl_surface = v.upgrade().ok()?; + let popup_data = utils::get_data::(&wl_surface)?; + let parent = utils::get_data::(&popup_data.parent())? + .as_ref() + .clone(); + let geometry = utils::get_data::(&wl_surface)? + .geometry + .lock() + .clone()?; + Some((uid.clone(), ChildInfo { parent, geometry })) + })) + } + + fn flush_client(&self) { + let Some(client) = self.toplevel_wl_surface().and_then(|s| s.client()) else { + return; + }; + if let Some(client_state) = client.get_data::() { + client_state.flush(); + } + } +} +impl Drop for XdgBackend { + fn drop(&mut self) { + debug!("Dropped panel item gracefully"); + } +} +impl Backend for XdgBackend { + fn start_data(&self) -> Result { + let toplevel = self.toplevel_wl_surface(); + let toplevel_data = toplevel.as_ref().and_then(utils::get_data::); + let toplevel_data = toplevel_data + .as_deref() + .clone() + .ok_or_else(|| eyre!("Could not get toplevel"))?; + + let pointer_grab = self.pointer_grab.lock().clone(); + let keyboard_grab = self.keyboard_grab.lock().clone(); + + Ok(PanelItemInitData { + cursor: self.cursor.borrow().as_ref().and_then(|c| c.cursor_data()), + toplevel: toplevel_data.into(), + children: self.child_data(), + pointer_grab, + keyboard_grab, + }) + } + + fn apply_surface_material(&self, surface: SurfaceID, model_part: &Arc) { + let Some(wl_surface) = self.wl_surface_from_id(&surface) else { + return; + }; + let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else { + return; + }; + + core_surface.apply_material(model_part); + } + + fn close_toplevel(&self) { + let Ok(xdg_toplevel) = self.toplevel.upgrade() else { + return; + }; + xdg_toplevel.close(); + } + fn auto_size_toplevel(&self) { + self.configure(Some([0, 0].into())); + } + fn set_toplevel_size(&self, size: Vector2) { + self.configure(Some(size)); + } + fn set_toplevel_focused_visuals(&self, focused: bool) { + self.toplevel_state.lock().activated = focused; + self.configure(None); + } + + fn pointer_motion(&self, surface: &SurfaceID, position: Vector2) { + let Some(surface) = self.wl_surface_from_id(surface) else { + return; + }; + self.seat + .pointer_event(&surface, PointerEvent::Motion(position)); + } + fn pointer_button(&self, surface: &SurfaceID, button: u32, pressed: bool) { + let Some(surface) = self.wl_surface_from_id(surface) else { + return; + }; + self.seat.pointer_event( + &surface, + PointerEvent::Button { + button, + state: if pressed { 1 } else { 0 }, + }, + ) + } + fn pointer_scroll( + &self, + surface: &SurfaceID, + scroll_distance: Option>, + scroll_steps: Option>, + ) { + let Some(surface) = self.wl_surface_from_id(surface) else { + return; + }; + self.seat.pointer_event( + &surface, + PointerEvent::Scroll { + axis_continuous: scroll_distance, + axis_discrete: scroll_steps, + }, + ) + } + + fn keyboard_keys(&self, surface: &SurfaceID, keymap_id: &str, keys: Vec) { + let Some(surface) = self.wl_surface_from_id(surface) else { + return; + }; + let keymaps = KEYMAPS.lock(); + let Some(keymap) = keymaps.get(keymap_id).cloned() else { + return; + }; + if self.seat.set_keymap(keymap, vec![surface.clone()]).is_err() { + return; + } + for key in keys { + self.seat.keyboard_event( + &surface, + KeyboardEvent::Key { + key: key.abs() as u32, + state: key > 0, + }, + ); + } + } + + fn touch_down(&self, surface: &SurfaceID, id: u32, position: Vector2) { + let Some(surface) = self.wl_surface_from_id(surface) else { + return; + }; + self.seat.touch_down(&surface, id, position) + } + fn touch_move(&self, id: u32, position: Vector2) { + self.seat.touch_move(id, position) + } + fn touch_up(&self, id: u32) { + self.seat.touch_up(id) + } + fn reset_touches(&self) { + self.seat.reset_touches() + } +} diff --git a/src/wayland/xdg_shell/mod.rs b/src/wayland/xdg_shell/mod.rs new file mode 100644 index 00000000..89c60af8 --- /dev/null +++ b/src/wayland/xdg_shell/mod.rs @@ -0,0 +1,69 @@ +use self::{backend::XdgBackend, toplevel::ToplevelData}; +use super::state::WaylandState; +use crate::wayland::{ + utils::insert_data, + xdg_shell::{positioner::PositionerData, surface::XdgSurfaceData}, +}; +use parking_lot::Mutex; +use smithay::reexports::{ + wayland_protocols::xdg::shell::server::xdg_wm_base::{self, XdgWmBase}, + wayland_server::{Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource}, +}; +use tracing::debug; + +mod backend; +mod popup; +mod positioner; +mod surface; +mod toplevel; + +impl GlobalDispatch for WaylandState { + fn bind( + _state: &mut WaylandState, + _handle: &DisplayHandle, + _client: &Client, + resource: New, + _global_data: &(), + data_init: &mut DataInit<'_, WaylandState>, + ) { + data_init.init(resource, ()); + } +} + +impl Dispatch for WaylandState { + fn request( + _state: &mut WaylandState, + _client: &Client, + _resource: &XdgWmBase, + request: xdg_wm_base::Request, + _data: &(), + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, WaylandState>, + ) { + match request { + xdg_wm_base::Request::CreatePositioner { id } => { + let positioner = data_init.init(id, Mutex::new(PositionerData::default())); + debug!(?positioner, "Create XDG positioner"); + } + xdg_wm_base::Request::GetXdgSurface { id, surface } => { + let xdg_surface = data_init.init(id, surface.downgrade()); + debug!(?xdg_surface, "Create XDG surface"); + insert_data( + &surface, + XdgSurfaceData { + wl_surface: surface.downgrade(), + xdg_surface, + geometry: Mutex::new(None), + }, + ); + } + xdg_wm_base::Request::Pong { serial } => { + debug!(serial, "Client pong"); + } + xdg_wm_base::Request::Destroy => { + debug!("Destroy XDG WM base"); + } + _ => unreachable!(), + } + } +} diff --git a/src/wayland/xdg_shell/popup.rs b/src/wayland/xdg_shell/popup.rs new file mode 100644 index 00000000..a4743350 --- /dev/null +++ b/src/wayland/xdg_shell/popup.rs @@ -0,0 +1,119 @@ +use super::{backend::XdgBackend, positioner::PositionerData}; +use crate::{ + nodes::items::panel::{Geometry, PanelItem, SurfaceID}, + wayland::{state::WaylandState, utils::get_data}, +}; +use parking_lot::Mutex; +use smithay::reexports::{ + wayland_protocols::xdg::shell::server::{ + xdg_popup::{self, XdgPopup}, + xdg_positioner::XdgPositioner, + }, + wayland_server::{ + protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle, Resource, + Weak as WlWeak, + }, +}; +use std::sync::{Arc, Weak}; +use tracing::{debug, error}; +use wayland_backend::server::ClientId; + +#[derive(Debug)] +pub struct PopupData { + pub uid: String, + grabbed: Mutex, + parent: Mutex>, + panel_item: Weak>, + positioner: Mutex, +} +impl PopupData { + pub fn new( + uid: impl ToString, + parent: WlSurface, + panel_item: &Arc>, + positioner: XdgPositioner, + ) -> Self { + PopupData { + uid: uid.to_string(), + grabbed: Mutex::new(false), + parent: Mutex::new(parent.downgrade()), + panel_item: Arc::downgrade(panel_item), + positioner: Mutex::new(positioner), + } + } + pub fn geometry(&self) -> Option { + let positioner = self.positioner.lock().clone(); + let positioner_data = positioner.data::>()?.lock(); + Some(positioner_data.clone().into()) + } + pub fn parent(&self) -> WlSurface { + self.parent.lock().upgrade().unwrap() + } +} + +impl Dispatch, WaylandState> for WaylandState { + fn request( + _state: &mut WaylandState, + _client: &Client, + xdg_popup: &XdgPopup, + request: xdg_popup::Request, + wl_surface_resource: &WlWeak, + _dhandle: &DisplayHandle, + _data_init: &mut DataInit<'_, WaylandState>, + ) { + let Ok(wl_surface) = wl_surface_resource.upgrade() else { + error!("Couldn't get the wayland surface of the xdg popup"); + return; + }; + let Some(popup_data) = get_data::(&wl_surface) else { + error!("Couldn't get the XdgPopup"); + return; + }; + let Some(panel_item) = popup_data.panel_item.upgrade() else { + error!("Couldn't get the panel item"); + return; + }; + match request { + xdg_popup::Request::Grab { seat, serial } => { + *popup_data.grabbed.lock() = true; + debug!(?xdg_popup, ?seat, serial, "XDG popup grab"); + panel_item.grab_keyboard(Some(SurfaceID::Child(popup_data.uid.clone()))); + } + xdg_popup::Request::Reposition { positioner, token } => { + debug!(?xdg_popup, ?positioner, token, "XDG popup reposition"); + *popup_data.positioner.lock() = positioner; + panel_item + .backend + .reposition_popup(&panel_item, &popup_data); + } + xdg_popup::Request::Destroy => { + debug!(?xdg_popup, "Destroy XDG popup"); + if *popup_data.grabbed.lock() { + panel_item.grab_keyboard(None); + } + } + _ => unreachable!(), + } + } + + fn destroyed( + _state: &mut WaylandState, + _client: ClientId, + _popup: &XdgPopup, + data: &WlWeak, + ) { + let Ok(wl_surface) = data.upgrade() else { + error!("Couldn't get the wayland surface of the xdg popup"); + return; + }; + let Some(popup_data) = get_data::(&wl_surface) else { + error!("Couldn't get the XdgPopup"); + return; + }; + let Some(panel_item) = popup_data.panel_item.upgrade() else { + error!("Couldn't get the panel item"); + return; + }; + panel_item.backend.drop_popup(&panel_item, &popup_data.uid); + } +} diff --git a/src/wayland/xdg_shell/positioner.rs b/src/wayland/xdg_shell/positioner.rs new file mode 100644 index 00000000..24caf263 --- /dev/null +++ b/src/wayland/xdg_shell/positioner.rs @@ -0,0 +1,226 @@ +use crate::{nodes::items::panel::Geometry, wayland::state::WaylandState}; +use mint::Vector2; +use parking_lot::Mutex; +use smithay::reexports::{ + wayland_protocols::xdg::shell::server::xdg_positioner::{ + self, Anchor, ConstraintAdjustment, Gravity, XdgPositioner, + }, + wayland_server::{Client, DataInit, Dispatch, DisplayHandle, Resource}, +}; +use tracing::{debug, warn}; +use wayland_backend::protocol::WEnum; + +#[derive(Debug, Clone, Copy)] +pub struct PositionerData { + size: Vector2, + anchor_rect_pos: Vector2, + anchor_rect_size: Vector2, + anchor: Anchor, + gravity: Gravity, + constraint_adjustment: ConstraintAdjustment, + offset: Vector2, + reactive: bool, +} +impl Default for PositionerData { + fn default() -> Self { + Self { + size: Vector2::from([0; 2]), + anchor_rect_pos: Vector2::from([0; 2]), + anchor_rect_size: Vector2::from([0; 2]), + anchor: Anchor::None, + gravity: Gravity::None, + constraint_adjustment: ConstraintAdjustment::None, + offset: Vector2::from([0; 2]), + reactive: false, + } + } +} + +impl PositionerData { + fn anchor_has_edge(&self, edge: Anchor) -> bool { + match edge { + Anchor::Top => { + self.anchor == Anchor::Top + || self.anchor == Anchor::TopLeft + || self.anchor == Anchor::TopRight + } + Anchor::Bottom => { + self.anchor == Anchor::Bottom + || self.anchor == Anchor::BottomLeft + || self.anchor == Anchor::BottomRight + } + Anchor::Left => { + self.anchor == Anchor::Left + || self.anchor == Anchor::TopLeft + || self.anchor == Anchor::BottomLeft + } + Anchor::Right => { + self.anchor == Anchor::Right + || self.anchor == Anchor::TopRight + || self.anchor == Anchor::BottomRight + } + _ => unreachable!(), + } + } + + fn gravity_has_edge(&self, edge: Gravity) -> bool { + match edge { + Gravity::Top => { + self.gravity == Gravity::Top + || self.gravity == Gravity::TopLeft + || self.gravity == Gravity::TopRight + } + Gravity::Bottom => { + self.gravity == Gravity::Bottom + || self.gravity == Gravity::BottomLeft + || self.gravity == Gravity::BottomRight + } + Gravity::Left => { + self.gravity == Gravity::Left + || self.gravity == Gravity::TopLeft + || self.gravity == Gravity::BottomLeft + } + Gravity::Right => { + self.gravity == Gravity::Right + || self.gravity == Gravity::TopRight + || self.gravity == Gravity::BottomRight + } + _ => unreachable!(), + } + } + + pub fn get_pos(&self) -> Vector2 { + let mut pos = self.offset; + + if self.anchor_has_edge(Anchor::Top) { + pos.y += self.anchor_rect_pos.y; + } else if self.anchor_has_edge(Anchor::Bottom) { + pos.y += self.anchor_rect_pos.y + self.anchor_rect_size.y as i32; + } else { + pos.y += self.anchor_rect_pos.y + self.anchor_rect_size.y as i32 / 2; + } + + if self.anchor_has_edge(Anchor::Left) { + pos.x += self.anchor_rect_pos.x; + } else if self.anchor_has_edge(Anchor::Right) { + pos.x += self.anchor_rect_pos.x + self.anchor_rect_size.x as i32; + } else { + pos.x += self.anchor_rect_pos.x + self.anchor_rect_size.x as i32 / 2; + } + + if self.gravity_has_edge(Gravity::Top) { + pos.y -= self.size.y as i32; + } else if !self.gravity_has_edge(Gravity::Bottom) { + pos.y -= self.size.y as i32 / 2; + } + + if self.gravity_has_edge(Gravity::Left) { + pos.x -= self.size.x as i32; + } else if !self.gravity_has_edge(Gravity::Right) { + pos.x -= self.size.x as i32 / 2; + } + + pos + } +} +impl From for Geometry { + fn from(value: PositionerData) -> Self { + Geometry { + origin: value.get_pos(), + size: value.size, + } + } +} + +impl Dispatch, WaylandState> for WaylandState { + fn request( + _state: &mut WaylandState, + _client: &Client, + positioner: &XdgPositioner, + request: xdg_positioner::Request, + data: &Mutex, + _dhandle: &DisplayHandle, + _data_init: &mut DataInit<'_, WaylandState>, + ) { + match request { + xdg_positioner::Request::SetSize { width, height } => { + debug!(?positioner, width, height, "Set positioner size"); + data.lock().size = Vector2::from([width as u32, height as u32]); + } + xdg_positioner::Request::SetAnchorRect { + x, + y, + width, + height, + } => { + if width < 1 || height < 1 { + positioner.post_error( + xdg_positioner::Error::InvalidInput, + "Invalid size for positioner's anchor rectangle.", + ); + warn!( + ?positioner, + width, height, "Invalid size for positioner's anchor rectangle" + ); + return; + } + + debug!( + ?positioner, + x, y, width, height, "Set positioner anchor rectangle" + ); + let mut data = data.lock(); + data.anchor_rect_pos = [x, y].into(); + data.anchor_rect_size = [width as u32, height as u32].into(); + } + xdg_positioner::Request::SetAnchor { anchor } => { + if let WEnum::Value(anchor) = anchor { + debug!(?positioner, ?anchor, "Set positioner anchor"); + data.lock().anchor = anchor; + } + } + xdg_positioner::Request::SetGravity { gravity } => { + if let WEnum::Value(gravity) = gravity { + debug!(?positioner, ?gravity, "Set positioner gravity"); + data.lock().gravity = gravity; + } + } + xdg_positioner::Request::SetConstraintAdjustment { + constraint_adjustment, + } => { + debug!( + ?positioner, + constraint_adjustment, "Set positioner constraint adjustment" + ); + let Some(constraint_adjustment) = + ConstraintAdjustment::from_bits(constraint_adjustment) + else { + return; + }; + data.lock().constraint_adjustment = constraint_adjustment; + } + xdg_positioner::Request::SetOffset { x, y } => { + debug!(?positioner, x, y, "Set positioner offset"); + data.lock().offset = [x, y].into(); + } + xdg_positioner::Request::SetReactive => { + debug!(?positioner, "Set positioner reactive"); + data.lock().reactive = true; + } + xdg_positioner::Request::SetParentSize { + parent_width, + parent_height, + } => { + debug!( + ?positioner, + parent_width, parent_height, "Set positioner parent size" + ); + } + xdg_positioner::Request::SetParentConfigure { serial } => { + debug!(?positioner, serial, "Set positioner parent size"); + } + xdg_positioner::Request::Destroy => (), + _ => unreachable!(), + } + } +} diff --git a/src/wayland/xdg_shell/surface.rs b/src/wayland/xdg_shell/surface.rs new file mode 100644 index 00000000..6cfde1c9 --- /dev/null +++ b/src/wayland/xdg_shell/surface.rs @@ -0,0 +1,236 @@ +use crate::{ + nodes::items::panel::{Geometry, PanelItem, SurfaceID}, + wayland::{ + seat::handle_cursor, + state::{ClientState, WaylandState}, + surface::CoreSurface, + utils, + xdg_shell::{popup::PopupData, toplevel::ToplevelData, XdgBackend}, + SERIAL_COUNTER, + }, +}; +use nanoid::nanoid; +use parking_lot::Mutex; +use smithay::reexports::{ + wayland_protocols::xdg::shell::server::{ + xdg_surface::{self, XdgSurface}, + xdg_toplevel::{XdgToplevel, EVT_WM_CAPABILITIES_SINCE}, + }, + wayland_server::{ + protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle, Resource, + Weak as WlWeak, + }, +}; +use std::sync::{Arc, Weak}; +use tracing::{debug, error}; + +#[derive(Debug)] +pub struct XdgSurfaceData { + pub wl_surface: WlWeak, + pub xdg_surface: XdgSurface, + pub geometry: Mutex>, +} + +impl Dispatch, WaylandState> for WaylandState { + fn request( + state: &mut WaylandState, + client: &Client, + xdg_surface: &XdgSurface, + request: xdg_surface::Request, + wl_surface_resource: &WlWeak, + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, WaylandState>, + ) { + let Ok(wl_surface) = wl_surface_resource.upgrade() else { + error!("Couldn't get the wayland surface of the xdg surface"); + return; + }; + let Some(xdg_surface_data) = utils::get_data::(&wl_surface) else { + error!("Couldn't get the XdgSurface"); + return; + }; + match request { + xdg_surface::Request::GetToplevel { id } => { + let toplevel = data_init.init(id, wl_surface_resource.clone()); + utils::insert_data(&wl_surface, SurfaceID::Toplevel); + utils::insert_data(&wl_surface, toplevel.clone()); + utils::insert_data(&wl_surface, ToplevelData::new(&wl_surface)); + debug!(?toplevel, ?xdg_surface, "Create XDG toplevel"); + + if toplevel.version() >= EVT_WM_CAPABILITIES_SINCE { + toplevel.wm_capabilities(vec![3]); + } + toplevel.configure( + 0, + 0, + if toplevel.version() >= 2 { + vec![1, 5, 6, 7, 8] + .into_iter() + .flat_map(u32::to_ne_bytes) + .collect() + } else { + vec![] + }, + ); + xdg_surface.configure(SERIAL_COUNTER.inc()); + + let client_credentials = client.get_credentials(&state.display_handle).ok(); + let Some(seat_data) = client.get_data::().map(|s| s.seat.clone()) + else { + return; + }; + + let xdg_surface = xdg_surface.clone(); + CoreSurface::add_to( + state.display_handle.clone(), + &wl_surface, + { + let wl_surface_resource = wl_surface_resource.clone(); + move || { + let wl_surface = wl_surface_resource.upgrade().unwrap(); + + let backend = XdgBackend::create( + wl_surface.clone(), + toplevel.clone(), + seat_data.clone(), + ); + let (node, panel_item) = PanelItem::create( + Box::new(backend), + client_credentials.map(|c| c.pid), + ); + utils::insert_data(&wl_surface, Arc::downgrade(&panel_item)); + utils::insert_data_raw(&wl_surface, node); + handle_cursor(&panel_item, panel_item.backend.cursor.clone()); + } + }, + { + let wl_surface_resource = wl_surface_resource.clone(); + move |_| { + let wl_surface = wl_surface_resource.upgrade().unwrap(); + + let Some(panel_item) = + utils::get_data::>(&wl_surface) + else { + let Some(toplevel) = utils::get_data::(&wl_surface) + else { + return; + }; + // if the wayland toplevel isn't mapped, hammer it again with a configure until it cooperates + toplevel.configure( + 0, + 0, + if toplevel.version() >= 2 { + vec![5, 6, 7, 8] + .into_iter() + .flat_map(u32::to_ne_bytes) + .collect() + } else { + vec![] + }, + ); + xdg_surface.configure(SERIAL_COUNTER.inc()); + return; + }; + let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) + else { + return; + }; + let Some(size) = core_surface.size() else { + return; + }; + panel_item.toplevel_size_changed(size); + } + }, + ); + } + xdg_surface::Request::GetPopup { + id, + parent, + positioner, + } => { + let Some(parent) = parent else { return }; + let Some(parent_wl_surface) = parent + .data::>() + .map(WlWeak::upgrade) + .map(Result::ok) + .flatten() + else { + return; + }; + let Some(panel_item) = + utils::get_data::>>(&parent_wl_surface) + .as_deref() + .and_then(Weak::upgrade) + else { + return; + }; + + let uid = nanoid!(); + let popup_data = PopupData::new( + uid.clone(), + parent_wl_surface.clone(), + &panel_item, + positioner, + ); + handle_cursor( + &panel_item, + panel_item.backend.seat.new_surface(&wl_surface), + ); + let xdg_popup = data_init.init(id, wl_surface.downgrade()); + utils::insert_data(&wl_surface, SurfaceID::Child(uid)); + utils::insert_data(&wl_surface, Arc::downgrade(&panel_item)); + utils::insert_data(&wl_surface, popup_data); + utils::insert_data(&wl_surface, xdg_popup.clone()); + debug!(?xdg_popup, ?xdg_surface, "Create XDG popup"); + + let xdg_surface = xdg_surface.downgrade(); + let popup_wl_surface = wl_surface.downgrade(); + CoreSurface::add_to( + state.display_handle.clone(), + &wl_surface, + move || { + let Ok(wl_surface) = popup_wl_surface.upgrade() else { + return; + }; + let Some(popup_data) = utils::get_data::(&wl_surface) else { + return; + }; + panel_item + .backend + .new_popup(&panel_item, &wl_surface, &*popup_data); + }, + move |commit_count| { + if commit_count == 0 { + if let Ok(xdg_surface) = xdg_surface.upgrade() { + xdg_surface.configure(SERIAL_COUNTER.inc()) + } + } + }, + ); + } + xdg_surface::Request::SetWindowGeometry { + x, + y, + width, + height, + } => { + debug!( + ?xdg_surface, + x, y, width, height, "Set XDG surface geometry" + ); + let geometry = Geometry { + origin: [x, y].into(), + size: [width as u32, height as u32].into(), + }; + xdg_surface_data.geometry.lock().replace(geometry); + } + xdg_surface::Request::AckConfigure { serial } => { + debug!(?xdg_surface, serial, "Acknowledge XDG surface configure"); + } + xdg_surface::Request::Destroy => { + debug!(?xdg_surface, "Destroy XDG surface"); + } + _ => unreachable!(), + } + } +} diff --git a/src/wayland/xdg_shell/toplevel.rs b/src/wayland/xdg_shell/toplevel.rs new file mode 100644 index 00000000..f816e31f --- /dev/null +++ b/src/wayland/xdg_shell/toplevel.rs @@ -0,0 +1,239 @@ +use super::{backend::XdgBackend, surface::XdgSurfaceData}; +use crate::{ + nodes::items::panel::{Geometry, PanelItem, ToplevelInfo}, + wayland::{ + state::WaylandState, + surface::CoreSurface, + utils::{self, get_data}, + }, +}; +use mint::Vector2; +use once_cell::sync::OnceCell; +use parking_lot::Mutex; +use smithay::reexports::{ + wayland_protocols::xdg::shell::server::xdg_toplevel::{self, ResizeEdge, XdgToplevel}, + wayland_server::{ + protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle, Resource, + Weak as WlWeak, + }, +}; +use std::sync::Weak; +use tracing::{debug, error}; +use wayland_backend::protocol::WEnum; + +pub struct ToplevelData { + panel_item: OnceCell>>, + wl_surface: WlWeak, + parent: Mutex>>, + title: Mutex>, + app_id: Mutex>, + max_size: Mutex>>, + min_size: Mutex>>, +} +impl ToplevelData { + pub fn new(wl_surface: &WlSurface) -> Self { + ToplevelData { + panel_item: OnceCell::new(), + wl_surface: wl_surface.downgrade(), + parent: Mutex::new(None), + title: Mutex::new(None), + app_id: Mutex::new(None), + max_size: Mutex::new(None), + min_size: Mutex::new(None), + } + } + pub fn parent(&self) -> Option { + self.parent + .lock() + .as_ref() + .map(WlWeak::upgrade) + .map(Result::ok) + .flatten() + } +} +impl From<&ToplevelData> for ToplevelInfo { + fn from(value: &ToplevelData) -> Self { + let wl_surface = value.wl_surface.upgrade().ok(); + let size = CoreSurface::from_wl_surface(wl_surface.as_ref().unwrap()) + .unwrap() + .size() + .unwrap(); + let logical_rectangle = wl_surface + .as_ref() + .and_then(utils::get_data::) + .and_then(|d| d.geometry.lock().clone()) + .unwrap_or_else(|| Geometry { + origin: [0, 0].into(), + size, + }); + let parent = value + .parent() + .as_ref() + .and_then(utils::get_data::>>) + .as_deref() + .and_then(Weak::upgrade) + .map(|i| i.uid.clone()); + ToplevelInfo { + parent, + title: value.title.lock().clone(), + app_id: value.app_id.lock().clone(), + size, + min_size: value.min_size.lock().clone(), + max_size: value.max_size.lock().clone(), + logical_rectangle, + } + } +} +impl Drop for ToplevelData { + fn drop(&mut self) { + // let Some(panel_item) = self.panel_item.get().and_then(Weak::upgrade) else { + // return; + // }; + // panel_item.drop_toplevel(); + } +} + +impl Dispatch, WaylandState> for WaylandState { + fn request( + _state: &mut WaylandState, + _client: &Client, + xdg_toplevel: &XdgToplevel, + request: xdg_toplevel::Request, + wl_surface_resource: &WlWeak, + _dhandle: &DisplayHandle, + _data_init: &mut DataInit<'_, WaylandState>, + ) { + let Ok(wl_surface) = wl_surface_resource.upgrade() else { + error!("Couldn't get the wayland surface of the xdg toplevel"); + return; + }; + let Some(toplevel_data) = utils::get_data::(&wl_surface) else { + error!("Couldn't get the XdgToplevel"); + return; + }; + match request { + xdg_toplevel::Request::SetParent { parent } => { + debug!(?xdg_toplevel, ?parent, "Set XDG Toplevel parent"); + let Some(parent_xdg_toplevel) = parent else { + *toplevel_data.parent.lock() = None; + return; + }; + let Some(parent_toplevel_data) = parent_xdg_toplevel.data::() else { + error!("Couldn't get XDG toplevel parent data"); + return; + }; + let Ok(parent_wl_surface) = parent_toplevel_data.wl_surface.upgrade() else { + error!("Couldn't get XDG toplevel parent wl surface"); + return; + }; + *toplevel_data.parent.lock() = Some(parent_wl_surface.downgrade()); + let Some(parent_panel_item) = parent_toplevel_data + .panel_item + .get() + .and_then(Weak::upgrade) + else { + return; + }; + let Some(panel_item) = get_data::>(&wl_surface) else { + error!("Couldn't get the panel item"); + return; + }; + panel_item.toplevel_parent_changed(&parent_panel_item.uid); + } + xdg_toplevel::Request::SetTitle { title } => { + debug!(?xdg_toplevel, ?title, "Set XDG Toplevel title"); + *toplevel_data.title.lock() = (!title.is_empty()).then_some(title.clone()); + let Some(panel_item) = get_data::>(&wl_surface) else { + error!("Couldn't get the panel item"); + return; + }; + panel_item.toplevel_title_changed(&title); + } + xdg_toplevel::Request::SetAppId { app_id } => { + debug!(?xdg_toplevel, ?app_id, "Set XDG Toplevel app ID"); + *toplevel_data.app_id.lock() = (!app_id.is_empty()).then_some(app_id.clone()); + let Some(panel_item) = get_data::>(&wl_surface) else { + error!("Couldn't get the panel item"); + return; + }; + panel_item.toplevel_app_id_changed(&app_id); + } + xdg_toplevel::Request::Move { seat, serial } => { + debug!(?xdg_toplevel, ?seat, serial, "XDG Toplevel move request"); + let Some(panel_item) = get_data::>(&wl_surface) else { + error!("Couldn't get the panel item"); + return; + }; + panel_item.toplevel_move_request(); + } + xdg_toplevel::Request::Resize { + seat, + serial, + edges, + } => { + let WEnum::Value(edges) = edges else { return }; + debug!( + ?xdg_toplevel, + ?seat, + serial, + ?edges, + "XDG Toplevel resize request" + ); + let (up, down, left, right) = match edges { + ResizeEdge::Top => (true, false, false, false), + ResizeEdge::Bottom => (false, true, false, false), + ResizeEdge::Left => (false, false, true, false), + ResizeEdge::TopLeft => (true, false, true, false), + ResizeEdge::BottomLeft => (false, true, true, false), + ResizeEdge::Right => (false, false, false, true), + ResizeEdge::TopRight => (true, false, false, true), + ResizeEdge::BottomRight => (false, true, false, true), + _ => (false, false, false, false), + }; + let Some(panel_item) = get_data::>(&wl_surface) else { + error!("Couldn't get the panel item"); + return; + }; + panel_item.toplevel_resize_request(up, down, left, right) + } + xdg_toplevel::Request::SetMaxSize { width, height } => { + debug!(?xdg_toplevel, width, height, "Set XDG Toplevel max size"); + *toplevel_data.max_size.lock() = (width > 1 || height > 1) + .then_some(Vector2::from([width as u32, height as u32])); + } + xdg_toplevel::Request::SetMinSize { width, height } => { + debug!(?xdg_toplevel, width, height, "Set XDG Toplevel min size"); + *toplevel_data.min_size.lock() = (width > 1 || height > 1) + .then_some(Vector2::from([width as u32, height as u32])); + } + xdg_toplevel::Request::SetFullscreen { output: _ } => { + let Some(panel_item) = get_data::>(&wl_surface) else { + error!("Couldn't get the panel item"); + return; + }; + panel_item.backend.toplevel_state.lock().fullscreen = true; + panel_item.backend.configure(None); + panel_item.toplevel_fullscreen_active(true); + } + xdg_toplevel::Request::UnsetFullscreen => { + let Some(panel_item) = get_data::>(&wl_surface) else { + error!("Couldn't get the panel item"); + return; + }; + panel_item.backend.toplevel_state.lock().fullscreen = false; + panel_item.backend.configure(None); + panel_item.toplevel_fullscreen_active(false); + } + xdg_toplevel::Request::Destroy => { + debug!(?xdg_toplevel, "Destroy XDG Toplevel"); + let Some(panel_item) = get_data::>(&wl_surface) else { + error!("Couldn't get the panel item"); + return; + }; + panel_item.backend.seat.drop_surface(&wl_surface); + panel_item.drop_toplevel(); + } + _ => {} + } + } +}