use crate::{ core::{ client::{get_env, startup_settings, Client, INTERNAL_CLIENT}, registry::Registry, }, nodes::{ drawable::{model::ModelPart, Drawable}, items::{self, Item, ItemType, TypeInfo}, spatial::Spatial, Message, Node, }, }; use color_eyre::eyre::{bail, eyre, Result}; use glam::Mat4; use lazy_static::lazy_static; use mint::Vector2; use nanoid::nanoid; use serde::{ de::{Deserializer, Error, SeqAccess, Visitor}, ser::Serializer, Deserialize, Serialize, }; use stardust_xr::schemas::flex::{deserialize, serialize}; use std::sync::{Arc, Weak}; use tracing::debug; 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), } pub trait Backend: Send + Sync + 'static { fn serialize_start_data(&self, id: &str) -> Result; fn serialize_toplevel(&self) -> Result; fn set_toplevel_capabilities(&self, capabilities: Vec); fn configure_toplevel( &self, size: Option>, states: Vec, bounds: Option>, ); fn apply_surface_material(&self, surface: SurfaceID, model_part: &Arc); fn pointer_motion(&self, surface: &SurfaceID, position: Vector2); fn pointer_button(&self, surface: &SurfaceID, button: u32, pressed: bool); fn pointer_scroll( &self, surface: &SurfaceID, scroll_distance: Option>, scroll_steps: Option>, ); fn keyboard_set_keymap(&self, keymap: &str) -> Result<()>; fn keyboard_key(&self, surface: &SurfaceID, key: u32, state: bool); } pub fn panel_item_from_node(node: &Node) -> Option> { let ItemType::Panel(panel_item) = &node.item.get()?.specialization else {return None}; Some(panel_item.clone()) } pub trait PanelItemTrait: Backend + Send + Sync + 'static { fn uid(&self) -> &str; // fn node(&self) -> Option>; } pub struct PanelItem { pub uid: String, node: Weak, pub backend: Box, } impl PanelItem { pub fn create(backend: Box, pid: Option) -> (Arc, Arc>) { debug!(?pid, "Create panel item"); let startup_settings = pid .and_then(|pid| get_env(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(); if let Some(startup_settings) = &startup_settings { spatial.set_local_transform( spatial.global_transform().inverse() * startup_settings.transform, ); } let panel_item = Arc::new(PanelItem { uid: uid.clone(), node: Arc::downgrade(&node), backend, }); let generic_panel_item: Arc = panel_item.clone(); let item = Item::add_to( &node, uid, &ITEM_TYPE_INFO_PANEL, ItemType::Panel(generic_panel_item), ); // panel_item // .seat_data // .new_surface(&wl_surface, Arc::downgrade(&panel_item)); 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", Self::apply_surface_material_flex); node.add_local_signal("configure_toplevel", Self::configure_toplevel_flex); node.add_local_signal( "set_toplevel_capabilities", Self::set_toplevel_capabilities_flex, ); node.add_local_signal("pointer_scroll", Self::pointer_scroll_flex); node.add_local_signal("pointer_button", Self::pointer_button_flex); node.add_local_signal("pointer_motion", Self::pointer_motion_flex); node.add_local_signal( "keyboard_set_keymap_string", Self::keyboard_set_keymap_string_flex, ); // node.add_local_signal( // "keyboard_set_keymap_names", // Self::keyboard_set_keymap_names_flex, // ); node.add_local_signal("keyboard_key", Self::keyboard_key_flex); (node, panel_item) } pub fn node(&self) -> Option> { self.node.upgrade() } fn apply_surface_material_flex( node: &Node, calling_client: Arc, message: Message, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) }; #[derive(Debug, Deserialize)] struct SurfaceMaterialInfo<'a> { surface: SurfaceID, model_node_path: &'a str, } let info: SurfaceMaterialInfo = deserialize(message.as_ref())?; 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_part)) = model_node.drawable.get() else {bail!("Node is not a model")}; debug!(?info, "Apply surface material"); panel_item.apply_surface_material(info.surface, model_part); Ok(()) } fn pointer_motion_flex( node: &Node, _calling_client: Arc, message: Message, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) }; let (surface_id, position): (SurfaceID, Vector2) = deserialize(message.as_ref())?; debug!(?surface_id, ?position, "Pointer deactivate"); panel_item.pointer_motion(&surface_id, position); Ok(()) } fn pointer_button_flex( node: &Node, _calling_client: Arc, message: Message, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) }; let (surface_id, button, state): (SurfaceID, u32, u32) = deserialize(message.as_ref())?; debug!(?surface_id, button, state, "Pointer button"); panel_item.pointer_button(&surface_id, button, state == 0); Ok(()) } fn pointer_scroll_flex( node: &Node, _calling_client: Arc, message: Message, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) }; #[derive(Debug, Deserialize)] struct PointerScrollInfo { surface_id: SurfaceID, axis_continuous: Option>, axis_discrete: Option>, } let info: PointerScrollInfo = deserialize(message.as_ref())?; debug!(?info, "Pointer scroll"); panel_item.pointer_scroll(&info.surface_id, info.axis_continuous, info.axis_discrete); Ok(()) } fn keyboard_set_keymap_string_flex( node: &Node, _calling_client: Arc, message: Message, ) -> Result<()> { let keymap_string: &str = deserialize(message.as_ref())?; let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) }; debug!("Keyboard set keymap"); panel_item.keyboard_set_keymap(keymap_string) // PanelItem::keyboard_set_keymap_flex(node, &keymap) } // fn keyboard_set_keymap_names_flex( // node: &Node, // _calling_client: Arc, // message: Message, // ) -> 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(message.as_ref())?; // 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: &str) -> Result<()> { // let Some(panel_item): Option>> = panel_item_from_node(node) else { return Ok(()) }; // debug!("Keyboard set keymap"); // panel_item.seat_data.set_keymap( // keymap, // match &panel_item { // 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, message: Message, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) }; let (surface_id, key, state): (SurfaceID, u32, u32) = deserialize(message.as_ref())?; debug!(key, state, "Set keyboard key state"); panel_item.keyboard_key(&surface_id, key, state == 0); Ok(()) } pub fn grab_keyboard(&self, sid: Option) { let Some(node) = self.node.upgrade() else {return}; let Ok(message) = serialize(sid) else {return}; let _ = node.send_remote_signal("grab_keyboard", message); } fn configure_toplevel_flex( node: &Node, _calling_client: Arc, message: Message, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) }; #[derive(Debug, Deserialize)] struct ConfigureToplevelInfo { size: Option>, states: Vec, bounds: Option>, } let info: ConfigureToplevelInfo = deserialize(message.as_ref())?; panel_item.configure_toplevel(info.size, info.states, info.bounds); Ok(()) } fn set_toplevel_capabilities_flex( node: &Node, _calling_client: Arc, message: Message, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) }; let capabilities: Vec = deserialize(message.as_ref())?; debug!("Set toplevel capabilities"); panel_item.set_toplevel_capabilities(capabilities); Ok(()) } pub fn commit_toplevel(&self) { debug!("Commit toplevel"); let Some(node) = self.node.upgrade() else {return}; let Ok(data) = self.backend.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); } } impl PanelItemTrait for PanelItem { fn uid(&self) -> &str { &self.uid } } impl Backend for PanelItem { fn serialize_start_data(&self, id: &str) -> Result { self.backend.serialize_start_data(id) } fn serialize_toplevel(&self) -> Result { self.backend.serialize_toplevel() } fn set_toplevel_capabilities(&self, capabilities: Vec) { self.backend.set_toplevel_capabilities(capabilities) } fn configure_toplevel( &self, size: Option>, states: Vec, bounds: Option>, ) { self.backend.configure_toplevel(size, states, bounds) } fn apply_surface_material(&self, surface: SurfaceID, model_part: &Arc) { self.backend.apply_surface_material(surface, model_part) } fn pointer_motion(&self, surface: &SurfaceID, position: Vector2) { self.backend.pointer_motion(surface, position) } fn pointer_button(&self, surface: &SurfaceID, button: u32, pressed: bool) { self.backend.pointer_button(surface, button, pressed) } fn pointer_scroll( &self, surface: &SurfaceID, scroll_distance: Option>, scroll_steps: Option>, ) { self.backend .pointer_scroll(surface, scroll_distance, scroll_steps) } fn keyboard_set_keymap(&self, keymap: &str) -> Result<()> { self.backend.keyboard_set_keymap(keymap) } fn keyboard_key(&self, surface: &SurfaceID, key: u32, state: bool) { self.backend.keyboard_key(surface, key, state) } } impl Drop for PanelItem { fn drop(&mut self) { // Dropped panel item, basically just a debug breakpoint place } }