use { crate::{ client::ClientId, cursor::KnownCursor, fixed::Fixed, ifs::{ jay_workspace::JayWorkspace, wl_output::OutputId, wl_seat::{NodeSeatState, WlSeatGlobal, tablet::TabletTool}, wl_surface::{ WlSurface, x_surface::xwindow::Xwindow, xdg_surface::xdg_toplevel::XdgToplevel, }, workspace_manager::{ ext_workspace_handle_v1::ExtWorkspaceHandleV1, ext_workspace_manager_v1::WorkspaceManagerId, }, }, rect::Rect, renderer::Renderer, state::State, text::TextTexture, tree::{ ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FloatNode, FoundNode, Node, NodeId, NodeLayerLink, NodeLocation, NodeVisitorBase, OutputNode, OutputNodeId, PlaceholderNode, StackedNode, ToplevelNode, container::ContainerNode, walker::NodeVisitor, }, utils::{ clonecell::CloneCell, copyhashmap::CopyHashMap, linkedlist::{LinkedList, LinkedNode, NodeRef}, numcell::NumCell, opt::Opt, threshold_counter::ThresholdCounter, }, wire::JayWorkspaceId, }, jay_config::workspace::WorkspaceDisplayOrder, std::{ cell::{Cell, RefCell}, fmt::Debug, ops::Deref, rc::Rc, }, }; tree_id!(WorkspaceNodeId); pub struct WorkspaceNode { pub id: WorkspaceNodeId, pub state: Rc, pub is_dummy: bool, pub output: CloneCell>, pub output_id: Cell, pub position: Cell, pub container: CloneCell>>, pub stacked: LinkedList>, pub seat_state: NodeSeatState, pub name: String, pub output_link: RefCell>>>, pub visible: Cell, pub fullscreen: CloneCell>>, pub visible_on_desired_output: Cell, pub desired_output: CloneCell>, pub jay_workspaces: CopyHashMap<(ClientId, JayWorkspaceId), Rc>, pub may_capture: Cell, pub has_capture: Cell, pub title_texture: RefCell>, pub attention_requests: ThresholdCounter, pub render_highlight: NumCell, pub ext_workspaces: CopyHashMap>, pub opt: Rc>, } impl WorkspaceNode { pub fn new(output: &Rc, name: &str, is_dummy: bool) -> Rc { let slf = Rc::new(Self { id: output.state.node_ids.next(), state: output.state.clone(), is_dummy, output: CloneCell::new(output.clone()), output_id: Cell::new(output.id), position: Default::default(), container: Default::default(), stacked: Default::default(), seat_state: Default::default(), name: name.to_string(), output_link: Default::default(), visible: Default::default(), fullscreen: Default::default(), visible_on_desired_output: Default::default(), desired_output: CloneCell::new(output.global.output_id.clone()), jay_workspaces: Default::default(), may_capture: output.state.default_workspace_capture.clone(), has_capture: Default::default(), title_texture: Default::default(), attention_requests: Default::default(), render_highlight: Default::default(), ext_workspaces: Default::default(), opt: Default::default(), }); slf.seat_state.disable_focus_history(); slf } pub fn clear(&self) { self.container.set(None); *self.output_link.borrow_mut() = None; self.fullscreen.set(None); self.jay_workspaces.clear(); self.ext_workspaces.clear(); self.opt.set(None); self.title_texture.take(); } pub fn update_has_captures(&self) { let mut has_capture = false; let output = self.output.get(); 'update: { if !self.may_capture.get() { break 'update; } for sc in output.screencasts.lock().values() { if sc.shows_ws(self) { has_capture = true; break 'update; } } if output.screencopies.is_not_empty() { has_capture = true; } } if self.has_capture.replace(has_capture) != has_capture { output.schedule_update_render_data(); output.state.damage(output.global.pos.get()); } } pub fn set_output(&self, output: &Rc) { self.output_id.set(output.id); let old = self.output.set(output.clone()); for wh in self.ext_workspaces.lock().values() { wh.handle_new_output(output); } for jw in self.jay_workspaces.lock().values() { jw.send_output(output); } self.update_has_captures(); struct OutputSetter<'a> { ws: &'a WorkspaceNode, old: &'a Rc, new: &'a Rc, } impl NodeVisitorBase for OutputSetter<'_> { fn visit_surface(&mut self, node: &Rc) { node.set_output(self.new, self.ws.location()); } fn visit_container(&mut self, node: &Rc) { node.tl_workspace_output_changed(self.old, self.new); node.node_visit_children(self); } fn visit_toplevel(&mut self, node: &Rc) { node.tl_workspace_output_changed(self.old, self.new); node.node_visit_children(self); } fn visit_float(&mut self, node: &Rc) { node.after_ws_move(self.new); node.node_visit_children(self); } fn visit_xwindow(&mut self, node: &Rc) { node.tl_workspace_output_changed(self.old, self.new); node.node_visit_children(self); } fn visit_placeholder(&mut self, node: &Rc) { node.tl_workspace_output_changed(self.old, self.new); node.node_visit_children(self); } } let mut visitor = OutputSetter { ws: self, old: &old, new: output, }; self.node_visit_children(&mut visitor); for stacked in self.stacked.iter() { stacked.deref().clone().node_visit(&mut visitor); } } pub fn set_container(self: &Rc, container: &Rc) { if let Some(prev) = self.container.get() { self.discard_child_properties(&*prev); } self.pull_child_properties(&**container); let pos = self.position.get(); container.clone().tl_change_extents(&pos); container.tl_set_parent(self.clone()); container.tl_set_visible(self.container_visible()); self.container.set(Some(container.clone())); self.state.damage(self.position.get()); } pub fn is_empty(&self) -> bool { self.stacked.is_empty() && self.fullscreen.is_none() && self.container.is_none() } pub fn container_visible(&self) -> bool { self.visible.get() && self.fullscreen.is_none() } pub fn float_visible(&self) -> bool { self.visible.get() && (self.fullscreen.is_none() || self.state.float_above_fullscreen.get()) } pub fn change_extents(&self, rect: &Rect) { self.position.set(*rect); if let Some(c) = self.container.get() { c.tl_change_extents(rect); } } pub fn flush_jay_workspaces(&self) { for jw in self.jay_workspaces.lock().values() { jw.send_done(); } } pub fn set_visible(&self, visible: bool) { self.visible.set(visible); for jw in self.jay_workspaces.lock().values() { jw.send_visible(visible); } for wh in self.ext_workspaces.lock().values() { wh.handle_visibility_changed(); } for stacked in self.stacked.iter() { stacked.stacked_prepare_set_visible(); } if let Some(fs) = self.fullscreen.get() { fs.tl_set_visible(visible); } if let Some(container) = self.container.get() { container.tl_set_visible(self.container_visible()); } for stacked in self.stacked.iter() { if stacked.stacked_needs_set_visible() { stacked.stacked_set_visible(self.float_visible()); } } self.seat_state.set_visible(self, visible); } pub fn set_fullscreen_node(&self, node: &Rc) { if let Some(prev) = self.fullscreen.set(Some(node.clone())) { self.discard_child_properties(&*prev); } self.pull_child_properties(&**node); if self.visible.get() { self.output.get().fullscreen_changed(); } else { node.tl_set_visible(false); } if let Some(surface) = node.tl_scanout_surface() && let Some(fb) = self.output.get().global.connector.connector.drm_feedback() { surface.send_feedback(&fb); } self.output.get().update_presentation_type(); } pub fn remove_fullscreen_node(&self) { if let Some(node) = self.fullscreen.take() { self.discard_child_properties(&*node); if self.visible.get() { self.output.get().fullscreen_changed(); } if let Some(surface) = node.tl_scanout_surface() && let Some(fb) = surface.client.state.drm_feedback.get() { surface.send_feedback(&fb); } self.output.get().update_presentation_type(); } } fn pull_child_properties(&self, child: &dyn ToplevelNode) { if child.tl_data().wants_attention.get() { self.mod_attention_requested(true); } } fn discard_child_properties(&self, child: &dyn ToplevelNode) { if child.tl_data().wants_attention.get() { self.mod_attention_requested(false); } } fn mod_attention_requested(&self, set: bool) { let crossed_threshold = self.attention_requests.adj(set); if crossed_threshold { for wh in self.ext_workspaces.lock().values() { wh.handle_urgent_changed(); } self.output.get().schedule_update_render_data(); } } pub fn location(&self) -> NodeLocation { NodeLocation::Workspace(self.output_id.get(), self.id) } } impl Node for WorkspaceNode { fn node_id(&self) -> NodeId { self.id.into() } fn node_seat_state(&self) -> &NodeSeatState { &self.seat_state } fn node_visit(self: Rc, visitor: &mut dyn NodeVisitor) { visitor.visit_workspace(&self); } fn node_visit_children(&self, visitor: &mut dyn NodeVisitor) { if let Some(c) = self.container.get() { visitor.visit_container(&c); } if let Some(fs) = self.fullscreen.get() { fs.node_visit(visitor); } } fn node_visible(&self) -> bool { self.visible.get() } fn node_absolute_position(&self) -> Rect { self.position.get() } fn node_output(&self) -> Option> { Some(self.output.get()) } fn node_location(&self) -> Option { Some(self.location()) } fn node_layer(&self) -> NodeLayerLink { NodeLayerLink::Workspace } fn node_do_focus(self: Rc, seat: &Rc, direction: Direction) { if let Some(fs) = self.fullscreen.get() { fs.node_do_focus(seat, direction); } else if self.stacked.is_not_empty() && let Some(last) = seat.get_last_focus_on_workspace(&self) { seat.focus_node(last); } else if let Some(container) = self.container.get() { container.node_do_focus(seat, direction); } else if let Some(child) = self .stacked .rev_iter() .filter_map(|node| (*node).clone().node_into_float()) .find_map(|float| float.child.get()) { child.node_do_focus(seat, direction); } else { seat.focus_node(self); } } fn node_active_changed(&self, _active: bool) { let output = self.output.get(); self.state.damage(output.bar_separator_rect.get()); } fn node_find_tree_at( &self, x: i32, y: i32, tree: &mut Vec, usecase: FindTreeUsecase, ) -> FindTreeResult { if let Some(n) = self.container.get() { tree.push(FoundNode { node: n.clone(), x, y, }); return n.node_find_tree_at(x, y, tree, usecase); } FindTreeResult::Other } fn node_render(&self, renderer: &mut Renderer, x: i32, y: i32, _bounds: Option<&Rect>) { renderer.render_workspace(self, x, y); } fn node_make_visible(self: Rc) { if self.is_dummy { return; } self.state.show_workspace2(None, &self.output.get(), &self); } fn node_on_pointer_focus(&self, seat: &Rc) { // log::info!("workspace focus"); seat.pointer_cursor().set_known(KnownCursor::Default); } fn node_on_tablet_tool_enter( self: Rc, tool: &Rc, _time_usec: u64, _x: Fixed, _y: Fixed, ) { tool.cursor().set_known(KnownCursor::Default) } fn node_into_workspace(self: Rc) -> Option> { Some(self.clone()) } fn node_into_containing_node(self: Rc) -> Option> { Some(self) } fn node_is_workspace(&self) -> bool { true } } impl ContainingNode for WorkspaceNode { fn cnode_replace_child(self: Rc, old: &dyn Node, new: Rc) { if let Some(container) = self.container.get() && container.node_id() == old.node_id() { let new = match new.node_into_container() { Some(c) => c, _ => { log::error!("cnode_replace_child called with non-container new"); return; } }; self.set_container(&new); return; } log::error!("Trying to replace child that's not a child"); } fn cnode_remove_child2(self: Rc, child: &dyn Node, _preserve_focus: bool) { if let Some(container) = self.container.get() && container.node_id() == child.node_id() { self.discard_child_properties(&*container); self.container.set(None); self.state.damage(self.position.get()); return; } if let Some(fs) = self.fullscreen.get() && fs.node_id() == child.node_id() { self.remove_fullscreen_node(); return; } log::error!("Trying to remove child that's not a child"); } fn cnode_accepts_child(&self, node: &dyn Node) -> bool { node.node_is_container() } fn cnode_child_attention_request_changed(self: Rc, _node: &dyn Node, set: bool) { self.mod_attention_requested(set); } fn cnode_workspace(self: Rc) -> Rc { self } fn cnode_make_visible(self: Rc, _child: &dyn Node) { self.node_make_visible(); } } pub struct WsMoveConfig { pub make_visible_always: bool, pub make_visible_if_empty: bool, pub source_is_destroyed: bool, pub before: Option>, } pub fn move_ws_to_output( ws: &NodeRef>, target: &Rc, config: WsMoveConfig, ) { let source = ws.output.get(); if let Some(visible) = source.workspace.get() && visible.id == ws.id { source.workspace.take(); } let mut new_source_ws = None; if !config.source_is_destroyed && !source.is_dummy && source.workspace.is_none() { new_source_ws = source .workspaces .iter() .find(|c| c.id != ws.id) .map(|c| (*c).clone()); if new_source_ws.is_none() && source.pinned.is_not_empty() { new_source_ws = Some(source.generate_workspace()); } } if let Some(new_source_ws) = &new_source_ws { for pinned in source.pinned.iter() { pinned.deref().clone().set_workspace(new_source_ws, false); } } ws.set_output(&target); let before = if target.state.workspace_display_order.get() == WorkspaceDisplayOrder::Sorted { target .find_workspace_insertion_point(&ws.name) .map(|nr| nr.deref().clone()) } else { config.before }; 'link: { if let Some(before) = before && let Some(link) = &*before.output_link.borrow() { link.prepend_existing(ws); break 'link; } target.workspaces.add_last_existing(&ws); } let make_visible = !target.is_dummy && (config.make_visible_always || (config.make_visible_if_empty && target.workspace.is_none())); if make_visible { ws.state.show_workspace2(None, target, &ws); } else { ws.set_visible(false); } ws.flush_jay_workspaces(); if let Some(ws) = new_source_ws { ws.state.show_workspace2(None, &source, &ws); } if !target.is_dummy { target.schedule_update_render_data(); } if !source.is_dummy { source.schedule_update_render_data(); } if source.node_visible() { target.state.damage(source.global.pos.get()); } if target.node_visible() { target.state.damage(target.global.pos.get()); } } pub struct WorkspaceDragDestination { pub highlight: Rect, pub output: Rc, pub before: Option>, }