mod animations; mod connectors; mod idle; mod rendering; mod xwayland; pub(crate) use animations::LayoutAnimationCandidate; pub use connectors::{ConnectorData, DrmDevData, OutputData}; pub use idle::IdleState; pub use xwayland::XWaylandState; use { crate::{ acceptor::Acceptor, allocator::BufferObject, animation::{AnimationCurve, AnimationState, AnimationStyle}, async_engine::{AsyncEngine, SpawnedFuture}, backend::{ Backend, BackendConnectorStateSerials, BackendEvent, ConnectorId, ConnectorIds, DrmDeviceId, DrmDeviceIds, HardwareCursorUpdate, InputDevice, InputDeviceGroupIds, InputDeviceId, InputDeviceIds, TabletIds, TabletInit, TabletPadIds, TabletPadInit, TabletToolIds, }, backends::dummy::DummyBackend, cli::RunArgs, client::{Client, ClientId, Clients, NUM_CACHED_SERIAL_RANGES, SerialRange}, cmm::cmm_manager::ColorManager, compositor::LIBEI_SOCKET, 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, forker::ForkerProxy, gfx_api::{GfxApi, GfxContext, GfxError}, gfx_apis::create_gfx_context, globals::{Globals, GlobalsError, RemovableWaylandGlobal, WaylandGlobal}, icons::Icons, ifs::{ ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, ext_session_lock_v1::ExtSessionLockV1, head_management::{ HeadNames, jay_head_manager_session_v1::{HeadManagerEvent, JayHeadManagerSessionV1}, }, data_transfer::{ DataOfferIds, DataSourceIds, data_control::DataControlDeviceIds, }, jay_render_ctx::JayRenderCtx, jay_seat_events::JaySeatEvents, jay_workspace_watcher::JayWorkspaceWatcher, wl_buffer::WlBuffer, wl_output::{BlendSpace, OutputGlobalOpt, OutputId, PersistentOutputState}, wl_seat::{ PhysicalKeyboardId, PhysicalKeyboardIds, PositionHintRequest, SeatIds, WlSeatGlobal, }, wl_surface::{ NoneSurfaceExt, tray::TrayItemIds, wl_subsurface::SubsurfaceIds, xdg_surface::XdgSurfaceConfigureEvent, zwp_idle_inhibitor_v1::IdleInhibitorIds, zwp_input_popup_surface_v2::ZwpInputPopupSurfaceV2, }, wlr_output_manager::WlrOutputManagerState, workspace_manager::WorkspaceManagerState, xdg_activation_token_v1::ActivationToken, zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1, zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, }, io_uring::IoUring, kbvm::{KbvmContext, KbvmMap}, logger::LogLevel, keyboard::{KeyboardStateIds, LedsListener}, leaks::Tracker, logger::Logger, pr_caps::PrCapsThread, rect::Rect, scale::Scale, theme::{BarPosition, Color, Theme, ThemeColor, ThemeSized}, time::Time, tree::{ ContainerNode, ContainerSplit, Direction, DisplayNode, FindTreeUsecase, FloatNode, FoundNode, LatchListener, Node, NodeIds, NodeVisitorBase, OutputNode, PlaceholderNode, TearingMode, TileState, ToplevelData, ToplevelIdentifier, ToplevelNode, ToplevelNodeBase, VrrMode, WorkspaceDisplayOrder, WorkspaceNode, WorkspaceNodeId, WsMoveConfig, generic_node_visitor, move_ws_to_output, }, udmabuf::UdmabufHolder, utils::{ asyncevent::AsyncEvent, 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, JayHeadManagerSessionV1Id, JayRenderCtxId, JaySeatEventsId, JayWorkspaceWatcherId, ZwlrForeignToplevelManagerV1Id, ZwpLinuxDmabufFeedbackV1Id, }, }, ahash::AHashMap, bstr::ByteSlice, std::{ cell::{Cell, RefCell}, fmt::{Debug, Formatter}, mem, ops::{Deref, DerefMut}, rc::{Rc, Weak}, sync::Arc, time::SystemTime, }, uapi::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_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 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 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 layout_animation_curve_override: Cell>, pub layout_animation_style_override: Cell>, pub(crate) layout_animation_batch: RefCell>>, 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 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, } 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()); } for client in self.clients.clients.borrow_mut().values() { for sc in client.data.objects.ext_copy_sessions.lock().values() { sc.stop(); } } 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(); let animate_new_app_map = node.tl_data().parent.is_none() && node.tl_data().kind.is_app_window() && !node.tl_data().visible.get(); if animate_new_app_map { self.with_layout_animations(|| self.do_map_tiled(seat.as_deref(), node.clone())); } else { 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)>, ) -> Rc { 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) }; let float = FloatNode::new(self, workspace, position, node.clone()); self.focus_after_map(node, self.seat_queue.last().as_deref()); float } 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 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_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.layout_animation_curve_override.set(None); self.layout_animation_style_override.set(None); self.suppress_animations_for_next_layout.set(false); self.render_ctx_watchers.clear(); self.workspace_watchers.clear(); self.toplevel_lists.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 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 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 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 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 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 = ConfigProxy::default(self); 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), }) } }