use { crate::{ backend::{HardwareCursor, KeyState, Mode}, client::ClientId, cursor::KnownCursor, fixed::Fixed, gfx_api::GfxTexture, ifs::{ jay_output::JayOutput, jay_screencast::JayScreencast, wl_output::WlOutputGlobal, wl_seat::{ collect_kb_foci2, wl_pointer::PendingScroll, NodeSeatState, SeatId, WlSeatGlobal, BTN_LEFT, }, wl_surface::{ ext_session_lock_surface_v1::ExtSessionLockSurfaceV1, zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, SurfaceSendPreferredScaleVisitor, SurfaceSendPreferredTransformVisitor, }, zwlr_layer_shell_v1::{BACKGROUND, BOTTOM, OVERLAY, TOP}, }, rect::Rect, renderer::Renderer, scale::Scale, state::State, text::{self, TextTexture}, tree::{ walker::NodeVisitor, Direction, FindTreeResult, FoundNode, Node, NodeId, WorkspaceNode, }, utils::{ clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, linkedlist::LinkedList, scroller::Scroller, }, wire::{JayOutputId, JayScreencastId}, }, ahash::AHashMap, jay_config::video::Transform, smallvec::SmallVec, std::{ cell::{Cell, RefCell}, fmt::{Debug, Formatter}, ops::{Deref, Sub}, rc::Rc, }, }; tree_id!(OutputNodeId); pub struct OutputNode { pub id: OutputNodeId, pub global: Rc, pub jay_outputs: CopyHashMap<(ClientId, JayOutputId), Rc>, pub workspaces: LinkedList>, pub workspace: CloneCell>>, pub seat_state: NodeSeatState, pub layers: [LinkedList>; 4], pub render_data: RefCell, pub state: Rc, pub is_dummy: bool, pub status: CloneCell>, pub scroll: Scroller, pub pointer_positions: CopyHashMap, pub lock_surface: CloneCell>>, pub hardware_cursor: CloneCell>>, pub hardware_cursor_needs_render: Cell, pub update_render_data_scheduled: Cell, pub screencasts: CopyHashMap<(ClientId, JayScreencastId), Rc>, } pub async fn output_render_data(state: Rc) { loop { let container = state.pending_output_render_data.pop().await; if container.global.destroyed.get() { continue; } if container.update_render_data_scheduled.get() { container.update_render_data(); } } } impl OutputNode { pub fn perform_screencopies( &self, tex: &Rc, render_hardware_cursor: bool, x_off: i32, y_off: i32, size: Option<(i32, i32)>, ) { if let Some(workspace) = self.workspace.get() { if !workspace.capture.get() { return; } } self.global .perform_screencopies(tex, render_hardware_cursor, x_off, y_off, size); for sc in self.screencasts.lock().values() { sc.copy_texture(self, tex, render_hardware_cursor, x_off, y_off, size); } } pub fn clear(&self) { self.global.clear(); self.workspace.set(None); let workspaces: Vec<_> = self.workspaces.iter().collect(); for workspace in workspaces { workspace.clear(); } self.render_data.borrow_mut().titles.clear(); self.lock_surface.take(); self.jay_outputs.clear(); } pub fn on_spaces_changed(self: &Rc) { self.schedule_update_render_data(); if let Some(c) = self.workspace.get() { c.change_extents(&self.workspace_rect()); } } pub fn set_preferred_scale(self: &Rc, scale: Scale) { let old_scale = self.global.preferred_scale.replace(scale); if scale == old_scale { return; } let legacy_scale = scale.round_up(); if self.global.legacy_scale.replace(legacy_scale) != legacy_scale { self.global.send_mode(); } self.state.remove_output_scale(old_scale); self.state.add_output_scale(scale); let rect = self.calculate_extents(); self.change_extents_(&rect); let mut visitor = SurfaceSendPreferredScaleVisitor; self.node_visit_children(&mut visitor); for ws in self.workspaces.iter() { for stacked in ws.stacked.iter() { stacked.deref().clone().node_visit(&mut visitor); } } self.schedule_update_render_data(); } pub fn schedule_update_render_data(self: &Rc) { if !self.update_render_data_scheduled.replace(true) { self.state.pending_output_render_data.push(self.clone()); } } fn update_render_data(&self) { self.update_render_data_scheduled.set(false); let mut rd = self.render_data.borrow_mut(); rd.titles.clear(); rd.inactive_workspaces.clear(); rd.attention_requested_workspaces.clear(); rd.captured_inactive_workspaces.clear(); rd.active_workspace = None; rd.status = None; let mut pos = 0; let font = self.state.theme.font.borrow_mut(); let theme = &self.state.theme; let th = theme.sizes.title_height.get(); let scale = self.global.preferred_scale.get(); let scale = if scale != 1 { Some(scale.to_f64()) } else { None }; let mut texture_height = th; if let Some(scale) = scale { texture_height = (th as f64 * scale).round() as _; } let active_id = self.workspace.get().map(|w| w.id); let output_width = self.global.pos.get().width(); rd.underline = Rect::new_sized(0, th, output_width, 1).unwrap(); for ws in self.workspaces.iter() { let old_tex = ws.title_texture.take(); let mut title_width = th; 'create_texture: { if let Some(ctx) = self.state.render_ctx.get() { if th == 0 || ws.name.is_empty() { break 'create_texture; } let tc = match active_id == Some(ws.id) { true => theme.colors.focused_title_text.get(), false => theme.colors.unfocused_title_text.get(), }; let title = match text::render_fitting( &ctx, old_tex, Some(texture_height), &font, &ws.name, tc, false, scale, ) { Ok(t) => t, Err(e) => { log::error!("Could not render title {}: {}", ws.name, ErrorFmt(e)); break 'create_texture; } }; ws.title_texture.set(Some(title.clone())); let mut x = pos + 1; let (mut width, _) = title.texture.size(); if let Some(scale) = scale { width = (width as f64 / scale).round() as _; } if width + 2 > title_width { title_width = width + 2; } else { x = pos + (title_width - width) / 2; } rd.titles.push(OutputTitle { x1: pos, x2: pos + title_width, tex_x: x, tex_y: 0, tex: title.texture, ws: ws.deref().clone(), }); } } let rect = Rect::new_sized(pos, 0, title_width, th).unwrap(); if Some(ws.id) == active_id { rd.active_workspace = Some(OutputWorkspaceRenderData { rect, captured: ws.capture.get(), }); } else { if ws.attention_requests.active() { rd.attention_requested_workspaces.push(rect); } if ws.capture.get() { rd.captured_inactive_workspaces.push(rect); } else { rd.inactive_workspaces.push(rect); } } pos += title_width; } 'set_status: { let old_tex = rd.status.take().map(|s| s.tex); let ctx = match self.state.render_ctx.get() { Some(ctx) => ctx, _ => break 'set_status, }; let status = self.status.get(); if status.is_empty() { break 'set_status; } let tc = self.state.theme.colors.bar_text.get(); let title = match text::render_fitting( &ctx, old_tex, Some(texture_height), &font, &status, tc, true, scale, ) { Ok(t) => t, Err(e) => { log::error!("Could not render status {}: {}", status, ErrorFmt(e)); break 'set_status; } }; let (mut width, _) = title.texture.size(); if let Some(scale) = scale { width = (width as f64 / scale).round() as _; } let pos = output_width - width - 1; rd.status = Some(OutputStatus { tex_x: pos, tex_y: 0, tex: title, }); } } pub fn ensure_workspace(self: &Rc) -> Rc { if let Some(ws) = self.workspace.get() { return ws; } let name = 'name: { for i in 1.. { let name = i.to_string(); if !self.state.workspaces.contains(&name) { break 'name name; } } unreachable!(); }; self.create_workspace(&name) } pub fn show_workspace(&self, ws: &Rc) -> bool { let mut seats = SmallVec::new(); if let Some(old) = self.workspace.set(Some(ws.clone())) { if old.id == ws.id { return false; } collect_kb_foci2(old.clone(), &mut seats); if old.is_empty() { for jw in old.jay_workspaces.lock().values() { jw.send_destroyed(); jw.workspace.set(None); } old.clear(); self.state.workspaces.remove(&old.name); } else { old.set_visible(false); old.flush_jay_workspaces(); } } ws.set_visible(true); if let Some(fs) = ws.fullscreen.get() { fs.tl_change_extents(&self.global.pos.get()); } ws.change_extents(&self.workspace_rect()); for seat in seats { ws.clone().node_do_focus(&seat, Direction::Unspecified); } true } pub fn create_workspace(self: &Rc, name: &str) -> Rc { let ws = Rc::new(WorkspaceNode { id: self.state.node_ids.next(), is_dummy: false, output: CloneCell::new(self.clone()), position: Cell::new(Default::default()), container: Default::default(), stacked: Default::default(), seat_state: Default::default(), name: name.to_string(), output_link: Cell::new(None), visible: Cell::new(false), fullscreen: Default::default(), visible_on_desired_output: Cell::new(false), desired_output: CloneCell::new(self.global.output_id.clone()), jay_workspaces: Default::default(), capture: self.state.default_workspace_capture.clone(), title_texture: Default::default(), attention_requests: Default::default(), }); ws.output_link .set(Some(self.workspaces.add_last(ws.clone()))); self.state.workspaces.set(name.to_string(), ws.clone()); if self.workspace.is_none() { self.show_workspace(&ws); } let mut clients_to_kill = AHashMap::new(); for watcher in self.state.workspace_watchers.lock().values() { if let Err(e) = watcher.send_workspace(&ws) { clients_to_kill.insert(watcher.client.id, (watcher.client.clone(), e)); } } for (client, e) in clients_to_kill.values() { client.error(e); } self.schedule_update_render_data(); ws } fn workspace_rect(&self) -> Rect { let rect = self.global.pos.get(); let th = self.state.theme.sizes.title_height.get(); Rect::new_sized( rect.x1(), rect.y1() + th + 1, rect.width(), rect.height().sub(th + 1).max(0), ) .unwrap() } pub fn set_position(self: &Rc, x: i32, y: i32) { let pos = self.global.pos.get(); if (pos.x1(), pos.y1()) == (x, y) { return; } let rect = pos.at_point(x, y); self.change_extents_(&rect); } pub fn update_mode(self: &Rc, mode: Mode) { self.update_mode_and_transform(mode, self.global.transform.get()); } pub fn update_transform(self: &Rc, transform: Transform) { self.update_mode_and_transform(self.global.mode.get(), transform); } pub fn update_mode_and_transform(self: &Rc, mode: Mode, transform: Transform) { let old_mode = self.global.mode.get(); let old_transform = self.global.transform.get(); if (old_mode, old_transform) == (mode, transform) { return; } let (old_width, old_height) = self.global.pixel_size(); self.global.mode.set(mode); self.state .output_transforms .borrow_mut() .insert(self.global.output_id.clone(), transform); self.global.transform.set(transform); let (new_width, new_height) = self.global.pixel_size(); self.change_extents_(&self.calculate_extents()); if (old_width, old_height) != (new_width, new_height) { let mut to_destroy = vec![]; if let Some(ctx) = self.state.render_ctx.get() { for sc in self.screencasts.lock().values() { if let Err(e) = sc.realloc(&ctx) { log::error!( "Could not re-allocate buffers for screencast after mode change: {}", ErrorFmt(e) ); to_destroy.push(sc.clone()); } } } for sc in to_destroy { sc.do_destroy(); } } if transform != old_transform { self.state.refresh_hardware_cursors(); self.node_visit_children(&mut SurfaceSendPreferredTransformVisitor); } } fn calculate_extents(&self) -> Rect { let (mut width, mut height) = self.global.pixel_size(); let scale = self.global.preferred_scale.get(); if scale != 1 { let scale = scale.to_f64(); width = (width as f64 / scale).round() as _; height = (height as f64 / scale).round() as _; } let pos = self.global.pos.get(); pos.with_size(width, height).unwrap() } fn change_extents_(self: &Rc, rect: &Rect) { self.global.pos.set(*rect); self.state.root.update_extents(); self.schedule_update_render_data(); if let Some(ls) = self.lock_surface.get() { ls.change_extents(*rect); } if let Some(c) = self.workspace.get() { if let Some(fs) = c.fullscreen.get() { fs.tl_change_extents(rect); } c.change_extents(&self.workspace_rect()); } for layer in &self.layers { for surface in layer.iter() { surface.compute_position(); } } self.global.send_mode(); } pub fn find_layer_surface_at( &self, x: i32, y: i32, layers: &[u32], tree: &mut Vec, ) -> FindTreeResult { let len = tree.len(); for layer in layers.iter().copied() { for surface in self.layers[layer as usize].rev_iter() { let pos = surface.output_position(); if pos.contains(x, y) { let (x, y) = pos.translate(x, y); if surface.node_find_tree_at(x, y, tree) == FindTreeResult::AcceptsInput { return FindTreeResult::AcceptsInput; } tree.truncate(len); } } } FindTreeResult::Other } pub fn set_status(self: &Rc, status: &Rc) { self.status.set(status.clone()); self.schedule_update_render_data(); } fn pointer_move(self: &Rc, seat: &Rc, x: i32, y: i32) { self.pointer_positions.set(seat.id(), (x, y)); } pub fn has_fullscreen(&self) -> bool { self.workspace .get() .map(|w| w.fullscreen.is_some()) .unwrap_or(false) } } pub struct OutputTitle { pub x1: i32, pub x2: i32, pub tex_x: i32, pub tex_y: i32, pub tex: Rc, pub ws: Rc, } pub struct OutputStatus { pub tex_x: i32, pub tex_y: i32, pub tex: TextTexture, } #[derive(Copy, Clone)] pub struct OutputWorkspaceRenderData { pub rect: Rect, pub captured: bool, } #[derive(Default)] pub struct OutputRenderData { pub active_workspace: Option, pub underline: Rect, pub inactive_workspaces: Vec, pub attention_requested_workspaces: Vec, pub captured_inactive_workspaces: Vec, pub titles: Vec, pub status: Option, } impl Debug for OutputNode { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("OutputNode").finish_non_exhaustive() } } impl Node for OutputNode { 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_output(&self); } fn node_visit_children(&self, visitor: &mut dyn NodeVisitor) { if let Some(ls) = self.lock_surface.get() { visitor.visit_lock_surface(&ls); } for ws in self.workspaces.iter() { visitor.visit_workspace(ws.deref()); } for layers in &self.layers { for surface in layers.iter() { visitor.visit_layer_surface(surface.deref()); } } } fn node_visible(&self) -> bool { true } fn node_absolute_position(&self) -> Rect { self.global.pos.get() } fn node_do_focus(self: Rc, seat: &Rc, direction: Direction) { if self.state.lock.locked.get() { if let Some(lock) = self.lock_surface.get() { seat.focus_node(lock.surface.clone()); } return; } if let Some(ws) = self.workspace.get() { ws.node_do_focus(seat, direction); } } fn node_find_tree_at(&self, x: i32, mut y: i32, tree: &mut Vec) -> FindTreeResult { if self.state.lock.locked.get() { if let Some(ls) = self.lock_surface.get() { tree.push(FoundNode { node: ls.clone(), x, y, }); return ls.node_find_tree_at(x, y, tree); } return FindTreeResult::AcceptsInput; } { let res = self.find_layer_surface_at(x, y, &[OVERLAY, TOP], tree); if res.accepts_input() { return res; } } if let Some(ws) = self.workspace.get() { if let Some(fs) = ws.fullscreen.get() { tree.push(FoundNode { node: fs.clone().tl_into_node(), x, y, }); return fs.tl_as_node().node_find_tree_at(x, y, tree); } } { let (x_abs, y_abs) = self.global.pos.get().translate_inv(x, y); for stacked in self.state.root.stacked.rev_iter() { let ext = stacked.node_absolute_position(); if !stacked.node_visible() { continue; } if stacked.stacked_absolute_position_constrains_input() && !ext.contains(x_abs, y_abs) { // TODO: make constrain always true continue; } let (x, y) = ext.translate(x_abs, y_abs); let idx = tree.len(); tree.push(FoundNode { node: stacked.deref().clone().stacked_into_node(), x, y, }); match stacked.node_find_tree_at(x, y, tree) { FindTreeResult::AcceptsInput => { return FindTreeResult::AcceptsInput; } FindTreeResult::Other => { tree.truncate(idx); } } } } let bar_height = self.state.theme.sizes.title_height.get() + 1; if y >= bar_height { y -= bar_height; let len = tree.len(); if let Some(ws) = self.workspace.get() { tree.push(FoundNode { node: ws.clone(), x, y, }); ws.node_find_tree_at(x, y, tree); } if tree.len() == len { self.find_layer_surface_at(x, y, &[BOTTOM, BACKGROUND], tree); } } FindTreeResult::AcceptsInput } fn node_render(&self, renderer: &mut Renderer, x: i32, y: i32, _bounds: Option<&Rect>) { renderer.render_output(self, x, y); } fn node_on_button( self: Rc, seat: &Rc, _time_usec: u64, button: u32, state: KeyState, _serial: u32, ) { if state != KeyState::Pressed || button != BTN_LEFT { return; } let (x, y) = match self.pointer_positions.get(&seat.id()) { Some(p) => p, _ => return, }; if y >= self.state.theme.sizes.title_height.get() { return; } let ws = 'ws: { let rd = self.render_data.borrow_mut(); for title in &rd.titles { if x >= title.x1 && x < title.x2 { break 'ws title.ws.clone(); } } return; }; self.show_workspace(&ws); ws.flush_jay_workspaces(); self.schedule_update_render_data(); self.state.tree_changed(); } fn node_on_axis_event(self: Rc, seat: &Rc, event: &PendingScroll) { let steps = match self.scroll.handle(event) { Some(e) => e, _ => return, }; if steps == 0 { return; } let ws = match self.workspace.get() { Some(ws) => ws, _ => return, }; let mut ws = 'ws: { for r in self.workspaces.iter() { if r.id == ws.id { break 'ws r; } } return; }; for _ in 0..steps.abs() { let new = if steps < 0 { ws.prev() } else { ws.next() }; ws = match new { Some(n) => n, None => break, }; } if !self.show_workspace(&ws) { return; } ws.flush_jay_workspaces(); ws.deref() .clone() .node_do_focus(seat, Direction::Unspecified); self.schedule_update_render_data(); self.state.tree_changed(); } fn node_on_pointer_enter(self: Rc, seat: &Rc, x: Fixed, y: Fixed) { self.pointer_move(seat, x.round_down(), y.round_down()); } fn node_on_pointer_focus(&self, seat: &Rc) { // log::info!("output focus"); seat.set_known_cursor(KnownCursor::Default); } fn node_on_pointer_motion(self: Rc, seat: &Rc, x: Fixed, y: Fixed) { self.pointer_move(seat, x.round_down(), y.round_down()); } fn node_into_output(self: Rc) -> Option> { Some(self.clone()) } fn node_is_output(&self) -> bool { true } }