use crate::nodes::Node; use super::{ panel_item::{PanelItem, RecommendedState, SurfaceID}, state::WaylandState, surface::CoreSurface, SERIAL_COUNTER, }; use mint::Vector2; use nanoid::nanoid; use parking_lot::Mutex; use serde::{ser::SerializeSeq, Serialize, Serializer}; 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, XdgToplevel, EVT_WM_CAPABILITIES_SINCE}, xdg_wm_base::{self, XdgWmBase}, }, wayland_server::{ backend::{ClientId, ObjectId}, protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, WEnum, Weak as WlWeak, }, }; use std::{ fmt::Debug, sync::{Arc, Weak}, }; 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, Serialize, Clone, Copy)] pub struct PositionerData { size: Vector2, anchor_rect_pos: Vector2, anchor_rect_size: Vector2, anchor: u32, gravity: u32, constraint_adjustment: u32, 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 as u32, gravity: Gravity::None as u32, constraint_adjustment: ConstraintAdjustment::None.bits(), offset: Vector2::from([0; 2]), reactive: false, } } } 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 as u32; } } xdg_positioner::Request::SetGravity { gravity } => { if let WEnum::Value(gravity) = gravity { debug!(?positioner, ?gravity, "Set positioner gravity"); data.lock().gravity = gravity as u32; } } xdg_positioner::Request::SetConstraintAdjustment { constraint_adjustment, } => { debug!( ?positioner, constraint_adjustment, "Set positioner constraint adjustment" ); 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!(), } } } #[derive(Debug, Serialize, Clone, Copy)] pub struct Geometry { pub origin: Vector2, pub size: Vector2, } impl Default for Geometry { fn default() -> Self { Self { origin: Vector2::from([0; 2]), size: Vector2::from([0; 2]), } } } #[derive(Debug)] 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) -> &Mutex { xdg_surface.data::>().unwrap() } pub fn wl_surface(&self) -> WlSurface { self.wl_surface.upgrade().unwrap() } 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![]); } toplevel.configure(0, 0, vec![]); xdg_surface.configure(SERIAL_COUNTER.inc()); let client_credentials = client.get_credentials(&state.display_handle).ok(); let seat_data = state.seats.get(&client.id()).unwrap().clone(); let toplevel_weak = toplevel.downgrade(); CoreSurface::add_to( &state.display, state.display_handle.clone(), &xdg_surface_data.lock().wl_surface(), move |c| match c { 0 => { let toplevel = toplevel_weak.upgrade().unwrap(); let toplevel_data = ToplevelData::get(&toplevel); let xdg_surface = toplevel_data.lock().xdg_surface(); let xdg_surface_data = XdgSurfaceData::get(&xdg_surface); let wl_surface = xdg_surface_data.lock().wl_surface(); xdg_surface_data.lock().surface_id = SurfaceID::Toplevel; let (node, panel_item) = PanelItem::create( toplevel.clone(), wl_surface.clone(), client_credentials, seat_data.clone(), ); toplevel_data.lock().panel_item_node.replace(node); xdg_surface_data.lock().panel_item = Arc::downgrade(&panel_item); } _ => { let toplevel = toplevel_weak.upgrade().unwrap(); let toplevel_data = ToplevelData::get(&toplevel); let panel_item = toplevel_data.lock().panel_item().unwrap(); panel_item.commit_toplevel(); } }, ); } xdg_surface::Request::GetPopup { id, parent, positioner, } => { let parent_clone = parent.clone().unwrap(); let parent_data = parent_clone.data::>().unwrap().lock(); // let positioner_data = positioner // .data::>() // .unwrap() // .lock() // .clone(); // let parent = match &*parent_data { // XdgSurfaceType::Toplevel(_) => SurfaceID::Toplevel, // XdgSurfaceType::Popup(p) => { // SurfaceID::Popup(p.upgrade().unwrap().uid.clone()) // } // XdgSurfaceType::Unknown => return, // }; let uid = nanoid!(); let popup_data = Mutex::new(PopupData::new( uid.clone(), xdg_surface, parent_data.surface_id.clone(), positioner, )); let xdg_popup = data_init.init(id, popup_data); xdg_surface_data.lock().surface_id = SurfaceID::Popup(uid); let panel_item = parent_data.panel_item().unwrap(); xdg_surface_data.lock().panel_item = Arc::downgrade(&panel_item); panel_item.seat_data.new_surface( &xdg_surface_data.lock().wl_surface(), Arc::downgrade(&panel_item), ); debug!(?xdg_popup, ?xdg_surface, "Create XDG popup"); let xdg_surface = xdg_surface.downgrade(); let xdg_popup = xdg_popup.downgrade(); CoreSurface::add_to( &state.display, state.display_handle.clone(), &xdg_surface_data.lock().wl_surface.upgrade().unwrap(), move |commit_count| match commit_count { 0 => xdg_surface .upgrade() .unwrap() .configure(SERIAL_COUNTER.inc()), c => { let xdg_popup = xdg_popup.upgrade().unwrap(); let popup_data = PopupData::get(&xdg_popup); let popup_data = popup_data.lock(); // panel_item.commit_popup(popup_data); if c == 1 { panel_item.new_popup(&xdg_popup, &*popup_data); } } }, ); } 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!(), } } } fn serde_error(msg: &str) -> Result { Err(serde::ser::Error::custom(msg)) } #[derive(Debug, Clone)] pub struct ToplevelData { panel_item_node: Option>, xdg_surface: WlWeak, parent: Option>, title: Option, app_id: Option, max_size: Option>, min_size: Option>, states: Vec, } impl ToplevelData { fn new(xdg_surface: &XdgSurface) -> Self { ToplevelData { panel_item_node: None, xdg_surface: xdg_surface.downgrade(), parent: None, title: None, app_id: None, max_size: None, min_size: None, states: Vec::new(), } } pub fn get(toplevel: &XdgToplevel) -> &Mutex { toplevel.data::>().unwrap() } pub fn xdg_surface(&self) -> XdgSurface { self.xdg_surface.upgrade().unwrap() } fn panel_item(&self) -> Option> { let xdg_surface = self.xdg_surface(); let xdg_surface_data = XdgSurfaceData::get(&xdg_surface).lock(); xdg_surface_data.panel_item() } } impl Serialize for ToplevelData { fn serialize(&self, serializer: S) -> Result { let xdg_surface = self.xdg_surface(); let xdg_surface_data = XdgSurfaceData::get(&xdg_surface).lock(); let geometry = xdg_surface_data.geometry.clone(); let wl_surface = xdg_surface_data.wl_surface(); let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else {return serde_error::("Core surface not found")}; let Some(size) = core_surface.size() else {return serializer.serialize_none()}; let geometry = geometry.unwrap_or_else(|| Geometry { origin: [0; 2].into(), size, }); let mut seq = serializer.serialize_seq(None)?; // Parent UID seq.serialize_element(&self.parent.as_ref().and_then(|p| { Some( ToplevelData::get(&p.upgrade().ok()?) .lock() .panel_item()? .uid .clone(), ) }))?; seq.serialize_element(&self.title)?; seq.serialize_element(&self.app_id)?; seq.serialize_element(&size)?; seq.serialize_element(&self.min_size)?; seq.serialize_element(&self.max_size)?; seq.serialize_element(&geometry)?; seq.serialize_element(&self.states)?; seq.end() } } 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.map(|toplevel| toplevel.downgrade()); } xdg_toplevel::Request::SetTitle { title } => { debug!(?xdg_toplevel, ?title, "Set XDG Toplevel title"); data.lock().title = (!title.is_empty()).then_some(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); } xdg_toplevel::Request::ShowWindowMenu { seat, serial, x, y } => { debug!( ?xdg_toplevel, ?seat, serial, x, y, "Show XDG Toplevel window menu" ); } 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.recommend_toplevel_state(RecommendedState::Move); } 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 }; panel_item.recommend_toplevel_state(RecommendedState::Resize(edges as u32)); } 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::SetMaximized => { let Some(panel_item) = data.lock().panel_item() else { return }; panel_item.recommend_toplevel_state(RecommendedState::Maximize(true)); } xdg_toplevel::Request::UnsetMaximized => { let Some(panel_item) = data.lock().panel_item() else { return }; panel_item.recommend_toplevel_state(RecommendedState::Maximize(false)); } xdg_toplevel::Request::SetFullscreen { output: _ } => { let Some(panel_item) = data.lock().panel_item() else { return }; panel_item.recommend_toplevel_state(RecommendedState::Fullscreen(true)); } xdg_toplevel::Request::UnsetFullscreen => { let Some(panel_item) = data.lock().panel_item() else { return }; panel_item.recommend_toplevel_state(RecommendedState::Fullscreen(true)); } xdg_toplevel::Request::SetMinimized => { let Some(panel_item) = data.lock().panel_item() else { return }; panel_item.recommend_toplevel_state(RecommendedState::Minimize); } xdg_toplevel::Request::Destroy => { debug!(?xdg_toplevel, "Destroy XDG Toplevel"); let Some(panel_item) = data.lock().panel_item() else { return }; panel_item.on_drop(); } _ => unreachable!(), } } } #[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) -> &Mutex { popup.data::>().unwrap() } pub fn xdg_surface(&self) -> XdgSurface { self.xdg_surface.upgrade().unwrap() } 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) -> WlSurface { XdgSurfaceData::get(&self.xdg_surface()).lock().wl_surface() } pub fn positioner_data(&self) -> Option { Some( self.positioner .data::>()? .lock() .clone(), ) } } impl Serialize for PopupData { fn serialize(&self, serializer: S) -> Result { let Some(positioner_data) = self.positioner_data() else {return serde_error::("Positioner not found")}; let mut seq = serializer.serialize_seq(None)?; seq.serialize_element(&self.uid)?; seq.serialize_element(&self.parent_id)?; seq.serialize_element(&positioner_data)?; seq.end() } } 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::Popup(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.reposition_popup(&*data); // xdg_popup.popup_done(); // temporary hack to avoid apps locking up before popups are implemented } 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, _resource: ObjectId, data: &Mutex, ) { let data = data.lock(); let Some(panel_item) = data.panel_item() else {return}; panel_item.drop_popup(&data.uid); } }