use {
    crate::{
        acceptor::Acceptor,
        allocator::BufferObject,
        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, NodeIds, NodeVisitorBase, OutputNode, PlaceholderNode,
            TearingMode, TileState, ToplevelData, ToplevelIdentifier, ToplevelNode,
            ToplevelNodeBase, Transform, VrrMode, WorkspaceDisplayOrder, WorkspaceNode,
            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<Rc<dyn Backend>>,
    pub forker: CloneCell<Option<Rc<ForkerProxy>>>,
    pub default_keymap: Rc<KbvmMap>,
    pub eng: Rc<AsyncEngine>,
    pub render_ctx: CloneCell<Option<Rc<dyn GfxContext>>>,
    pub drm_feedback: CloneCell<Option<Rc<DrmFeedback>>>,
    pub drm_feedback_consumers:
        CopyHashMap<(ClientId, ZwpLinuxDmabufFeedbackV1Id), Rc<ZwpLinuxDmabufFeedbackV1>>,
    pub render_ctx_version: NumCell<u32>,
    pub render_ctx_ever_initialized: Cell<bool>,
    pub cursors: CloneCell<Option<Rc<ServerCursors>>>,
    pub wheel: Rc<Wheel>,
    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<DisplayNode>,
    pub workspaces: CopyHashMap<String, Rc<WorkspaceNode>>,
    pub dummy_output: CloneCell<Option<Rc<OutputNode>>>,
    pub backend_events: AsyncQueue<BackendEvent>,
    pub input_device_handlers: RefCell<AHashMap<InputDeviceId, InputDeviceData>>,
    pub seat_queue: LinkedList<Rc<WlSeatGlobal>>,
    pub slow_clients: AsyncQueue<Rc<Client>>,
    pub none_surface_ext: Rc<NoneSurfaceExt>,
    pub tree_changed_sent: Cell<bool>,
    pub config: CloneCell<Option<Rc<ConfigProxy>>>,
    pub theme: Theme,
    pub pending_container_layout: AsyncQueue<Rc<ContainerNode>>,
    pub pending_container_render_positions: AsyncQueue<Rc<ContainerNode>>,
    pub pending_output_render_data: AsyncQueue<Rc<OutputNode>>,
    pub pending_float_layout: AsyncQueue<Rc<FloatNode>>,
    pub pending_input_popup_positioning: AsyncQueue<Rc<ZwpInputPopupSurfaceV2>>,
    pub pending_toplevel_screencasts: AsyncQueue<Rc<JayScreencast>>,
    pub pending_screencast_reallocs_or_reconfigures: AsyncQueue<Rc<JayScreencast>>,
    pub pending_placeholder_render_textures: AsyncQueue<Rc<PlaceholderNode>>,
    pub pending_container_tab_render_textures: AsyncQueue<Rc<ContainerNode>>,
    pub dbus: Dbus,
    pub fdcloser: Arc<FdCloser>,
    pub logger: Option<Arc<Logger>>,
    pub connectors: CopyHashMap<ConnectorId, Rc<ConnectorData>>,
    pub outputs: CopyHashMap<ConnectorId, Rc<OutputData>>,
    pub wlr_output_managers: WlrOutputManagerState,
    pub drm_devs: CopyHashMap<DrmDeviceId, Rc<DrmDevData>>,
    pub status: CloneCell<Rc<String>>,
    pub idle: IdleState,
    pub run_args: RunArgs,
    pub xwayland: XWaylandState,
    pub acceptor: CloneCell<Option<Rc<Acceptor>>>,
    pub serial: NumCell<u64>,
    pub run_toplevel: Rc<RunToplevel>,
    pub config_dir: Option<String>,
    pub config_file_id: NumCell<u64>,
    pub tracker: Tracker<Self>,
    pub data_offer_ids: DataOfferIds,
    pub data_source_ids: DataSourceIds,
    pub ring: Rc<IoUring>,
    pub lock: ScreenlockState,
    pub scales: RefCounted<Scale>,
    pub cursor_sizes: RefCounted<u32>,
    pub hardware_tick_cursor: AsyncQueue<Option<Rc<dyn Cursor>>>,
    pub testers: RefCell<AHashMap<(ClientId, JaySeatEventsId), Rc<JaySeatEvents>>>,
    pub render_ctx_watchers: CopyHashMap<(ClientId, JayRenderCtxId), Rc<JayRenderCtx>>,
    pub workspace_watchers: CopyHashMap<(ClientId, JayWorkspaceWatcherId), Rc<JayWorkspaceWatcher>>,
    pub default_workspace_capture: Cell<bool>,
    pub default_gfx_api: Cell<GfxApi>,
    pub activation_tokens: CopyHashMap<ActivationToken, ()>,
    pub toplevel_lists:
        CopyHashMap<(ClientId, ExtForeignToplevelListV1Id), Rc<ExtForeignToplevelListV1>>,
    pub dma_buf_ids: Rc<DmaBufIds>,
    pub drm_feedback_ids: DrmFeedbackIds,
    pub direct_scanout_enabled: Cell<bool>,
    pub persistent_output_states: CopyHashMap<Rc<OutputId>, Rc<PersistentOutputState>>,
    pub double_click_interval_usec: Cell<u64>,
    pub double_click_distance: Cell<i32>,
    pub create_default_seat: Cell<bool>,
    pub subsurface_ids: SubsurfaceIds,
    pub wait_for_syncobj: Rc<WaitForSyncobj>,
    pub explicit_sync_enabled: Cell<bool>,
    pub explicit_sync_supported: Cell<bool>,
    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<CursorUserGroupId, Rc<CursorUserGroup>>,
    pub cursor_user_group_hardware_cursor: CloneCell<Option<Rc<CursorUserGroup>>>,
    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<VrrMode>,
    pub default_vrr_cursor_hz: Cell<Option<f64>>,
    pub default_tearing_mode: Cell<TearingMode>,
    pub ei_acceptor: CloneCell<Option<Rc<EiAcceptor>>>,
    pub ei_acceptor_future: CloneCell<Option<SpawnedFuture<()>>>,
    pub enable_ei_acceptor: Cell<bool>,
    pub ei_clients: EiClients,
    pub slow_ei_clients: AsyncQueue<Rc<EiClient>>,
    pub cpu_worker: Rc<CpuWorker>,
    pub ui_drag_enabled: Cell<bool>,
    pub ui_drag_threshold_squared: Cell<i32>,
    pub toplevels: CopyHashMap<ToplevelIdentifier, Weak<dyn ToplevelNode>>,
    pub const_40hz_latch: EventSource<dyn LatchListener>,
    pub tray_item_ids: TrayItemIds,
    pub data_control_device_ids: DataControlDeviceIds,
    pub workspace_managers: WorkspaceManagerState,
    pub toplevel_managers:
        CopyHashMap<(ClientId, ZwlrForeignToplevelManagerV1Id), Rc<ZwlrForeignToplevelManagerV1>>,
    pub color_management_enabled: Cell<bool>,
    pub color_manager: Rc<ColorManager>,
    pub float_above_fullscreen: Cell<bool>,
    pub icons: Icons,
    #[allow(dead_code)]
    pub show_pin_icon: Cell<bool>,
    pub cl_matcher_manager: ClMatcherManager,
    pub tl_matcher_manager: TlMatcherManager,
    pub caps_thread: Option<PrCapsThread>,
    pub node_at_tree: RefCell<Vec<FoundNode>>,
    pub position_hint_requests: AsyncQueue<PositionHintRequest>,
    pub pending_warp_mouse_to_focus: AsyncQueue<Rc<WlSeatGlobal>>,
    pub backend_connector_state_serials: BackendConnectorStateSerials,
    pub head_names: HeadNames,
    pub head_managers:
        CopyHashMap<(ClientId, JayHeadManagerSessionV1Id), Rc<JayHeadManagerSessionV1>>,
    pub head_managers_async: AsyncQueue<HeadManagerEvent>,
    pub show_bar: Cell<bool>,
    pub enable_primary_selection: Cell<bool>,
    pub xdg_surface_configure_events: AsyncQueue<XdgSurfaceConfigureEvent>,
    pub workspace_display_order: Cell<WorkspaceDisplayOrder>,
    pub outputs_without_hc: NumCell<usize>,
    pub udmabuf: Rc<UdmabufHolder>,
    pub gfx_ctx_changed: EventSource<WlBuffer>,
    pub copy_device_registry: Rc<CopyDeviceRegistry>,
    pub supports_presentation_feedback: Cell<bool>,
    pub eventfd_cache: Rc<EventfdCache>,
    pub lazy_event_sources: Rc<LazyEventSources>,
    pub post_layout_event_sources: Rc<LazyEventSources>,
    pub bo_drop_queue: Rc<ObjectDropQueue<Rc<dyn BufferObject>>>,
    pub virtual_outputs: VirtualOutputs,
    pub clean_logs_older_than: Cell<Option<SystemTime>>,
}

// 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<bool>,
    pub lock: CloneCell<Option<Rc<ExtSessionLockV1>>>,
}

pub struct XWaylandState {
    pub enabled: Cell<bool>,
    pub running: Cell<bool>,
    pub pidfd: CloneCell<Option<Rc<OwnedFd>>>,
    pub handler: RefCell<Option<SpawnedFuture<()>>>,
    pub queue: Rc<AsyncQueue<XWaylandEvent>>,
    pub ipc_device_ids: XIpcDeviceIds,
    pub use_wire_scale: Cell<bool>,
    pub wire_scale: Cell<Option<i32>>,
    pub windows: CopyHashMap<XwindowId, Rc<Xwindow>>,
    pub client: CloneCell<Option<Rc<Client>>>,
    pub display: CloneCell<Option<Rc<String>>>,
}

pub struct IdleState {
    pub input: Cell<bool>,
    pub change: AsyncEvent,
    pub timeout: Cell<Duration>,
    pub grace_period: Cell<Duration>,
    pub timeout_changed: Cell<bool>,
    pub inhibitors: CopyHashMap<IdleInhibitorId, Rc<ZwpIdleInhibitorV1>>,
    pub inhibitors_changed: Cell<bool>,
    pub backend_idle: Cell<bool>,
    pub inhibited_idle_notifications:
        CopyHashMap<(ClientId, ExtIdleNotificationV1Id), Rc<ExtIdleNotificationV1>>,
    pub in_grace_period: Cell<bool>,
}

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<ZwpIdleInhibitorV1>) {
        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<ExtIdleNotificationV1>) {
        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<DeviceHandlerData>,
    pub async_event: Rc<AsyncEvent>,
}

pub struct DeviceHandlerData {
    pub keyboard_id: PhysicalKeyboardId,
    pub seat: CloneCell<Option<Rc<WlSeatGlobal>>>,
    pub px_per_scroll_wheel: Cell<f64>,
    pub device: Rc<dyn InputDevice>,
    pub syspath: Option<String>,
    pub devnode: Option<String>,
    pub keymap: CloneCell<Option<Rc<KbvmMap>>>,
    pub output: CloneCell<Option<Rc<OutputGlobalOpt>>>,
    pub tablet_init: Option<Box<TabletInit>>,
    pub tablet_pad_init: Option<Box<TabletPadInit>>,
    pub is_touch: bool,
    pub is_kb: bool,
    pub mods_listener: EventListener<dyn LedsListener>,
}

pub struct ConnectorData {
    pub id: ConnectorId,
    pub connector: Rc<dyn Connector>,
    pub handler: Cell<Option<SpawnedFuture<()>>>,
    pub connected: Cell<bool>,
    pub name: Rc<String>,
    pub description: RefCell<String>,
    pub drm_dev: Option<Rc<DrmDevData>>,
    pub async_event: Rc<AsyncEvent>,
    pub damaged: Cell<bool>,
    pub damage: RefCell<Vec<Rect>>,
    pub needs_vblank_emulation: Cell<bool>,
    pub damage_intersect: Cell<Rect>,
    pub state: RefCell<BackendConnectorState>,
    pub head_managers: HeadManagers,
    pub wlr_output_heads: CopyHashMap<WlrOutputManagerId, Rc<ZwlrOutputHeadV1>>,
}

pub struct OutputData {
    pub connector: Rc<ConnectorData>,
    pub monitor_info: Rc<MonitorInfo>,
    pub node: Option<Rc<OutputNode>>,
    pub lease_connectors: Rc<Bindings<WpDrmLeaseConnectorV1>>,
}

pub struct DrmDevData {
    pub dev: Rc<dyn BackendDrmDevice>,
    pub handler: Cell<Option<SpawnedFuture<()>>>,
    pub connectors: CopyHashMap<ConnectorId, Rc<ConnectorData>>,
    pub syspath: Option<String>,
    pub devnode: Option<String>,
    pub vendor: Option<String>,
    pub model: Option<String>,
    pub pci_id: Option<PciId>,
    pub lease_global: Rc<WpDrmLeaseDeviceV1Global>,
}

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<ContainerNode>) {
        node.node_visit_children(self);
    }
    fn visit_output(&mut self, node: &Rc<OutputNode>) {
        node.schedule_update_render_data();
        node.node_visit_children(self);
    }
    fn visit_float(&mut self, node: &Rc<FloatNode>) {
        node.node_visit_children(self);
    }
    fn visit_workspace(&mut self, node: &Rc<WorkspaceNode>) {
        node.title_texture.take();
        node.node_visit_children(self);
    }
    fn visit_placeholder(&mut self, node: &Rc<PlaceholderNode>) {
        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<GfxApi>,
    ) -> Result<Rc<dyn GfxContext>, 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<Rc<dyn GfxContext>>) {
        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<ContainerNode>) {
                    node.node_visit_children(self);
                }
                fn visit_workspace(&mut self, node: &Rc<WorkspaceNode>) {
                    node.title_texture.take();
                    node.node_visit_children(self);
                }
                fn visit_output(&mut self, node: &Rc<OutputNode>) {
                    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<FloatNode>) {
                    node.node_visit_children(self);
                }
                fn visit_placeholder(&mut self, node: &Rc<PlaceholderNode>) {
                    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<T: WaylandGlobal>(&self, global: &Rc<T>) {
        self.globals.add_global(self, global)
    }

    pub fn remove_global<T: RemovableWaylandGlobal>(
        &self,
        global: &Rc<T>,
    ) -> 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<WlSeatGlobal>>) -> Rc<WorkspaceNode> {
        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<Self>, node: Rc<dyn ToplevelNode>) {
        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<Self>, seat: Option<&Rc<WlSeatGlobal>>, node: Rc<dyn ToplevelNode>) {
        let ws = self.ensure_map_workspace(seat);
        self.map_tiled_on(node, &ws);
    }

    pub fn map_tiled_on(self: &Rc<Self>, node: Rc<dyn ToplevelNode>, ws: &Rc<WorkspaceNode>) {
        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<Self>,
        node: Rc<dyn ToplevelNode>,
        mut width: i32,
        mut height: i32,
        workspace: &Rc<WorkspaceNode>,
        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<dyn ToplevelNode>, seat: Option<&Rc<WlSeatGlobal>>) {
        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<WlSeatGlobal>>,
        output: &Rc<OutputNode>,
        ws: &Rc<WorkspaceNode>,
    ) {
        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<WlSeatGlobal>,
        name: &str,
        output: Option<Rc<OutputNode>>,
    ) {
        let ws = match self.workspaces.get(name) {
            Some(ws) => ws,
            _ => {
                let output = output.unwrap_or_else(|| seat.get_fallback_output());
                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<WorkspaceNode> {
        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<Self>) {
        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<Self>, 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.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<OutputNode>,
        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<F: Fn(&JaySeatEvents)>(&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<dyn GfxFramebuffer>,
        cd: &Rc<ColorDescription>,
        acquire_sync: AcquireSync,
        release_sync: ReleaseSync,
        tex: &Rc<dyn GfxTexture>,
        render_hw_cursor: bool,
        blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
        blend_cd: &Rc<ColorDescription>,
    ) -> Result<Option<FdSync>, 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<dyn GfxTexture>,
        resv: Option<&Rc<dyn BufferResv>>,
        acquire_sync: &AcquireSync,
        release_sync: ReleaseSync,
        src_cd: &Rc<ColorDescription>,
        target: &Rc<dyn GfxFramebuffer>,
        target_acquire_sync: AcquireSync,
        target_release_sync: ReleaseSync,
        target_transform: Transform,
        target_cd: &Rc<ColorDescription>,
        position: Rect,
        render_hardware_cursors: bool,
        x_off: i32,
        y_off: i32,
        size: Option<(i32, i32)>,
        transform: Transform,
        scale: Scale,
    ) -> Result<Option<FdSync>, 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<dyn GfxTexture>,
        src_cd: &Rc<ColorDescription>,
        acquire_sync: &AcquireSync,
        position: Rect,
        x_off: i32,
        y_off: i32,
        size: Option<(i32, i32)>,
        capture: &Rc<ZwlrScreencopyFrameV1>,
        mem: &Rc<ClientMemOffset>,
        stride: i32,
        format: &'static Format,
        transform: Transform,
        scale: Scale,
    ) -> Result<Option<PendingShmTransfer>, 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<dyn GfxFramebuffer>),
            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<Self>, name: &str) -> Rc<WlSeatGlobal> {
        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<OutputNode>, 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>) {
        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<Self>) {
        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<TileState> {
        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<Client>,
        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<Rc<OutputNode>> {
        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<OutputNode>) {
        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<ContainerNode>) {
                node.on_colors_changed();
                node.node_visit_children(self);
            }

            fn visit_output(&mut self, node: &Rc<OutputNode>) {
                node.on_colors_changed();
                node.node_visit_children(self);
            }

            fn visit_float(&mut self, node: &Rc<FloatNode>) {
                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<Self>) {
        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<Self>, 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<ContainerNode>) {
                node.on_spaces_changed();
                node.node_visit_children(self);
            }
            fn visit_output(&mut self, node: &Rc<OutputNode>) {
                node.on_spaces_changed();
                node.node_visit_children(self);
            }
            fn visit_float(&mut self, node: &Rc<FloatNode>) {
                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<ContainerNode>) {
                node.node_visit_children(self);
            }
            fn visit_output(&mut self, node: &Rc<OutputNode>) {
                node.schedule_update_render_data();
                node.node_visit_children(self);
            }
            fn visit_float(&mut self, node: &Rc<FloatNode>) {
                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<OutputId>,
    ) -> Rc<PersistentOutputState> {
        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<PersistentOutputState> {
        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),
}
