diff --git a/src/wayland/xdg/backend.rs b/src/wayland/xdg/backend.rs index 889e3e7..a2b6838 100644 --- a/src/wayland/xdg/backend.rs +++ b/src/wayland/xdg/backend.rs @@ -1,28 +1,30 @@ use super::toplevel::Toplevel; use crate::{ - core::error::Result, - nodes::{ - drawable::model::ModelPart, - items::panel::{Backend, Geometry, PanelItemInitData, SurfaceId, ToplevelInfo}, - }, - wayland::{Message, core::surface::Surface}, + core::error::Result, + nodes::{ + drawable::model::ModelPart, + items::panel::{Backend, Geometry, PanelItemInitData, SurfaceId, ToplevelInfo}, + }, + wayland::{Message, core::surface::Surface}, }; use mint::Vector2; -use std::sync::Arc; -use std::sync::Weak; +use parking_lot::Mutex; +use std::{collections::HashMap, sync::{Arc, Weak}}; use tracing; #[derive(Debug)] pub struct XdgBackend { - toplevel: Weak, + toplevel: Weak, + children: Mutex>>, } impl XdgBackend { - pub fn new(toplevel: Arc) -> Self { - Self { - toplevel: Arc::downgrade(&toplevel), - } - } + pub fn new(toplevel: Arc) -> Self { + Self { + toplevel: Arc::downgrade(&toplevel), + children: Mutex::new(HashMap::new()), + } + } // Since XdgBackend is created and owned by Mapped which is owned by Toplevel, // we can safely assume the Toplevel reference will always be valid @@ -32,12 +34,20 @@ impl XdgBackend { .expect("Toplevel should always be valid while XdgBackend exists") } - fn surface_from_id(&self, id: SurfaceId) -> Option> { - match id { - SurfaceId::Toplevel(_) => Some(self.toplevel().surface()), - SurfaceId::Child(_) => None, - } - } + fn surface_from_id(&self, id: SurfaceId) -> Option> { + match id { + SurfaceId::Toplevel(_) => Some(self.toplevel().surface()), + SurfaceId::Child(uid) => self.children.lock().get(&uid).and_then(Weak::upgrade), + } + } + + pub fn register_child(&self, id: u64, surface: &Arc) { + self.children.lock().insert(id, Arc::downgrade(surface)); + } + + pub fn unregister_child(&self, id: u64) { + self.children.lock().remove(&id); + } } impl Backend for XdgBackend { diff --git a/src/wayland/xdg/popup.rs b/src/wayland/xdg/popup.rs index a05248b..d3058ef 100644 --- a/src/wayland/xdg/popup.rs +++ b/src/wayland/xdg/popup.rs @@ -4,8 +4,8 @@ use super::{ surface::Surface, }; use crate::{ - nodes::items::panel::{Geometry, PanelItem, SurfaceId}, - wayland::util::DoubleBuffer, + nodes::items::panel::{ChildInfo, Geometry, PanelItem, SurfaceId}, + wayland::util::DoubleBuffer, }; use parking_lot::Mutex; use rand::Rng; @@ -25,10 +25,10 @@ pub struct Popup { surface_id: SurfaceId, parent: Arc, surface: Weak, - pub panel_item: Weak>, - positioner_data: Mutex, - geometry: DoubleBuffer, - mapped: AtomicBool, + pub panel_item: Weak>, + positioner_data: Mutex, + geometry: Mutex>, + mapped: AtomicBool, } impl Popup { pub fn new( @@ -46,12 +46,83 @@ impl Popup { surface_id: SurfaceId::Child(rand::thread_rng().gen_range(0..u64::MAX)), parent, surface: Arc::downgrade(xdg_surface), - panel_item: Arc::downgrade(panel_item), - positioner_data: Mutex::new(positioner_data), - geometry: DoubleBuffer::new(positioner_data.infinite_geometry()), - mapped: AtomicBool::new(false), - } - } + panel_item: Arc::downgrade(panel_item), + positioner_data: Mutex::new(positioner_data), + geometry: Mutex::new(DoubleBuffer::new(positioner_data.infinite_geometry())), + mapped: AtomicBool::new(false), + } + } +} + +impl Popup { + fn id(&self) -> u64 { + match self.surface_id { + SurfaceId::Child(id) => id, + SurfaceId::Toplevel(_) => 0, + } + } + + pub fn surface_id(&self) -> SurfaceId { self.surface_id.clone() } + + pub fn current_geometry(&self) -> Geometry { + *self.geometry.lock().current() + } + + pub fn is_mapped(&self) -> bool { + self.mapped.load(std::sync::atomic::Ordering::SeqCst) + } + + pub fn map(&self) { + if self.is_mapped() { + return; + } + + let Some(panel_item) = self.panel_item.upgrade() else { return; }; + let xdg_surface = match self.surface.upgrade() { Some(s) => s, None => return }; + let core_surface = xdg_surface.wl_surface(); + + // Determine parent surface id + let parent_wl_surface = self.parent.wl_surface(); + let parent_role = parent_wl_surface.role.lock(); + let parent_id = match parent_role.as_ref().unwrap() { + crate::wayland::core::surface::SurfaceRole::XdgToplevel(_) => { + SurfaceId::Toplevel(()) + } + crate::wayland::core::surface::SurfaceRole::XDGPopup(p) => p.surface_id(), + }; + + let geometry = *self.geometry.lock().current(); + let info = ChildInfo { + id: self.id(), + parent: parent_id, + geometry, + z_order: 0, + receives_input: true, + }; + panel_item.create_child(self.id(), &info); + panel_item.backend.register_child(self.id(), &core_surface); + self.mapped.store(true, std::sync::atomic::Ordering::SeqCst); + } + + pub fn unmap(&self) { + if !self.mapped.swap(false, std::sync::atomic::Ordering::SeqCst) { + return; + } + + if let Some(panel_item) = self.panel_item.upgrade() { + panel_item.destroy_child(self.id()); + panel_item.backend.unregister_child(self.id()); + } + } + + fn reposition_child(&self) { + if self.is_mapped() { + if let Some(panel_item) = self.panel_item.upgrade() { + let geometry = *self.geometry.lock().current(); + panel_item.reposition_child(self.id(), &geometry); + } + } + } } impl XdgPopup for Popup { /// https://wayland.app/protocols/xdg-shell#xdg_popup:request:grab @@ -73,28 +144,35 @@ impl XdgPopup for Popup { positioner: ObjectId, token: u32, ) -> Result<()> { - let positioner = client.get::(positioner).unwrap(); - let positioner_data = positioner.data(); - *self.positioner_data.lock() = positioner_data; - if self.version >= 5 { - self.repositioned(client, sender_id, token).await?; - } - let geometry = positioner_data.infinite_geometry(); - self.configure( - client, - sender_id, - geometry.origin.x, - geometry.origin.y, - geometry.size.x as i32, - geometry.size.y as i32, - ) - .await?; - self.surface.upgrade().unwrap().reconfigure(client).await?; - Ok(()) - } + let positioner = client.get::(positioner).unwrap(); + let positioner_data = positioner.data(); + *self.positioner_data.lock() = positioner_data; + if self.version >= 5 { + self.repositioned(client, sender_id, token).await?; + } + let geometry = positioner_data.infinite_geometry(); + { + let mut geo = self.geometry.lock(); + geo.pending = geometry; + geo.apply(); + } + self.configure( + client, + sender_id, + geometry.origin.x, + geometry.origin.y, + geometry.size.x as i32, + geometry.size.y as i32, + ) + .await?; + self.surface.upgrade().unwrap().reconfigure(client).await?; + self.reposition_child(); + Ok(()) + } /// https://wayland.app/protocols/xdg-shell#xdg_popup:request:destroy - async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { - Ok(()) - } + async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + self.unmap(); + Ok(()) + } } diff --git a/src/wayland/xdg/surface.rs b/src/wayland/xdg/surface.rs index 4676bd3..d5daa0d 100644 --- a/src/wayland/xdg/surface.rs +++ b/src/wayland/xdg/surface.rs @@ -1,5 +1,6 @@ use super::{popup::Popup, positioner::Positioner, toplevel::Mapped}; use crate::wayland::{core::surface::SurfaceRole, display::Display, xdg::toplevel::Toplevel}; +use waynest::server::protocol::stable::xdg_shell::xdg_popup::XdgPopup; use std::sync::Arc; use std::sync::Weak; pub use waynest::server::protocol::stable::xdg_shell::xdg_surface::*; @@ -119,29 +120,43 @@ impl XdgSurface for Surface { parent: Option, positioner: ObjectId, ) -> Result<()> { - let parent = client.get::(parent.unwrap()).unwrap(); - let panel_item = match parent.wl_surface().role.lock().as_ref().unwrap() { - SurfaceRole::XdgToplevel(toplevel) => { - let toplevel_lock = toplevel.mapped.lock(); - toplevel_lock.as_ref().unwrap()._panel_item.clone() - } - SurfaceRole::XDGPopup(popup) => popup.panel_item.upgrade().unwrap(), - }; - let positioner = client.get::(positioner).unwrap(); + let parent = client.get::(parent.unwrap()).unwrap(); + let panel_item = match parent.wl_surface().role.lock().as_ref().unwrap() { + SurfaceRole::XdgToplevel(toplevel) => { + let toplevel_lock = toplevel.mapped.lock(); + toplevel_lock.as_ref().unwrap()._panel_item.clone() + } + SurfaceRole::XDGPopup(popup) => popup.panel_item.upgrade().unwrap(), + }; + let positioner = client.get::(positioner).unwrap(); - let surface = client.get::(self.id).unwrap(); + let surface = client.get::(self.id).unwrap(); + let wl_surface = surface.wl_surface(); - let popup = client.insert( - popup_id, - Popup::new( - popup_id, - self.version, - parent, - &panel_item, - &surface, - &positioner, - ), - ); + let popup = client.insert( + popup_id, + Popup::new( + popup_id, + self.version, + parent, + &panel_item, + &surface, + &positioner, + ), + ); + + // Initial popup configure + let geometry = popup.current_geometry(); + popup + .configure( + client, + popup_id, + geometry.origin.x, + geometry.origin.y, + geometry.size.x as i32, + geometry.size.y as i32, + ) + .await?; { let wl_surface = self.wl_surface(); @@ -155,8 +170,27 @@ impl XdgSurface for Surface { } } - let serial = client.next_event_serial(); - self.configure(client, sender_id, serial).await?; + let serial = client.next_event_serial(); + self.configure(client, sender_id, serial).await?; + + let configured = self.configured.clone(); + wl_surface.add_commit_handler(move |surface, state| { + let Some(SurfaceRole::XDGPopup(popup)) = &*surface.role.lock() else { + return true; + }; + let has_valid_buffer = state + .buffer + .as_ref() + .is_some_and(|b| b.buffer.size().x > 0 && b.buffer.size().y > 0); + if !popup.is_mapped() + && configured.load(std::sync::atomic::Ordering::SeqCst) + && has_valid_buffer + { + popup.map(); + return false; + } + true + }); // let pid = client.get::(ObjectId::DISPLAY).unwrap().pid; // let configured = self.configured.clone();