1503 lines
51 KiB
Rust
1503 lines
51 KiB
Rust
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<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<WorkspaceNodeId, 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_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 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 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 animations: AnimationState,
|
|
pub layout_animations_requested: Cell<bool>,
|
|
pub layout_animations_active: Cell<bool>,
|
|
pub layout_animation_curve_override: Cell<Option<AnimationCurve>>,
|
|
pub layout_animation_style_override: Cell<Option<AnimationStyle>>,
|
|
pub(crate) layout_animation_batch: RefCell<Option<Vec<LayoutAnimationCandidate>>>,
|
|
pub suppress_animations_for_next_layout: Cell<bool>,
|
|
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 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>,
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
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<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();
|
|
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<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)>,
|
|
) -> Rc<FloatNode> {
|
|
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<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 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<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 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<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 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 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 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 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 = 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<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),
|
|
})
|
|
}
|
|
}
|