use super::{ seat::{Cursor, SeatData}, surface::CoreSurface, xdg_shell::{PopupData, ToplevelData, XdgSurfaceData}, SERIAL_COUNTER, }; use crate::{ core::{ client::{get_env, startup_settings, Client, INTERNAL_CLIENT}, registry::Registry, }, nodes::{ drawable::Drawable, items::{self, Item, ItemSpecialization, ItemType, TypeInfo}, spatial::Spatial, Node, }, wayland::seat::{KeyboardEvent, PointerEvent}, }; use color_eyre::eyre::{bail, eyre, Result}; use glam::Mat4; use lazy_static::lazy_static; use mint::Vector2; use nanoid::nanoid; use parking_lot::Mutex; use rustc_hash::FxHashMap; use serde::{ de::{Deserializer, Error, SeqAccess, Visitor}, ser::Serializer, Deserialize, Serialize, }; use smithay::{ reexports::{ wayland_protocols::xdg::shell::server::{ xdg_popup::XdgPopup, xdg_surface::XdgSurface, xdg_toplevel::{XdgToplevel, EVT_CONFIGURE_BOUNDS_SINCE, EVT_WM_CAPABILITIES_SINCE}, }, wayland_server::{ backend::Credentials, protocol::wl_surface::WlSurface, Resource, Weak as WlWeak, }, }, wayland::compositor, }; #[cfg(feature = "xwayland")] use smithay::{ utils::Rectangle, xwayland::{xwm::X11SurfaceError, X11Surface}, }; use stardust_xr::schemas::flex::{deserialize, serialize}; use std::sync::{Arc, Weak}; use tracing::debug; use xkbcommon::xkb::{self, ffi::XKB_KEYMAP_FORMAT_TEXT_V1, Keymap}; lazy_static! { pub static ref ITEM_TYPE_INFO_PANEL: TypeInfo = TypeInfo { type_name: "panel", aliased_local_signals: vec![ "apply_surface_material", "configure_toplevel", "set_toplevel_capabilities", "pointer_scroll", "pointer_button", "pointer_motion", "keyboard_key", "keyboard_set_keymap_names", "keyboard_set_keymap_string", "close", ], aliased_local_methods: vec![], aliased_remote_signals: vec![ "commit_toplevel", "recommend_toplevel_state", "set_cursor", "new_popup", "reposition_popup", "drop_popup", ], ui: Default::default(), items: Registry::new(), acceptors: Registry::new(), }; } /// An ID for a surface inside this panel item #[derive(Debug, Clone)] #[allow(dead_code)] pub enum SurfaceID { Cursor, Toplevel, Popup(String), } impl Default for SurfaceID { fn default() -> Self { Self::Toplevel } } impl<'de> serde::Deserialize<'de> for SurfaceID { fn deserialize>(deserializer: D) -> Result { deserializer.deserialize_seq(SurfaceIDVisitor) } } struct SurfaceIDVisitor; impl<'de> Visitor<'de> for SurfaceIDVisitor { type Value = SurfaceID; fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.write_str("idk") } fn visit_seq>(self, mut seq: A) -> Result { let Some(discrim) = seq.next_element()? else { return Err(A::Error::missing_field("discrim")); }; // idk if you wanna check for extraneous elements // I didn't bother match discrim { "Cursor" => Ok(SurfaceID::Cursor), "Toplevel" => Ok(SurfaceID::Toplevel), "Popup" => { let Some(text) = seq.next_element()? else { return Err(A::Error::missing_field("popup_text")); }; Ok(SurfaceID::Popup(text)) } _ => Err(A::Error::unknown_variant( discrim, &["Cursor", "Toplevel", "Popup"], )), } } } impl serde::Serialize for SurfaceID { fn serialize(&self, serializer: S) -> Result { match self { Self::Cursor => ["Cursor"].serialize(serializer), Self::Toplevel => ["Toplevel"].serialize(serializer), Self::Popup(text) => ["Popup", text].serialize(serializer), } } } #[derive(Debug, Clone, Copy, Serialize)] #[serde(tag = "type", content = "content")] pub enum RecommendedState { Maximize(bool), Fullscreen(bool), Minimize, Move, Resize(u32), } #[derive(Debug)] pub struct WaylandBackend { toplevel: WlWeak, toplevel_wl_surface: WlWeak, popups: Mutex>>, cursor: Mutex>>, } impl WaylandBackend { pub fn create(toplevel: XdgToplevel) -> Option { let toplevel_wl_surface = XdgSurfaceData::get(&ToplevelData::get(&toplevel).lock().xdg_surface()?)? .lock() .wl_surface()? .downgrade(); Some(WaylandBackend { toplevel: toplevel.downgrade(), toplevel_wl_surface, popups: Mutex::new(FxHashMap::default()), cursor: Mutex::new(None), }) } 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 wl_surface_from_id(&self, id: &SurfaceID) -> Option { match id { SurfaceID::Cursor => self.cursor.lock().clone()?.upgrade().ok(), SurfaceID::Toplevel => self.toplevel_wl_surface(), SurfaceID::Popup(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 input_surfaces(&self) -> Vec { let mut surfaces = self .toplevel_wl_surface() .map(|s| vec![s]) .unwrap_or_default(); surfaces.extend(self.popups.lock().values().filter_map(|p| { let popup = p.upgrade().ok()?; let popup_data = PopupData::get(&popup)?.lock(); let xdg_surface = popup_data.xdg_surface()?; let xdg_surface_data = XdgSurfaceData::get(&xdg_surface)?.lock(); xdg_surface_data.wl_surface() })); surfaces } fn configure_toplevel( &self, size: Option>, states: Vec, bounds: Option>, ) { let Ok(xdg_toplevel) = self.toplevel.upgrade() else {return}; let Some(xdg_surface) = self.toplevel_xdg_surface() else {return}; debug!(?size, ?states, ?bounds, "Configure toplevel info"); if let Some(bounds) = bounds { if xdg_toplevel.version() > EVT_CONFIGURE_BOUNDS_SINCE { xdg_toplevel.configure_bounds(bounds.x as i32, bounds.y as i32); } } let size = size.unwrap_or(Vector2::from([0; 2])); xdg_toplevel.configure( size.x as i32, size.y as i32, states .into_iter() .flat_map(|state| state.to_ne_bytes()) .collect(), ); xdg_surface.configure(SERIAL_COUNTER.inc()); } fn serialize_toplevel(&self) -> Result> { let toplevel = self .toplevel() .ok_or_else(|| eyre!("Toplevel does not exist"))?; let state = ToplevelData::get(&toplevel); let data = serialize(&state.lock().clone())?; Ok(data) } fn set_toplevel_capabilities(&self, capabilities: Vec) { let Ok(xdg_toplevel) = self.toplevel.upgrade() else {return}; xdg_toplevel.wm_capabilities(capabilities); let Some(xdg_surface) = self.toplevel_xdg_surface() else {return}; xdg_surface.configure(SERIAL_COUNTER.inc()); } pub fn new_popup(&self, panel_item: &PanelItem, popup: &XdgPopup, data: &PopupData) { let uid = data.uid.clone(); self.popups.lock().insert(uid.clone(), popup.downgrade()); let Some(node) = panel_item.node.upgrade() else { return }; let _ = node.send_remote_signal("new_popup", &serialize(&(&uid, data)).unwrap()); } pub fn reposition_popup(&self, panel_item: &PanelItem, popup_state: &PopupData) { let Some(node) = panel_item.node.upgrade() else { return }; let _ = node.send_remote_signal( "reposition_popup", &serialize(popup_state.positioner_data().unwrap()).unwrap(), ); } pub fn drop_popup(&self, panel_item: &PanelItem, uid: &str) { 'seat_drop: { let Some(popup) = self .popups .lock() .remove(uid) else {break 'seat_drop}; let Some(popup) = popup.upgrade().ok() else {break 'seat_drop}; let Some(popup) = popup.data::>().cloned() else {break 'seat_drop}; let Some(wl_surface) = popup.wl_surface() else {break 'seat_drop}; panel_item.seat_data.drop_surface(&wl_surface); } let Some(node) = panel_item.node.upgrade() else { return }; let _ = node.send_remote_signal("drop_popup", &serialize(uid).unwrap()); } fn popups_data(&self) -> Vec { self.popups .lock() .values() .filter_map(|v| Some(v.upgrade().ok()?.data::>()?.lock().clone())) .collect::>() } fn cursor_data(&self) -> Option<(Vector2, Vector2)> { let cursor = self.cursor.lock().as_ref().and_then(|c| c.upgrade().ok()); let cursor_size = cursor .as_ref() .and_then(|c| CoreSurface::from_wl_surface(&c)) .and_then(|c| c.size()); let cursor_hotspot = cursor .and_then(|c| { compositor::with_states(&c, |data| data.data_map.get::>().cloned()) }) .map(|cursor| cursor.hotspot); cursor_size.zip(cursor_hotspot) } pub fn set_cursor( &self, panel_item: &PanelItem, surface: Option<&WlSurface>, hotspot_x: i32, hotspot_y: i32, ) { let Some(node) = panel_item.node.upgrade() else { return }; debug!(?surface, hotspot_x, hotspot_y, "Set cursor size"); let mut data = serialize(()).unwrap(); let cursor_size = surface .and_then(|c| CoreSurface::from_wl_surface(c)) .and_then(|c| c.size()); if let Some(size) = cursor_size { data = serialize((size, (hotspot_x, hotspot_y))).unwrap(); } let _ = node.send_remote_signal("set_cursor", &data); *self.cursor.lock() = surface.map(|surf| surf.downgrade()); } } #[cfg(feature = "xwayland")] #[derive(Debug)] pub struct X11Backend { pub toplevel_parent: Option, pub toplevel: X11Surface, } #[cfg(feature = "xwayland")] impl X11Backend { fn configure_toplevel( &self, size: Option>, states: Vec, ) -> Result<(), X11SurfaceError> { self.toplevel.configure( size.map(|s| Rectangle::from_loc_and_size((0, 0), (s.x as i32, s.y as i32))), )?; self.toplevel.set_maximized(states.contains(&1))?; Ok(()) } fn serialize_toplevel(&self) -> Result> { let toplevel_state = ( None::, self.toplevel.title(), None::, ( self.toplevel.geometry().size.w, self.toplevel.geometry().size.h, ), self.toplevel.min_size().map(|s| (s.w, s.h)), self.toplevel.max_size().map(|s| (s.w, s.w)), ); let data = serialize(&toplevel_state)?; Ok(data) } fn wl_surface_from_id(&self, id: &SurfaceID) -> Option { match id { SurfaceID::Cursor => None, SurfaceID::Toplevel => self.toplevel.wl_surface(), SurfaceID::Popup(_) => None, } } } // TODO: abstract this away into a trait so other platforms e.g. arcan/android/windows/xrdesktop can also be 2D backends #[derive(Debug)] pub enum Backend { Wayland(WaylandBackend), #[cfg(feature = "xwayland")] X11(X11Backend), } pub struct PanelItem { pub uid: String, node: Weak, pub seat_data: Arc, pub backend: Backend, pointer_grab: Mutex>, keyboard_grab: Mutex>, } impl PanelItem { pub fn create( wl_surface: WlSurface, backend: Backend, client_credentials: Option, seat_data: Arc, ) -> (Arc, Arc) { debug!(?backend, ?client_credentials, "Create panel item"); let startup_settings = client_credentials .and_then(|cred| get_env(cred.pid).ok()) .and_then(|env| startup_settings(&env)); let uid = nanoid!(); let node = Node::create(&INTERNAL_CLIENT, "/item/panel/item", &uid, true) .add_to_scenegraph() .unwrap(); let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false).unwrap(); let panel_item = Arc::new(PanelItem { uid: uid.clone(), node: Arc::downgrade(&node), seat_data, backend, pointer_grab: Mutex::new(None), keyboard_grab: Mutex::new(None), }); if let Some(startup_settings) = &startup_settings { spatial.set_local_transform( spatial.global_transform().inverse() * startup_settings.transform, ); } panel_item .seat_data .new_surface(&wl_surface, Arc::downgrade(&panel_item)); let item = Item::add_to( &node, uid, &ITEM_TYPE_INFO_PANEL, ItemType::Panel(panel_item.clone()), ); if let Some(startup_settings) = &startup_settings { if let Some(acceptor) = startup_settings .acceptors .get(&*ITEM_TYPE_INFO_PANEL) .and_then(|acc| acc.upgrade()) { items::capture(&item, &acceptor); } } node.add_local_signal( "apply_surface_material", PanelItem::apply_surface_material_flex, ); node.add_local_signal("configure_toplevel", PanelItem::configure_toplevel_flex); node.add_local_signal( "set_toplevel_capabilities", PanelItem::set_toplevel_capabilities_flex, ); node.add_local_signal("pointer_scroll", PanelItem::pointer_scroll_flex); node.add_local_signal("pointer_button", PanelItem::pointer_button_flex); node.add_local_signal("pointer_motion", PanelItem::pointer_motion_flex); node.add_local_signal( "keyboard_set_keymap_string", PanelItem::keyboard_set_keymap_string_flex, ); node.add_local_signal( "keyboard_set_keymap_names", PanelItem::keyboard_set_keymap_names_flex, ); node.add_local_signal("keyboard_key", PanelItem::keyboard_key_flex); (node, panel_item) } pub fn from_node(node: &Node) -> Option> { let ItemType::Panel(panel_item) = &node.item.get()?.specialization else {return None}; Some(panel_item.clone()) } fn toplevel_wl_surface(&self) -> Option { match &self.backend { Backend::Wayland(w) => w.toplevel_wl_surface(), #[cfg(feature = "xwayland")] Backend::X11(x) => x.toplevel.wl_surface(), } } fn core_surface(&self) -> Option> { compositor::with_states(&self.toplevel_wl_surface()?, |data| { data.data_map.get::>().cloned() }) } fn flush_clients(&self) { if let Some(core_surface) = self.core_surface() { core_surface.flush_clients(); } } fn wl_surface_from_id(&self, id: &SurfaceID) -> Option { match &self.backend { Backend::Wayland(w) => w.wl_surface_from_id(id), #[cfg(feature = "xwayland")] Backend::X11(x) => x.wl_surface_from_id(id), } } fn wl_surface_from_id_result(&self, id: &SurfaceID) -> Result { self.wl_surface_from_id(id) .ok_or(eyre!("Surface with ID not found")) } fn apply_surface_material_flex( node: &Node, calling_client: Arc, data: &[u8], ) -> Result<()> { let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) }; #[derive(Debug, Deserialize)] struct SurfaceMaterialInfo<'a> { surface: SurfaceID, model_node_path: &'a str, } let info: SurfaceMaterialInfo = deserialize(data)?; let Some(wl_surface) = panel_item.wl_surface_from_id(&info.surface) else { return Ok(()) }; let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else { return Ok(()) }; let model_node = calling_client .scenegraph .get_node(info.model_node_path) .ok_or_else(|| eyre!("Model node not found"))?; let Some(Drawable::ModelPart(model_node)) = model_node.drawable.get() else {bail!("Node is not a model")}; debug!(?info, "Apply surface material"); core_surface.apply_material(model_node); Ok(()) } fn pointer_motion_flex(node: &Node, _calling_client: Arc, data: &[u8]) -> Result<()> { let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) }; let (surface_id, position): (SurfaceID, Vector2) = deserialize(data)?; let wl_surface = panel_item.wl_surface_from_id_result(&surface_id)?; debug!(?surface_id, ?position, "Pointer deactivate"); panel_item .seat_data .pointer_event(&wl_surface, PointerEvent::Motion(position)); panel_item.flush_clients(); Ok(()) } fn pointer_button_flex(node: &Node, _calling_client: Arc, data: &[u8]) -> Result<()> { let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) }; let (surface_id, button, state): (SurfaceID, u32, u32) = deserialize(data)?; let wl_surface = panel_item.wl_surface_from_id_result(&surface_id)?; debug!(?surface_id, button, state, "Pointer button"); panel_item .seat_data .pointer_event(&wl_surface, PointerEvent::Button { button, state }); panel_item.flush_clients(); Ok(()) } fn pointer_scroll_flex(node: &Node, _calling_client: Arc, data: &[u8]) -> Result<()> { let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) }; #[derive(Debug, Deserialize)] struct PointerScrollInfo { surface_id: SurfaceID, axis_continuous: Option>, axis_discrete: Option>, } let info: PointerScrollInfo = deserialize(data)?; let wl_surface = panel_item.wl_surface_from_id_result(&info.surface_id)?; debug!(?info, "Pointer scroll"); panel_item.seat_data.pointer_event( &wl_surface, PointerEvent::Scroll { axis_continuous: info.axis_continuous, axis_discrete: info.axis_discrete, }, ); panel_item.flush_clients(); Ok(()) } fn keyboard_set_keymap_string_flex( node: &Node, _calling_client: Arc, data: &[u8], ) -> Result<()> { let context = xkb::Context::new(0); let keymap = Keymap::new_from_string(&context, deserialize(data)?, XKB_KEYMAP_FORMAT_TEXT_V1, 0) .ok_or_else(|| eyre!("Keymap is not valid"))?; PanelItem::keyboard_set_keymap_flex(node, &keymap) } fn keyboard_set_keymap_names_flex( node: &Node, _calling_client: Arc, data: &[u8], ) -> Result<()> { #[derive(Debug, Deserialize)] struct Names<'a> { rules: &'a str, model: &'a str, layout: &'a str, variant: &'a str, options: Option, } let names: Names = deserialize(data)?; let context = xkb::Context::new(0); let keymap = Keymap::new_from_names( &context, names.rules, names.model, names.layout, names.variant, names.options, XKB_KEYMAP_FORMAT_TEXT_V1, ) .ok_or_else(|| eyre!("Keymap is not valid"))?; PanelItem::keyboard_set_keymap_flex(node, &keymap) } fn keyboard_set_keymap_flex(node: &Node, keymap: &Keymap) -> Result<()> { let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) }; debug!("Keyboard set keymap"); panel_item.seat_data.set_keymap( keymap, match &panel_item.backend { Backend::Wayland(w) => w.input_surfaces(), #[cfg(feature = "xwayland")] Backend::X11(_) => panel_item .toplevel_wl_surface() .map(|s| vec![s]) .unwrap_or_default(), }, ); Ok(()) } fn keyboard_key_flex(node: &Node, _calling_client: Arc, data: &[u8]) -> Result<()> { let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) }; let (surface_id, key, state): (SurfaceID, u32, u32) = deserialize(data)?; let wl_surface = panel_item.wl_surface_from_id_result(&surface_id)?; debug!(key, state, "Set keyboard key state"); panel_item .seat_data .keyboard_event(&wl_surface, KeyboardEvent::Key { key, state }); Ok(()) } fn configure_toplevel_flex( node: &Node, _calling_client: Arc, data: &[u8], ) -> Result<()> { let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) }; #[derive(Debug, Deserialize)] struct ConfigureToplevelInfo { size: Option>, states: Vec, bounds: Option>, } let info: ConfigureToplevelInfo = deserialize(data)?; match &panel_item.backend { Backend::Wayland(w) => w.configure_toplevel(info.size, info.states, info.bounds), #[cfg(feature = "xwayland")] Backend::X11(x) => x.configure_toplevel(info.size, info.states)?, } Ok(()) } fn set_toplevel_capabilities_flex( node: &Node, _calling_client: Arc, data: &[u8], ) -> Result<()> { let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) }; let Some(core_surface) = panel_item.core_surface() else { return Ok(()) }; if let Backend::Wayland(w) = &panel_item.backend { let Some(toplevel) = w.toplevel() else {return Ok(())}; if toplevel.version() < EVT_WM_CAPABILITIES_SINCE { return Ok(()); } } let capabilities: Vec = deserialize(data)?; debug!("Set toplevel capabilities"); match &panel_item.backend { Backend::Wayland(w) => w.set_toplevel_capabilities(capabilities), _ => (), } core_surface.flush_clients(); Ok(()) } pub fn commit_toplevel(&self) { debug!("Commit toplevel"); let Some(node) = self.node.upgrade() else { return }; let Ok(data) = (match &self.backend { Backend::Wayland(w) => w.serialize_toplevel(), #[cfg(feature = "xwayland")] Backend::X11(x) => x.serialize_toplevel(), }) else {return}; let _ = node.send_remote_signal("commit_toplevel", &data); } pub fn recommend_toplevel_state(&self, state: RecommendedState) { let Some(node) = self.node.upgrade() else { return }; let data = serialize(state).unwrap(); debug!(?state, "Recommend toplevel state"); let _ = node.send_remote_signal("recommend_toplevel_state", &data); } pub fn grab_keyboard(&self, sid: Option) { let Some(node) = self.node.upgrade() else { return }; let _ = node.send_remote_signal("grab_keyboard", &serialize(sid).unwrap()); } pub fn on_drop(&self) { let Some(toplevel) = self.toplevel_wl_surface() else {return}; self.seat_data.drop_surface(&toplevel); debug!("Dropped panel item gracefully"); } } impl ItemSpecialization for PanelItem { fn serialize_start_data(&self, id: &str) -> Option> { match &self.backend { Backend::Wayland(w) => { let toplevel = w.toplevel()?; let toplevel_state = ToplevelData::get(&toplevel); let toplevel_state = toplevel_state.lock().clone(); let pointer_grab = self.pointer_grab.lock().clone(); let keyboard_grab = self.keyboard_grab.lock().clone(); serialize(( id, ( w.cursor_data(), toplevel_state, w.popups_data(), pointer_grab, keyboard_grab, ), )) .ok() } #[cfg(feature = "xwayland")] Backend::X11(x) => { let size = ( x.toplevel.geometry().size.w as u32, x.toplevel.geometry().size.h as u32, ); let toplevel_state = ( None::, x.toplevel.title(), None::, ( x.toplevel.geometry().size.w as u32, x.toplevel.geometry().size.h as u32, ), x.toplevel.min_size().map(|s| (s.w as u32, s.h as u32)), x.toplevel.max_size().map(|s| (s.w as u32, s.w as u32)), ((0_i32, 0_i32), size), vec![0_u32; 0], ); let info = ( None::<(Vector2, Vector2)>, toplevel_state, Vec::::new(), None::, None::, ); serialize((id, info)).ok() } } } } impl Drop for PanelItem { fn drop(&mut self) { // Dropped panel item, basically just a debug breakpoint place } }