use { crate::{ acceptor::Acceptor, allocator::BufferObject, animation::{ AnimationCurve, AnimationState, AnimationTick, RetainedToplevel, expand_damage_rect, spawn_in_start_rect, }, async_engine::{AsyncEngine, SpawnedFuture}, backend::{ Backend, BackendConnectorState, BackendConnectorStateSerials, BackendDrmDevice, BackendEvent, Connector, ConnectorId, ConnectorIds, DrmDeviceId, DrmDeviceIds, HardwareCursorUpdate, InputDevice, InputDeviceGroupIds, InputDeviceId, InputDeviceIds, MonitorInfo, transaction::BackendConnectorTransactionError, }, backends::dummy::DummyBackend, cli::RunArgs, client::{Client, ClientCaps, ClientId, Clients, NUM_CACHED_SERIAL_RANGES, SerialRange}, clientmem::ClientMemOffset, cmm::{ cmm_description::ColorDescription, cmm_manager::ColorManager, cmm_render_intent::RenderIntent, }, compositor::{LIBEI_SOCKET, LogLevel}, config::ConfigProxy, copy_device::CopyDeviceRegistry, cpu_worker::CpuWorker, criteria::{clm::ClMatcherManager, tlm::TlMatcherManager}, cursor::{Cursor, ServerCursors}, cursor_user::{CursorUserGroup, CursorUserGroupId, CursorUserGroupIds, CursorUserIds}, damage::DamageVisualizer, dbus::Dbus, drm_feedback::{DrmFeedback, DrmFeedbackIds}, ei::{ ei_acceptor::EiAcceptor, ei_client::{EiClient, EiClients}, }, eventfd_cache::EventfdCache, fixed::Fixed, forker::ForkerProxy, format::Format, gfx_api::{ AcquireSync, AlphaMode, BufferResv, FdSync, GfxApi, GfxBlendBuffer, GfxContext, GfxError, GfxFramebuffer, GfxTexture, PendingShmTransfer, ReleaseSync, STAGING_DOWNLOAD, SampleRect, }, gfx_apis::create_gfx_context, globals::{Globals, GlobalsError, RemovableWaylandGlobal, WaylandGlobal}, icons::Icons, ifs::{ ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, ext_idle_notification_v1::ExtIdleNotificationV1, ext_session_lock_v1::ExtSessionLockV1, head_management::{ HeadManagers, HeadNames, jay_head_manager_session_v1::{HeadManagerEvent, JayHeadManagerSessionV1}, }, ipc::{ DataOfferIds, DataSourceIds, data_control::DataControlDeviceIds, x_data_device::XIpcDeviceIds, }, jay_render_ctx::JayRenderCtx, jay_screencast::JayScreencast, jay_seat_events::JaySeatEvents, jay_workspace_watcher::JayWorkspaceWatcher, wl_buffer::WlBuffer, wl_output::{BlendSpace, OutputGlobalOpt, OutputId, PersistentOutputState}, wl_seat::{ PhysicalKeyboardId, PhysicalKeyboardIds, PositionHintRequest, SeatIds, WlSeatGlobal, tablet::{TabletIds, TabletInit, TabletPadIds, TabletPadInit, TabletToolIds}, }, wl_surface::{ NoneSurfaceExt, tray::TrayItemIds, wl_subsurface::SubsurfaceIds, x_surface::xwindow::{Xwindow, XwindowId}, xdg_surface::XdgSurfaceConfigureEvent, zwp_idle_inhibitor_v1::{IdleInhibitorId, IdleInhibitorIds, ZwpIdleInhibitorV1}, zwp_input_popup_surface_v2::ZwpInputPopupSurfaceV2, }, wlr_output_manager::{ WlrOutputManagerState, zwlr_output_head_v1::ZwlrOutputHeadV1, zwlr_output_manager_v1::WlrOutputManagerId, }, workspace_manager::WorkspaceManagerState, wp_drm_lease_connector_v1::WpDrmLeaseConnectorV1, wp_drm_lease_device_v1::WpDrmLeaseDeviceV1Global, xdg_activation_token_v1::ActivationToken, zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1, zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1, zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, }, io_uring::IoUring, kbvm::{KbvmContext, KbvmMap}, keyboard::{KeyboardStateIds, LedsListener}, leaks::Tracker, logger::Logger, pr_caps::PrCapsThread, rect::{Rect, Region}, renderer::Renderer, scale::Scale, security_context_acceptor::SecurityContextAcceptors, tagged_acceptor::TaggedAcceptors, theme::{BarPosition, Color, Theme, ThemeColor, ThemeSized}, time::Time, tree::{ ContainerNode, ContainerSplit, Direction, DisplayNode, FindTreeUsecase, FloatNode, FoundNode, LatchListener, Node, NodeId, NodeIds, NodeVisitorBase, OutputNode, PlaceholderNode, TearingMode, TileState, ToplevelData, ToplevelIdentifier, ToplevelNode, ToplevelNodeBase, Transform, VrrMode, WorkspaceDisplayOrder, WorkspaceNode, WorkspaceNodeId, WsMoveConfig, generic_node_visitor, move_ws_to_output, }, udmabuf::UdmabufHolder, utils::{ asyncevent::AsyncEvent, bindings::Bindings, clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, event_listener::{EventListener, EventSource, LazyEventSources}, fdcloser::FdCloser, hash_map_ext::HashMapExt, linkedlist::LinkedList, numcell::NumCell, object_drop_queue::ObjectDropQueue, queue::AsyncQueue, refcounted::RefCounted, run_toplevel::RunToplevel, }, video::{ dmabuf::DmaBufIds, drm::{Drm, wait_for_syncobj::WaitForSyncobj}, }, virtual_output::VirtualOutputs, wheel::Wheel, wire::{ ExtForeignToplevelListV1Id, ExtIdleNotificationV1Id, JayHeadManagerSessionV1Id, JayRenderCtxId, JaySeatEventsId, JayWorkspaceWatcherId, ZwlrForeignToplevelManagerV1Id, ZwpLinuxDmabufFeedbackV1Id, }, xwayland::{self, XWaylandEvent}, }, ahash::AHashMap, bstr::ByteSlice, jay_config::PciId, std::{ cell::{Cell, RefCell}, fmt::{Debug, Formatter}, mem, ops::{Deref, DerefMut}, rc::{Rc, Weak}, sync::Arc, time::{Duration, SystemTime}, }, thiserror::Error, uapi::{OwnedFd, c}, }; pub struct State { pub pid: c::pid_t, pub kb_ctx: KbvmContext, pub backend: CloneCell>, pub forker: CloneCell>>, pub default_keymap: Rc, pub eng: Rc, pub render_ctx: CloneCell>>, pub drm_feedback: CloneCell>>, pub drm_feedback_consumers: CopyHashMap<(ClientId, ZwpLinuxDmabufFeedbackV1Id), Rc>, pub render_ctx_version: NumCell, pub render_ctx_ever_initialized: Cell, pub cursors: CloneCell>>, pub wheel: Rc, pub clients: Clients, pub globals: Globals, pub connector_ids: ConnectorIds, pub drm_dev_ids: DrmDeviceIds, pub seat_ids: SeatIds, pub idle_inhibitor_ids: IdleInhibitorIds, pub input_device_ids: InputDeviceIds, pub node_ids: NodeIds, pub root: Rc, pub workspaces: CopyHashMap>, pub dummy_output: CloneCell>>, pub backend_events: AsyncQueue, pub input_device_handlers: RefCell>, pub seat_queue: LinkedList>, pub slow_clients: AsyncQueue>, pub none_surface_ext: Rc, pub tree_changed_sent: Cell, pub config: CloneCell>>, pub theme: Theme, pub pending_container_layout: AsyncQueue>, pub pending_container_render_positions: AsyncQueue>, pub pending_output_render_data: AsyncQueue>, pub pending_float_layout: AsyncQueue>, pub pending_input_popup_positioning: AsyncQueue>, pub pending_toplevel_screencasts: AsyncQueue>, pub pending_screencast_reallocs_or_reconfigures: AsyncQueue>, pub pending_placeholder_render_textures: AsyncQueue>, pub pending_container_tab_render_textures: AsyncQueue>, pub dbus: Dbus, pub fdcloser: Arc, pub logger: Option>, pub connectors: CopyHashMap>, pub outputs: CopyHashMap>, pub wlr_output_managers: WlrOutputManagerState, pub drm_devs: CopyHashMap>, pub status: CloneCell>, pub idle: IdleState, pub run_args: RunArgs, pub xwayland: XWaylandState, pub acceptor: CloneCell>>, pub serial: NumCell, pub run_toplevel: Rc, pub config_dir: Option, pub config_file_id: NumCell, pub tracker: Tracker, pub data_offer_ids: DataOfferIds, pub data_source_ids: DataSourceIds, pub ring: Rc, pub lock: ScreenlockState, pub scales: RefCounted, pub cursor_sizes: RefCounted, pub hardware_tick_cursor: AsyncQueue>>, pub testers: RefCell>>, pub render_ctx_watchers: CopyHashMap<(ClientId, JayRenderCtxId), Rc>, pub workspace_watchers: CopyHashMap<(ClientId, JayWorkspaceWatcherId), Rc>, pub default_workspace_capture: Cell, pub default_gfx_api: Cell, pub activation_tokens: CopyHashMap, pub toplevel_lists: CopyHashMap<(ClientId, ExtForeignToplevelListV1Id), Rc>, pub dma_buf_ids: Rc, pub drm_feedback_ids: DrmFeedbackIds, pub direct_scanout_enabled: Cell, pub persistent_output_states: CopyHashMap, Rc>, pub double_click_interval_usec: Cell, pub double_click_distance: Cell, pub create_default_seat: Cell, pub subsurface_ids: SubsurfaceIds, pub wait_for_syncobj: Rc, pub explicit_sync_enabled: Cell, pub explicit_sync_supported: Cell, pub keyboard_state_ids: KeyboardStateIds, pub physical_keyboard_ids: PhysicalKeyboardIds, pub security_context_acceptors: SecurityContextAcceptors, pub tagged_acceptors: TaggedAcceptors, pub cursor_user_group_ids: CursorUserGroupIds, pub cursor_user_ids: CursorUserIds, pub cursor_user_groups: CopyHashMap>, pub cursor_user_group_hardware_cursor: CloneCell>>, pub input_device_group_ids: InputDeviceGroupIds, pub tablet_ids: TabletIds, pub tablet_tool_ids: TabletToolIds, pub tablet_pad_ids: TabletPadIds, pub damage_visualizer: DamageVisualizer, pub default_vrr_mode: Cell, pub default_vrr_cursor_hz: Cell>, pub default_tearing_mode: Cell, pub ei_acceptor: CloneCell>>, pub ei_acceptor_future: CloneCell>>, pub enable_ei_acceptor: Cell, pub ei_clients: EiClients, pub slow_ei_clients: AsyncQueue>, pub cpu_worker: Rc, pub ui_drag_enabled: Cell, pub ui_drag_threshold_squared: Cell, pub animations: AnimationState, pub layout_animations_requested: Cell, pub layout_animations_active: Cell, pub suppress_animations_for_next_layout: Cell, pub toplevels: CopyHashMap>, pub const_40hz_latch: EventSource, pub tray_item_ids: TrayItemIds, pub data_control_device_ids: DataControlDeviceIds, pub workspace_managers: WorkspaceManagerState, pub toplevel_managers: CopyHashMap<(ClientId, ZwlrForeignToplevelManagerV1Id), Rc>, pub color_management_enabled: Cell, pub color_manager: Rc, pub float_above_fullscreen: Cell, pub icons: Icons, #[allow(dead_code)] pub show_pin_icon: Cell, pub cl_matcher_manager: ClMatcherManager, pub tl_matcher_manager: TlMatcherManager, pub caps_thread: Option, pub node_at_tree: RefCell>, pub position_hint_requests: AsyncQueue, pub pending_warp_mouse_to_focus: AsyncQueue>, pub backend_connector_state_serials: BackendConnectorStateSerials, pub head_names: HeadNames, pub head_managers: CopyHashMap<(ClientId, JayHeadManagerSessionV1Id), Rc>, pub head_managers_async: AsyncQueue, pub show_bar: Cell, pub enable_primary_selection: Cell, pub xdg_surface_configure_events: AsyncQueue, pub workspace_display_order: Cell, pub outputs_without_hc: NumCell, pub udmabuf: Rc, pub gfx_ctx_changed: EventSource, pub copy_device_registry: Rc, pub supports_presentation_feedback: Cell, pub eventfd_cache: Rc, pub lazy_event_sources: Rc, pub post_layout_event_sources: Rc, pub bo_drop_queue: Rc>>, pub virtual_outputs: VirtualOutputs, pub clean_logs_older_than: Cell>, } // impl Drop for State { // fn drop(&mut self) { // log::info!("drop state"); // } // } impl Debug for State { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("State").finish_non_exhaustive() } } pub struct ScreenlockState { pub locked: Cell, pub lock: CloneCell>>, } pub struct XWaylandState { pub enabled: Cell, pub running: Cell, pub pidfd: CloneCell>>, pub handler: RefCell>>, pub queue: Rc>, pub ipc_device_ids: XIpcDeviceIds, pub use_wire_scale: Cell, pub wire_scale: Cell>, pub windows: CopyHashMap>, pub client: CloneCell>>, pub display: CloneCell>>, } pub struct IdleState { pub input: Cell, pub change: AsyncEvent, pub timeout: Cell, pub grace_period: Cell, pub timeout_changed: Cell, pub inhibitors: CopyHashMap>, pub inhibitors_changed: Cell, pub backend_idle: Cell, pub inhibited_idle_notifications: CopyHashMap<(ClientId, ExtIdleNotificationV1Id), Rc>, pub in_grace_period: Cell, } impl IdleState { pub fn set_timeout(&self, state: &State, timeout: Duration) { self.timeout.set(timeout); self.timeout_changed(state); } pub fn set_grace_period(&self, state: &State, grace_period: Duration) { self.grace_period.set(grace_period); self.timeout_changed(state); } fn timeout_changed(&self, _state: &State) { self.timeout_changed.set(true); self.change.trigger(); } pub fn add_inhibitor(&self, state: &State, inhibitor: &Rc) { self.inhibitors.set(inhibitor.inhibit_id, inhibitor.clone()); self.inhibitors_changed(state); } pub fn remove_inhibitor(&self, state: &State, inhibitor: &ZwpIdleInhibitorV1) { self.inhibitors.remove(&inhibitor.inhibit_id); self.inhibitors_changed(state); if self.inhibitors.is_empty() { self.resume_inhibited_notifications(); } } fn inhibitors_changed(&self, _state: &State) { self.inhibitors_changed.set(true); self.change.trigger(); } fn resume_inhibited_notifications(&self) { for notification in self.inhibited_idle_notifications.lock().drain_values() { notification.resume.trigger(); } } pub fn add_inhibited_notification(&self, n: &Rc) { self.inhibited_idle_notifications .set((n.client.id, n.id), n.clone()); } pub fn remove_inhibited_notification(&self, n: &ExtIdleNotificationV1) { self.inhibited_idle_notifications .remove(&(n.client.id, n.id)); } } pub struct InputDeviceData { pub _handler: SpawnedFuture<()>, pub id: InputDeviceId, pub data: Rc, pub async_event: Rc, } pub struct DeviceHandlerData { pub keyboard_id: PhysicalKeyboardId, pub seat: CloneCell>>, pub px_per_scroll_wheel: Cell, pub device: Rc, pub syspath: Option, pub devnode: Option, pub keymap: CloneCell>>, pub output: CloneCell>>, pub tablet_init: Option>, pub tablet_pad_init: Option>, pub is_touch: bool, pub is_kb: bool, pub mods_listener: EventListener, } pub struct ConnectorData { pub id: ConnectorId, pub connector: Rc, pub handler: Cell>>, pub connected: Cell, pub name: Rc, pub description: RefCell, pub drm_dev: Option>, pub async_event: Rc, pub damaged: Cell, pub damage: RefCell>, pub needs_vblank_emulation: Cell, pub damage_intersect: Cell, pub state: RefCell, pub head_managers: HeadManagers, pub wlr_output_heads: CopyHashMap>, } pub struct OutputData { pub connector: Rc, pub monitor_info: Rc, pub node: Option>, pub lease_connectors: Rc>, } pub struct DrmDevData { pub dev: Rc, pub handler: Cell>>, pub connectors: CopyHashMap>, pub syspath: Option, pub devnode: Option, pub vendor: Option, pub model: Option, pub pci_id: Option, pub lease_global: Rc, } impl ConnectorData { pub fn damage(&self) { if !self.damaged.replace(true) { self.connector.damage(); } } pub fn modify_state( &self, state: &State, f: impl FnOnce(&mut BackendConnectorState), ) -> Result<(), BackendConnectorTransactionError> { let old = self.state.borrow().clone(); let mut s = old.clone(); f(&mut s); if old == s { return Ok(()); } s.serial = state.backend_connector_state_serials.next(); let mut tran = self.connector.create_transaction()?; tran.add(&self.connector, s.clone())?; tran.prepare()?.apply()?.commit(); self.set_state(state, s); Ok(()) } pub fn set_state(&self, state: &State, s: BackendConnectorState) { let old = self.state.borrow().clone(); if old.serial >= s.serial { return; } *self.state.borrow_mut() = s.clone(); if old.enabled != s.enabled { self.head_managers.handle_enabled_change(state, s.enabled); } if old.active != s.active { self.head_managers.handle_active_change(s.active); } if old.non_desktop_override != s.non_desktop_override { self.head_managers .handle_non_desktop_override_changed(s.non_desktop_override); } if old.vrr != s.vrr { self.head_managers.handle_vrr_change(s.vrr); } if old.tearing != s.tearing { self.head_managers.handle_tearing_enabled_change(s.tearing); } if old.format != s.format { self.head_managers.handle_format_change(s.format); } if (old.color_space, old.eotf) != (s.color_space, s.eotf) { self.head_managers .handle_colors_change(s.color_space, s.eotf); } if old.mode != s.mode { self.head_managers.handle_mode_change(s.mode); for head in self.wlr_output_heads.lock().values() { head.handle_mode_change(s.mode); } } if let Some(output) = state.outputs.get(&self.connector.id()) && let Some(node) = &output.node { node.update_state(old, s); } } } impl DrmDevData { pub fn make_render_device(&self) { log::info!( "Making {} the render device", self.devnode.as_deref().unwrap_or("unknown"), ); self.dev.clone().make_render_device(); } pub fn set_direct_scanout_enabled(&self, _state: &State, enabled: bool) { self.dev.set_direct_scanout_enabled(enabled); } pub fn set_flip_margin(&self, _state: &State, margin: u64) { self.dev.set_flip_margin(margin); } } struct UpdateTextTexturesVisitor; impl NodeVisitorBase for UpdateTextTexturesVisitor { fn visit_container(&mut self, node: &Rc) { node.node_visit_children(self); } fn visit_output(&mut self, node: &Rc) { node.schedule_update_render_data(); node.node_visit_children(self); } fn visit_float(&mut self, node: &Rc) { node.node_visit_children(self); } fn visit_workspace(&mut self, node: &Rc) { node.title_texture.take(); node.node_visit_children(self); } fn visit_placeholder(&mut self, node: &Rc) { node.textures.borrow_mut().clear(); node.schedule_update_texture(); node.node_visit_children(self); } } impl State { pub fn create_gfx_context( &self, drm: &Drm, api: Option, ) -> Result, GfxError> { create_gfx_context( &self.eng, &self.ring, &self.eventfd_cache, drm, api.unwrap_or(self.default_gfx_api.get()), self.caps_thread.as_ref(), ) } pub fn add_output_scale(&self, scale: Scale) { if self.scales.add(scale) { self.output_scales_changed(); } } pub fn remove_output_scale(&self, scale: Scale) { if self.scales.remove(&scale) { self.output_scales_changed(); } } pub fn add_cursor_size(&self, size: u32) { if self.cursor_sizes.add(size) { self.cursor_sizes_changed(); } } pub fn remove_cursor_size(&self, size: u32) { if self.cursor_sizes.remove(&size) { self.cursor_sizes_changed(); } } fn output_scales_changed(&self) { UpdateTextTexturesVisitor.visit_display(&self.root); self.reload_cursors(); self.update_xwayland_wire_scale(); self.icons.update_sizes(self); } fn cursor_sizes_changed(&self) { self.reload_cursors(); } pub fn devices_enumerated(&self) { if let Some(config) = self.config.get() { config.devices_enumerated() } if self.render_ctx.is_none() { for dev in self.drm_devs.lock().values() { if let Ok(version) = dev.dev.version() && version.name.contains_str("nvidia") { continue; } if dev.dev.gfx_fast_ram_access() { continue; } dev.make_render_device(); if self.render_ctx.is_some() { break; } } if self.render_ctx.is_none() && let Some(dev) = self.drm_devs.lock().values().next() { dev.make_render_device(); } } } pub fn set_render_ctx(&self, ctx: Option>) { self.explicit_sync_supported.set(false); self.render_ctx.set(ctx.clone()); self.render_ctx_version.fetch_add(1); self.cursors.set(None); self.drm_feedback.set(None); self.icons.clear(); self.wait_for_syncobj .set_ctx(ctx.as_ref().and_then(|c| c.syncobj_ctx().cloned())); self.virtual_outputs.handle_render_ctx_change(self); 'handle_new_feedback: { if let Some(ctx) = &ctx { let feedback = match DrmFeedback::new(&self.drm_feedback_ids, &**ctx) { Ok(fb) => fb, Err(e) => { log::error!("Could not create new DRM feedback: {}", ErrorFmt(e)); break 'handle_new_feedback; } }; for watcher in self.drm_feedback_consumers.lock().values() { watcher.send_feedback(&feedback); } self.drm_feedback.set(Some(Rc::new(feedback))); } } { struct Walker; impl NodeVisitorBase for Walker { fn visit_container(&mut self, node: &Rc) { node.node_visit_children(self); } fn visit_workspace(&mut self, node: &Rc) { node.title_texture.take(); node.node_visit_children(self); } fn visit_output(&mut self, node: &Rc) { node.render_data.borrow_mut().titles.clear(); node.render_data.borrow_mut().status.take(); node.set_hardware_cursor(None); node.node_visit_children(self); } fn visit_float(&mut self, node: &Rc) { node.node_visit_children(self); } fn visit_placeholder(&mut self, node: &Rc) { node.textures.borrow_mut().clear(); node.node_visit_children(self); } } Walker.visit_display(&self.root); let mut updated_buffers = AHashMap::new(); for buffer in self.gfx_ctx_changed.iter() { let had_buffer_texture = buffer.handle_gfx_context_change(); updated_buffers.insert(Rc::as_ptr(&buffer), had_buffer_texture); } for client in self.clients.clients.borrow_mut().values() { for surface in client.data.objects.surfaces.lock().values() { let had_shm_texture = surface.reset_shm_textures(); if let Some(buffer) = surface.buffer.get() { let buf = &buffer.buffer.buf; let had_buffer_texture = *updated_buffers.get(&Rc::as_ptr(buf)).unwrap(); if had_shm_texture || had_buffer_texture { buf.update_texture_or_log(surface, true); } } } } } if ctx.is_some() { self.reload_cursors(); UpdateTextTexturesVisitor.visit_display(&self.root); } for cursor_user_groups in self.cursor_user_groups.lock().values() { cursor_user_groups.render_ctx_changed(); } if let Some(ctx) = &ctx { if let Some(ctx) = ctx.syncobj_ctx() && ctx.supports_async_wait() { self.explicit_sync_supported.set(true); } if !self.render_ctx_ever_initialized.replace(true) && let Some(config) = self.config.get() { config.graphics_initialized(); } } for watcher in self.render_ctx_watchers.lock().values() { watcher.send_render_ctx(ctx.clone()); } let mut scs = vec![]; for client in self.clients.clients.borrow_mut().values() { for sc in client.data.objects.screencasts.lock().values() { scs.push(sc.clone()); } for sc in client.data.objects.ext_copy_sessions.lock().values() { sc.stop(); } } for sc in scs { sc.do_destroy(); } self.expose_new_singletons(); } fn reload_cursors(&self) { if let Some(ctx) = self.render_ctx.get() { let cursors = match ServerCursors::load(&ctx, self) { Ok(c) => c.map(Rc::new), Err(e) => { log::error!("Could not load the cursors: {}", ErrorFmt(e)); None } }; self.cursors.set(cursors); for cursor_user_group in self.cursor_user_groups.lock().values() { cursor_user_group.reload_known_cursor(); } } } pub fn add_global(&self, global: &Rc) { self.globals.add_global(self, global) } pub fn remove_global( &self, global: &Rc, ) -> Result<(), GlobalsError> { self.globals.remove(self, global) } pub fn tree_changed(&self) { // log::info!("state.tree_changed\n{:?}", Backtrace::new()); if self.tree_changed_sent.replace(true) { return; } let seats = self.globals.seats.lock(); for seat in seats.values() { seat.trigger_tree_changed(false); } } pub fn ensure_map_workspace(&self, seat: Option<&Rc>) -> Rc { seat.cloned() .or_else(|| self.seat_queue.last().map(|s| s.deref().clone())) .map(|s| s.get_fallback_output()) .or_else(|| self.root.outputs.lock().values().next().cloned()) .or_else(|| self.dummy_output.get()) .unwrap() .ensure_workspace() } pub fn map_tiled(self: &Rc, node: Rc) { let seat = self.seat_queue.last(); self.do_map_tiled(seat.as_deref(), node.clone()); self.focus_after_map(node, seat.as_deref()); } fn do_map_tiled(self: &Rc, seat: Option<&Rc>, node: Rc) { let ws = self.ensure_map_workspace(seat); self.map_tiled_on(node, &ws); } pub fn map_tiled_on(self: &Rc, node: Rc, ws: &Rc) { if let Some(c) = ws.container.get() { let la = c.clone().tl_last_active_child(); let lap = la .tl_data() .parent .get() .and_then(|n| n.node_into_container()); if let Some(lap) = lap { lap.add_child_after(&*la, node); } else { c.append_child(node); } } else { let container = ContainerNode::new(self, ws, node, ContainerSplit::Horizontal); ws.set_container(&container); } } pub fn map_floating( self: &Rc, node: Rc, mut width: i32, mut height: i32, workspace: &Rc, abs_pos: Option<(i32, i32)>, ) { width += 2 * self.theme.sizes.border_width.get(); height += 2 * self.theme.sizes.border_width.get() + self.theme.title_plus_underline_height(); let output = workspace.output.get(); let output_rect = output.global.pos.get(); let position = if let Some((mut x1, mut y1)) = abs_pos { if y1 <= output_rect.y1() { y1 = output_rect.y1() + 1; } if y1 > output_rect.y2() { y1 = output_rect.y2(); } y1 -= self.theme.sizes.border_width.get() + self.theme.title_plus_underline_height(); x1 -= self.theme.sizes.border_width.get(); Rect::new_sized_saturating(x1, y1, width, height) } else { let mut x1 = output_rect.x1(); let mut y1 = output_rect.y1(); if width < output_rect.width() { x1 += (output_rect.width() - width) / 2; } else { width = output_rect.width(); } if height < output_rect.height() { y1 += (output_rect.height() - height) / 2; } else { height = output_rect.height(); } Rect::new_sized_saturating(x1, y1, width, height) }; FloatNode::new(self, workspace, position, node.clone()); self.focus_after_map(node, self.seat_queue.last().as_deref()); } fn focus_after_map(&self, node: Rc, seat: Option<&Rc>) { if !node.node_visible() { return; } let Some(seat) = seat else { return; }; if let Some(config) = self.config.get() && !config.auto_focus(node.tl_data()) { return; } node.node_do_focus(&seat, Direction::Unspecified); } pub fn show_workspace2( &self, seat: Option<&Rc>, output: &Rc, ws: &Rc, ) { let mut pinned_is_focused = false; if let Some(seat) = seat { for pinned in output.pinned.iter() { pinned .deref() .clone() .node_visit(&mut generic_node_visitor(|node| { node.node_seat_state().for_each_kb_focus(|s| { pinned_is_focused |= s.id() == seat.id(); }); })); } } let did_change = output.show_workspace(&ws); if !pinned_is_focused && let Some(seat) = seat { ws.clone().node_do_focus(seat, Direction::Unspecified); } if !did_change { return; } ws.flush_jay_workspaces(); if !output.is_dummy { output.schedule_update_render_data(); self.tree_changed(); output.workspace_switched.trigger(); } } pub fn show_workspace( &self, seat: &Rc, name: &str, output: Option>, ) { let output = output.unwrap_or_else(|| seat.get_fallback_output()); let ws = match output.find_workspace(name) { Some(ws) => ws, _ => { if output.is_dummy { log::warn!("Not showing workspace because seat is on dummy output"); return; } output.create_workspace(name) } }; self.show_workspace2(Some(seat), &ws.output.get(), &ws); seat.maybe_schedule_warp_mouse_to_focus(); } pub fn float_map_ws(&self) -> Rc { if let Some(seat) = self.seat_queue.last() { let output = seat.get_fallback_output(); if !output.is_dummy { return output.ensure_workspace(); } } if let Some(output) = self.root.outputs.lock().values().next().cloned() { return output.ensure_workspace(); } self.dummy_output.get().unwrap().ensure_workspace() } pub fn set_status(&self, status: &str) { let status = Rc::new(status.to_owned()); self.status.set(status.clone()); let outputs = self.root.outputs.lock(); for output in outputs.values() { output.set_status(&status); } } pub fn input_occurred(&self) { if !self.idle.input.replace(true) { self.idle.change.trigger(); } } pub fn start_xwayland(self: &Rc) { if !self.xwayland.enabled.get() { return; } let mut handler = self.xwayland.handler.borrow_mut(); if handler.is_none() { *handler = Some(self.eng.spawn("xwayland", xwayland::manage(self.clone()))); } } pub fn stop_xwayland(&self) { if self.xwayland.running.get() { return; } self.xwayland.handler.take(); } pub fn set_xwayland_enabled(self: &Rc, enabled: bool) { if self.xwayland.enabled.replace(enabled) == enabled { return; } if enabled { self.start_xwayland(); } else { self.stop_xwayland(); } } pub fn set_xwayland_use_wire_scale(&self, use_wire_scale: bool) { if self.xwayland.use_wire_scale.replace(use_wire_scale) == use_wire_scale { return; } self.update_xwayland_wire_scale(); } pub fn next_serial(&self, client: Option<&Client>) -> u64 { let serial = self.serial.fetch_add(1); if let Some(client) = client { 'update_range: { let mut serials = client.serials.borrow_mut(); if let Some(last) = serials.back_mut() && last.hi.wrapping_add(1) == serial { last.hi = serial; break 'update_range; } if serials.len() >= NUM_CACHED_SERIAL_RANGES { serials.pop_front(); } serials.push_back(SerialRange { lo: serial, hi: serial, }); } } serial as _ } pub fn damage(&self, rect: Rect) { self.damage2(false, false, rect); } pub fn damage2(&self, cursor: bool, skip_hc: bool, rect: Rect) { if rect.is_empty() { return; } self.damage_visualizer.add(rect); for output in self.root.outputs.lock().values() { if output.global.pos.get().intersects(&rect) { if skip_hc && output.hardware_cursor.is_some() { continue; } output.global.add_damage_area(&rect); if cursor && output.schedule.defer_cursor_updates() { output.schedule.software_cursor_changed(); } else { output.global.connector.damage(); } } } } pub fn do_unlock(&self) { self.lock.locked.set(false); self.lock.lock.take(); for output in self.root.outputs.lock().values() { if let Some(surface) = output.set_lock_surface(None) { surface.destroy_node(); } } self.tree_changed(); self.damage(self.root.extents.get()); } pub fn clear(&self) { self.lock.lock.take(); self.xwayland.handler.borrow_mut().take(); self.clients.clear(); if let Some(config) = self.config.set(None) { config.clear(); } if let Some(forker) = self.forker.set(None) { forker.clear(); } self.acceptor.set(None); self.backend.set(Rc::new(DummyBackend)).clear(); self.run_toplevel.clear(); self.xwayland.handler.borrow_mut().take(); self.xwayland.queue.clear(); self.xwayland.windows.clear(); self.idle.inhibitors.clear(); self.idle.change.clear(); for drm_dev in self.drm_devs.lock().drain_values() { drm_dev.handler.take(); drm_dev.connectors.clear(); } for connector in self.connectors.lock().drain_values() { connector.handler.take(); connector.async_event.clear(); } self.outputs.clear(); for output in self.root.outputs.lock().values() { output.clear(); } self.wlr_output_managers.clear(); self.dbus.clear(); self.pending_container_layout.clear(); self.pending_container_render_positions.clear(); self.pending_output_render_data.clear(); self.pending_float_layout.clear(); self.pending_input_popup_positioning.clear(); self.pending_toplevel_screencasts.clear(); self.pending_screencast_reallocs_or_reconfigures.clear(); self.pending_placeholder_render_textures.clear(); self.pending_container_tab_render_textures.clear(); self.animations.clear(); self.layout_animations_requested.set(false); self.layout_animations_active.set(false); self.suppress_animations_for_next_layout.set(false); self.render_ctx_watchers.clear(); self.workspace_watchers.clear(); self.toplevel_lists.clear(); self.security_context_acceptors.clear(); self.tagged_acceptors.clear(); self.slow_clients.clear(); for h in self.input_device_handlers.borrow_mut().drain_values() { h.async_event.clear(); } self.backend_events.clear(); self.workspaces.clear(); { let seats = mem::take(self.globals.seats.lock().deref_mut()); for seat in seats.values() { seat.clear(); } } self.globals.clear(); self.render_ctx.set(None); self.root.clear(); if let Some(output) = self.dummy_output.set(None) { output.clear(); } self.wheel.clear(); self.eng.clear(); self.ei_acceptor.take(); self.ei_acceptor_future.take(); self.ei_clients.clear(); self.slow_ei_clients.clear(); self.toplevels.clear(); self.workspace_managers.clear(); self.cl_matcher_manager.clear(); self.tl_matcher_manager.clear(); self.node_at_tree.borrow_mut().clear(); self.position_hint_requests.clear(); self.pending_warp_mouse_to_focus.clear(); self.head_managers.clear(); self.head_managers_async.clear(); self.const_40hz_latch.clear(); self.cursor_user_groups.clear(); self.cursor_user_group_hardware_cursor.take(); self.cpu_worker.clear(); self.wait_for_syncobj.clear(); self.xdg_surface_configure_events.clear(); self.lazy_event_sources.clear(); self.post_layout_event_sources.clear(); self.bo_drop_queue.kill(); self.virtual_outputs.clear(); } pub fn remove_toplevel_id(&self, id: ToplevelIdentifier) { self.toplevels.remove(&id); if let Some(config) = self.config.get() { config.toplevel_removed(id); } } pub fn damage_hardware_cursors(&self, render: bool) { for output in self.root.outputs.lock().values() { if let Some(hc) = output.hardware_cursor.get() { if render { output.hardware_cursor_needs_render.set(true); } hc.damage(); } } } pub fn refresh_hardware_cursors(&self) { if let Some(g) = self.cursor_user_group_hardware_cursor.get() && let Some(u) = g.active() { u.update_hardware_cursor(); return; } self.damage_hardware_cursors(false) } pub fn present_hardware_cursor( &self, output: &Rc, hc: &mut dyn HardwareCursorUpdate, ) { if self.idle.in_grace_period.get() { hc.set_enabled(false); return; } let Some(g) = self.cursor_user_group_hardware_cursor.get() else { hc.set_enabled(false); return; }; g.present_hardware_cursor(output, hc); } pub fn for_each_seat_tester(&self, f: F) { let testers = self.testers.borrow_mut(); for tester in testers.values() { f(tester); } } pub fn present_output( &self, output: &OutputNode, fb: &Rc, cd: &Rc, acquire_sync: AcquireSync, release_sync: ReleaseSync, tex: &Rc, render_hw_cursor: bool, blend_buffer: Option<&Rc>, blend_cd: &Rc, ) -> Result, GfxError> { let sync = fb.render_output( acquire_sync, release_sync, cd, output, self, Some(output.global.pos.get()), output.global.persistent.scale.get(), render_hw_cursor, true, blend_buffer, blend_cd, )?; output.latched(false); output.perform_screencopies( tex, cd, None, &AcquireSync::Unnecessary, ReleaseSync::None, !render_hw_cursor, 0, 0, None, ); Ok(sync) } pub fn perform_screencopy( &self, src: &Rc, resv: Option<&Rc>, acquire_sync: &AcquireSync, release_sync: ReleaseSync, src_cd: &Rc, target: &Rc, target_acquire_sync: AcquireSync, target_release_sync: ReleaseSync, target_transform: Transform, target_cd: &Rc, position: Rect, render_hardware_cursors: bool, x_off: i32, y_off: i32, size: Option<(i32, i32)>, transform: Transform, scale: Scale, ) -> Result, GfxError> { let mut ops = vec![]; let mut renderer = Renderer { base: target.renderer_base(&mut ops, scale, target_transform), state: self, logical_extents: position.at_point(0, 0), pixel_extents: { let (width, height) = target.logical_size(target_transform); Rect::new_sized_saturating(0, 0, width, height) }, stretch: None, corner_radius: None, }; let mut sample_rect = SampleRect::identity(); sample_rect.buffer_transform = transform; renderer.base.render_texture( src, None, x_off, y_off, Some(sample_rect), size, scale, None, resv.cloned(), acquire_sync.clone(), release_sync, false, src_cd, RenderIntent::Perceptual, AlphaMode::PremultipliedElectrical, ); if render_hardware_cursors && let Some(cursor_user_group) = self.cursor_user_group_hardware_cursor.get() && let Some(cursor_user) = cursor_user_group.active() && let Some(cursor) = cursor_user.get() { let (mut x, mut y) = cursor_user.position(); x = x + x_off - Fixed::from_int(position.x1()); y = y + y_off - Fixed::from_int(position.y1()); cursor.render(&mut renderer, x, y); } target.render( target_acquire_sync, target_release_sync, target_cd, &ops, Some(&Color::SOLID_BLACK), &target_cd.linear, None, target_cd, ) } pub fn perform_shm_screencopy( &self, src: &Rc, src_cd: &Rc, acquire_sync: &AcquireSync, position: Rect, x_off: i32, y_off: i32, size: Option<(i32, i32)>, capture: &Rc, mem: &Rc, stride: i32, format: &'static Format, transform: Transform, scale: Scale, ) -> Result, ShmScreencopyError> { let Some(ctx) = self.render_ctx.get() else { return Err(ShmScreencopyError::NoRenderContext); }; let fb = ctx .clone() .create_internal_fb( &self.cpu_worker, capture.rect.width(), capture.rect.height(), stride, format, ) .map_err(ShmScreencopyError::CreateTemporaryFb)?; self.perform_screencopy( src, None, acquire_sync, ReleaseSync::None, src_cd, &(fb.clone() as Rc), AcquireSync::Unnecessary, ReleaseSync::None, transform, self.color_manager.srgb_gamma22(), position, true, x_off - capture.rect.x1(), y_off - capture.rect.y1(), size, transform, scale, ) .map_err(ShmScreencopyError::CopyToTemporary)?; let staging = ctx.create_staging_buffer(fb.staging_size(), STAGING_DOWNLOAD); let pending = fb .download( &staging, capture.clone(), mem.clone(), Region::new(capture.rect.at_point(0, 0)), ) .map_err(ShmScreencopyError::ReadPixels)?; Ok(pending) } pub fn create_seat(self: &Rc, name: &str) -> Rc { let global_name = self.globals.name(); let seat = WlSeatGlobal::new(global_name, name, self); self.globals.add_global(self, &seat); self.ei_clients.announce_seat(&seat); seat } pub fn set_backend_idle(&self, idle: bool) { if self.idle.backend_idle.replace(idle) != idle { self.root.update_visible(self); } } pub fn root_visible(&self) -> bool { !self.idle.backend_idle.get() } pub fn find_closest_output(&self, mut x: i32, mut y: i32) -> (Rc, i32, i32) { let mut optimal_dist = i128::MAX; let mut optimal_output = None; let outputs = self.root.outputs.lock(); for output in outputs.values() { let pos = output.global.pos.get(); let dist = pos.dist_squared(x, y); if dist == 0 { if pos.contains(x, y) { return (output.clone(), x, y); } } if dist < optimal_dist { optimal_dist = dist; optimal_output = Some(output.clone()); } } if let Some(output) = optimal_output { let pos = output.global.pos.get(); if pos.is_empty() { return (output, pos.x1(), pos.y1()); } if x < pos.x1() { x = pos.x1(); } else if x >= pos.x2() { x = pos.x2() - 1; } if y < pos.y1() { y = pos.y1(); } else if y >= pos.y2() { y = pos.y2() - 1; } return (output, x, y); } (self.dummy_output.get().unwrap(), 0, 0) } pub fn now(&self) -> Time { self.eng.now() } pub fn now_nsec(&self) -> u64 { self.eng.now().nsec() } pub fn now_usec(&self) -> u64 { self.eng.now().usec() } pub fn now_msec(&self) -> u64 { self.eng.now().msec() } pub fn queue_tiled_animation( self: &Rc, node_id: NodeId, old: Rect, new: Rect, retained: Option>, ) { if !self.animations.enabled.get() || !self.layout_animations_active.get() || self.suppress_animations_for_next_layout.get() { return; } let (old_output, old_scale) = { let (x, y) = old.center(); let (output, _, _) = self.find_closest_output(x, y); (output.id, output.global.persistent.scale.get()) }; let (new_output, new_scale) = { let (x, y) = new.center(); let (output, _, _) = self.find_closest_output(x, y); (output.id, output.global.persistent.scale.get()) }; if old_output != new_output || old_scale != new_scale { return; } let now = self.now_nsec(); let started = self.animations.set_target( node_id, old, new, retained, now, self.animations.duration_ms.get(), self.animations.curve.get(), ); if started { self.damage(expand_damage_rect( old.union(new), self.theme.sizes.border_width.get().max(0), )); self.ensure_animation_tick(); } } pub fn queue_spawn_in_animation( self: &Rc, node_id: NodeId, target: Rect, retained: Option>, ) { if !self.animations.enabled.get() || target.is_empty() { return; } let start = spawn_in_start_rect(target); let now = self.now_nsec(); let started = self.animations.set_spawn_in( node_id, target, retained, now, self.animations.duration_ms.get(), ); if started { self.damage(expand_damage_rect( start.union(target), self.theme.sizes.border_width.get().max(0), )); self.ensure_animation_tick(); } } pub fn set_animations_enabled(&self, enabled: bool) { if self.animations.enabled.replace(enabled) && !enabled { self.animations.clear(); self.damage(self.root.extents.get()); } } pub fn set_animation_duration_ms(&self, duration_ms: u32) { self.animations.duration_ms.set(duration_ms); } pub fn set_animation_curve(&self, curve: u32) { self.animations .curve .set(AnimationCurve::from_config(curve)); } pub fn with_layout_animations(&self, f: impl FnOnce() -> T) -> T { let prev_requested = self.layout_animations_requested.replace(true); let prev_active = self.layout_animations_active.replace(true); let res = f(); self.layout_animations_requested.set(prev_requested); self.layout_animations_active.set(prev_active); res } fn ensure_animation_tick(self: &Rc) { if self.animations.tick_is_active() { return; } let outputs: Vec<_> = self.root.outputs.lock().values().cloned().collect(); if outputs.is_empty() { return; } let tick = Rc::new_cyclic(|weak| AnimationTick::new(self, weak)); for output in &outputs { tick.attach(output); } self.animations.set_tick(tick); for output in &outputs { self.damage(output.global.pos.get()); } } pub fn output_extents_changed(&self) { self.root.update_extents(); for seat in self.globals.seats.lock().values() { seat.output_extents_changed(); } } pub fn update_ei_acceptor(self: &Rc) { self.update_ei_acceptor2(); if let Some(forker) = self.forker.get() { match self.ei_acceptor.get() { None => { forker.unsetenv(LIBEI_SOCKET.as_bytes()); } Some(s) => { forker.setenv(LIBEI_SOCKET.as_bytes(), s.socket_name().as_bytes()); } } } } fn update_ei_acceptor2(self: &Rc) { if self.ei_acceptor.is_some() == self.enable_ei_acceptor.get() { return; } if self.enable_ei_acceptor.get() { let (acceptor, future) = match EiAcceptor::spawn(self) { Ok(v) => v, Err(e) => { log::error!("Could not create libei socket: {}", ErrorFmt(e)); return; } }; self.ei_acceptor.set(Some(acceptor)); self.ei_acceptor_future.set(Some(future)); } else { log::info!("Disabling libei socket"); self.ei_acceptor.take(); self.ei_acceptor_future.take(); } } pub fn vblank(&self, connector: ConnectorId) { if let Some(output) = self.root.outputs.get(&connector) { output.vblank(); } } #[cfg(feature = "it")] pub fn latch(&self, connector: ConnectorId) { if let Some(output) = self.root.outputs.get(&connector) { output.latched(false); } } #[cfg(feature = "it")] pub fn latch_tearing(&self, connector: ConnectorId) { if let Some(output) = self.root.outputs.get(&connector) { output.latched(true); } } #[cfg(feature = "it")] pub async fn idle(&self) { loop { self.eng.idle().await; if self.cpu_worker.wait_idle() { break; } } } pub fn ui_drag_threshold_reached(&self, (x1, y1): (i32, i32), (x2, y2): (i32, i32)) -> bool { if !self.ui_drag_enabled.get() { return false; } let dx = x1 - x2; let dy = y1 - y2; dx * dx + dy * dy > self.ui_drag_threshold_squared.get() } pub fn update_xwayland_wire_scale(&self) { let scale = self .scales .lock() .iter() .map(|v| v.0.round_up()) .max() .unwrap_or(1); let wire_scale = match self.xwayland.use_wire_scale.get() { true => Some(scale as i32), false => None, }; self.xwayland.wire_scale.set(wire_scale); for client in self.clients.clients.borrow().values() { let client = &client.data; if !client.is_xwayland { continue; } if client.wire_scale.replace(wire_scale) == wire_scale { continue; } for output in client.objects.outputs.lock().values() { output.send_updates(); } for surface in client.objects.surfaces.lock().values() { surface.handle_xwayland_wire_scale_change(); } } } pub fn tray_icon_size(&self) -> i32 { if !self.show_bar.get() { return 0; } (self.theme.sizes.bar_height() - 2).max(0) } pub fn color_management_available(&self) -> bool { if !self.color_management_enabled.get() { return false; } let Some(ctx) = self.render_ctx.get() else { return false; }; ctx.supports_color_management() } pub fn initial_tile_state(&self, data: &ToplevelData) -> Option { self.config.get()?.initial_tile_state(data) } pub fn predict_tiled_body_size(&self) -> Option<(i32, i32)> { let seat = self.seat_queue.last(); let ws = self.ensure_map_workspace(seat.as_deref()); let pos = ws.position.get(); if pos.is_empty() { return None; } if let Some(c) = ws.container.get() { let la = c.clone().tl_last_active_child(); let target = la .tl_data() .parent .get() .and_then(|n| n.node_into_container()) .unwrap_or(c); Some(target.predict_child_body_size()) } else { Some((pos.width(), pos.height())) } } pub fn update_capabilities( &self, data: &Rc, bounding_caps: ClientCaps, set_bounding_caps: bool, ) { if let Some(config) = self.config.get() { config.update_capabilities(data, bounding_caps, set_bounding_caps); } } pub fn find_output_in_direction( &self, source_output: &OutputNode, direction: Direction, ) -> Option> { if source_output.is_dummy { return None; } let outputs = self.root.outputs.lock(); let ref_box = source_output.global.pos.get(); let ref_x1 = ref_box.x1(); let ref_y1 = ref_box.y1(); let ref_x2 = ref_box.x2(); let ref_y2 = ref_box.y2(); // Use the center of the source output as the reference point (like wlroots) let (ref_lx, ref_ly) = ref_box.center(); // Find the closest output in the given direction using wlroots-style algorithm let mut min_distance = i64::MAX; let mut closest_output = None; for output in outputs.values() { if output.id == source_output.id { continue; } let box_pos = output.global.pos.get(); let box_x1 = box_pos.x1(); let box_y1 = box_pos.y1(); let box_x2 = box_pos.x2(); let box_y2 = box_pos.y2(); // Edge-based direction check (like wlroots) // Test to make sure this output is in the given direction let is_in_direction = match direction { Direction::Left => box_x2 <= ref_x1, Direction::Right => box_x1 >= ref_x2, Direction::Up => box_y2 <= ref_y1, Direction::Down => box_y1 >= ref_y2, Direction::Unspecified => false, }; if !is_in_direction { continue; } // Calculate distance from reference point to closest point on this output // This mimics wlr_box_closest_point + squared Euclidean distance let closest_x = ref_lx.clamp(box_x1, box_x2); let closest_y = ref_ly.clamp(box_y1, box_y2); let dx = (closest_x - ref_lx) as i64; let dy = (closest_y - ref_ly) as i64; let distance = dx * dx + dy * dy; if distance < min_distance { min_distance = distance; closest_output = Some(output); } } closest_output.cloned() } pub fn node_at(&self, x: i32, y: i32) -> FoundNode { let mut found_tree = self.node_at_tree.borrow_mut(); found_tree.push(FoundNode { node: self.root.clone(), x, y, }); self.root .node_find_tree_at(x, y, &mut found_tree, FindTreeUsecase::None); let node = found_tree.pop().unwrap(); found_tree.clear(); node } pub fn move_ws_to_output(&self, ws: &WorkspaceNode, output: &Rc) { if ws.is_dummy || output.is_dummy { return; } if ws.output.get().id == output.id { return; } let link = match &*ws.output_link.borrow() { None => return, Some(l) => l.to_ref(), }; let config = WsMoveConfig { make_visible_always: false, make_visible_if_empty: true, source_is_destroyed: false, before: None, }; move_ws_to_output(&link, &output, config); ws.desired_output.set(output.global.output_id.clone()); self.tree_changed(); } fn expose_new_singletons(&self) { self.globals.expose_new_singletons(self); } pub fn set_color_management_enabled(&self, enabled: bool) { self.color_management_enabled.set(enabled); self.expose_new_singletons(); } pub fn set_primary_selection_enabled(&self, enabled: bool) { self.enable_primary_selection.set(enabled); self.expose_new_singletons(); } pub fn set_explicit_sync_enabled(&self, enabled: bool) { self.explicit_sync_enabled.set(enabled); self.expose_new_singletons(); } pub fn set_log_level(&self, level: LogLevel) { if let Some(logger) = &self.logger { logger.set_level(level); } } pub fn perform_clean_logs_older_than(&self) { if let Some(time) = self.clean_logs_older_than.get() && let Some(logger) = &self.logger { logger.clean_logs_older_than(time); } } fn colors_changed(&self) { struct V; impl NodeVisitorBase for V { fn visit_container(&mut self, node: &Rc) { node.on_colors_changed(); node.node_visit_children(self); } fn visit_output(&mut self, node: &Rc) { node.on_colors_changed(); node.node_visit_children(self); } fn visit_float(&mut self, node: &Rc) { node.on_colors_changed(); node.node_visit_children(self); } } self.root.clone().node_visit(&mut V); self.damage(self.root.extents.get()); self.icons.clear(); } pub fn reset_colors(&self) { self.theme.colors.reset(); self.colors_changed(); } pub fn quit(&self) { log::info!("Quitting"); self.ring.stop(); } pub fn reload_config(self: &Rc) { log::info!("Reloading config"); let config = match ConfigProxy::from_config_dir(self) { Ok(c) => c, Err(e) => { log::error!("Cannot reload config: {}", ErrorFmt(e)); return; } }; if let Some(config) = self.config.take() { config.destroy(); for seat in self.globals.seats.lock().values() { seat.clear_shortcuts(); } } config.configure(true); self.config.set(Some(Rc::new(config))); } pub fn set_ei_socket_enabled(self: &Rc, enabled: bool) { self.enable_ei_acceptor.set(enabled); self.update_ei_acceptor(); } pub fn set_workspace_display_order(&self, order: WorkspaceDisplayOrder) { self.workspace_display_order.set(order); for output in self.root.outputs.lock().values() { output.handle_workspace_display_order_update(); } } fn spaces_changed(&self) { struct V; impl NodeVisitorBase for V { fn visit_container(&mut self, node: &Rc) { node.on_spaces_changed(); node.node_visit_children(self); } fn visit_output(&mut self, node: &Rc) { node.on_spaces_changed(); node.node_visit_children(self); } fn visit_float(&mut self, node: &Rc) { node.on_spaces_changed(); node.node_visit_children(self); } } self.root.clone().node_visit(&mut V); self.damage(self.root.extents.get()); self.icons.update_sizes(self); } pub fn set_show_bar(&self, show: bool) { self.show_bar.set(show); self.spaces_changed(); } #[allow(dead_code)] pub fn set_show_titles(&self, show: bool) { self.theme.show_titles.set(show); self.spaces_changed(); } #[allow(dead_code)] pub fn set_floating_titles(&self, floating: bool) { self.theme.floating_titles.set(floating); self.spaces_changed(); } pub fn set_ui_drag_enabled(&self, enabled: bool) { self.ui_drag_enabled.set(enabled); } pub fn set_ui_drag_threshold(&self, threshold: i32) { self.ui_drag_threshold_squared .set(threshold.saturating_mul(threshold)); } #[allow(dead_code)] pub fn set_show_pin_icon(&self, show: bool) { self.show_pin_icon.set(show); } pub fn set_float_above_fullscreen(&self, v: bool) { self.float_above_fullscreen.set(v); for seat in self.globals.seats.lock().values() { seat.emulate_cursor_moved(); seat.trigger_tree_changed(false); } self.root.update_visible(self); } pub fn reset_sizes(&self) { self.theme.sizes.reset(); self.spaces_changed(); } fn fonts_changed(&self) { struct V; impl NodeVisitorBase for V { fn visit_container(&mut self, node: &Rc) { node.node_visit_children(self); } fn visit_output(&mut self, node: &Rc) { node.schedule_update_render_data(); node.node_visit_children(self); } fn visit_float(&mut self, node: &Rc) { node.node_visit_children(self); } } self.root.clone().node_visit(&mut V); } pub fn reset_fonts(&self) { let theme = &self.theme; theme.font.set(self.theme.default_font.clone()); theme.bar_font.set(None); theme.title_font.set(None); self.fonts_changed(); } pub fn set_font(&self, font: &str) { self.theme.font.set(Arc::new(font.to_string())); self.fonts_changed(); } pub fn set_bar_font(&self, font: Option<&str>) { let font = font.map(|font| Arc::new(font.to_string())); self.theme.bar_font.set(font); self.fonts_changed(); } #[allow(dead_code)] pub fn set_title_font(&self, font: Option<&str>) { let font = font.map(|font| Arc::new(font.to_string())); self.theme.title_font.set(font); self.fonts_changed(); } pub fn set_bar_position(&self, p: BarPosition) { self.theme.bar_position.set(p); self.spaces_changed(); } pub fn set_size(&self, sized: ThemeSized, size: i32) { let field = sized.field(&self.theme); field.val.set(size); field.set.set(true); self.spaces_changed(); } pub fn set_color(&self, colored: ThemeColor, v: Color) { colored.field(&self.theme).set(v); self.colors_changed(); } pub fn ensure_persistent_output_state( &self, output_id: &Rc, ) -> Rc { match self.persistent_output_states.get(output_id) { Some(ds) => ds, _ => { let ds = self.new_persistent_output_state(); self.persistent_output_states .set(output_id.clone(), ds.clone()); ds } } } pub fn new_persistent_output_state(&self) -> Rc { let x1 = self .root .outputs .lock() .values() .map(|o| o.global.pos.get().x2()) .max() .unwrap_or(0); Rc::new(PersistentOutputState { transform: Default::default(), scale: Default::default(), pos: Cell::new((x1, 0)), vrr_mode: Cell::new(self.default_vrr_mode.get()), vrr_cursor_hz: Cell::new(self.default_vrr_cursor_hz.get()), tearing_mode: Cell::new(self.default_tearing_mode.get()), brightness: Cell::new(None), blend_space: Cell::new(BlendSpace::Srgb), use_native_gamut: Cell::new(false), }) } } #[derive(Debug, Error)] pub enum ShmScreencopyError { #[error("There is no render context")] NoRenderContext, #[error("Could not create a bridge framebuffer")] CreateTemporaryFb(#[source] GfxError), #[error("Could not copy texture to bridge framebuffer")] CopyToTemporary(#[source] GfxError), #[error("Could not read pixels from texture")] ReadPixels(#[source] GfxError), }