1
0
Fork 0
forked from wry/wry
wry/src/state.rs

2886 lines
96 KiB
Rust

use {
crate::{
acceptor::Acceptor,
allocator::BufferObject,
animation::{
AnimationCurve, AnimationState, AnimationStyle, AnimationTick, RetainedExitLayer,
RetainedToplevel,
expand_damage_rect,
multiphase::{
MultiphasePhase, MultiphasePlan, MultiphasePlanFailure, MultiphaseRequest,
MultiphaseWindow, MultiphaseWindowHierarchy,
partition_motion_groups, plan_no_overlap_with_diagnostics, validate_phase_paths,
},
spawn_in_start_rect,
},
async_engine::{AsyncEngine, SpawnedFuture},
backend::{
Backend, BackendConnectorState, BackendConnectorStateSerials, BackendDrmDevice,
BackendEvent, Connector, ConnectorId, ConnectorIds, DrmDeviceId, DrmDeviceIds,
HardwareCursorUpdate, InputDevice, InputDeviceGroupIds, InputDeviceId, InputDeviceIds,
MonitorInfo, TabletIds, TabletInit, TabletPadIds, TabletPadInit, TabletToolIds,
transaction::{BackendConnectorTransactionError, ConnectorTransaction},
},
backends::dummy::DummyBackend,
cli::RunArgs,
client::{Client, ClientId, Clients, NUM_CACHED_SERIAL_RANGES, SerialRange},
clientmem::ClientMemOffset,
cmm::{
cmm_description::ColorDescription, cmm_manager::ColorManager,
cmm_render_intent::RenderIntent,
},
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,
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},
},
data_transfer::{
DataOfferIds, DataSourceIds, data_control::DataControlDeviceIds,
x_data_device::XTransferDeviceIds,
},
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,
},
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},
logger::LogLevel,
keyboard::{KeyboardStateIds, LedsListener},
leaks::Tracker,
logger::Logger,
pr_caps::PrCapsThread,
rect::{Rect, Region},
renderer::Renderer,
scale::Scale,
security_context_acceptor::SecurityContextAcceptors,
theme::{BarPosition, Color, Theme, ThemeColor, ThemeSized},
time::Time,
tree::{
ContainerNode, ContainerSplit, Direction, DisplayNode, FindTreeUsecase, FloatNode,
FoundNode, LatchListener, Node, NodeId, NodeIds, NodeVisitorBase, OutputNode,
PlaceholderNode, TearingMode, TileState, ToplevelData, ToplevelIdentifier,
ToplevelNode, ToplevelNodeBase, Transform, VrrMode, WorkspaceDisplayOrder,
WorkspaceNode, WorkspaceNodeId, WsMoveConfig, generic_node_visitor, move_ws_to_output,
},
udmabuf::UdmabufHolder,
utils::{
asyncevent::AsyncEvent,
bindings::Bindings,
clonecell::CloneCell,
copyhashmap::CopyHashMap,
errorfmt::ErrorFmt,
event_listener::{EventListener, EventSource, LazyEventSources},
fdcloser::FdCloser,
hash_map_ext::HashMapExt,
linkedlist::LinkedList,
numcell::NumCell,
object_drop_queue::ObjectDropQueue,
queue::AsyncQueue,
refcounted::RefCounted,
run_toplevel::RunToplevel,
},
video::{
dmabuf::DmaBufIds,
drm::{Drm, wait_for_syncobj::WaitForSyncobj},
},
virtual_output::VirtualOutputs,
wheel::Wheel,
wire::{
ExtForeignToplevelListV1Id, ExtIdleNotificationV1Id, JayHeadManagerSessionV1Id,
JayRenderCtxId, JaySeatEventsId, JayWorkspaceWatcherId, ZwlrForeignToplevelManagerV1Id,
ZwpLinuxDmabufFeedbackV1Id,
},
xwayland::{self, XWaylandEvent},
},
ahash::AHashMap,
bstr::ByteSlice,
jay_config::PciId,
std::{
cell::{Cell, RefCell},
fmt::{Debug, Formatter},
mem,
ops::{Deref, DerefMut},
rc::{Rc, Weak},
sync::Arc,
time::{Duration, SystemTime},
},
thiserror::Error,
uapi::{OwnedFd, c},
};
#[derive(Clone)]
pub(crate) struct LayoutAnimationCandidate {
node_id: NodeId,
old: Rect,
new: Rect,
curve: AnimationCurve,
style: AnimationStyle,
hierarchy: MultiphaseWindowHierarchy,
}
fn coalesce_layout_animation_candidates(
candidates: Vec<LayoutAnimationCandidate>,
) -> Vec<LayoutAnimationCandidate> {
let mut merged: Vec<LayoutAnimationCandidate> = vec![];
for candidate in candidates {
if let Some(existing) = merged
.iter_mut()
.find(|existing| existing.node_id == candidate.node_id)
{
existing.new = candidate.new;
existing.curve = candidate.curve;
existing.style = candidate.style;
existing.hierarchy = MultiphaseWindowHierarchy::new(
existing.hierarchy.source,
candidate.hierarchy.target,
);
} else {
merged.push(candidate);
}
}
merged
}
fn layout_animation_group_uses_plain(
candidates: &[LayoutAnimationCandidate],
group: &[usize],
) -> bool {
group
.iter()
.any(|&idx| candidates[idx].style == AnimationStyle::Plain)
}
fn bridged_retarget_plan(
request: &MultiphaseRequest,
candidates: &[LayoutAnimationCandidate],
group: &[usize],
bridge_paths: &[Vec<(Rect, Rect)>],
bridge_phase_count: usize,
follow_phases: &[MultiphasePhase],
) -> Result<MultiphasePlan, MultiphasePlanFailure> {
let mut paths = vec![];
for (group_pos, &idx) in group.iter().enumerate() {
let candidate = &candidates[idx];
let window = request.windows[group_pos];
let Some(bridge_path) = bridge_paths.get(group_pos) else {
return Err(MultiphasePlanFailure::NoPattern);
};
let mut path = bridge_path.clone();
let mut current = path
.last()
.map(|(_, to)| *to)
.unwrap_or(window.from);
while path.len() < bridge_phase_count {
path.push((current, current));
}
if current != candidate.old {
return Err(MultiphasePlanFailure::NoPattern);
}
for phase in follow_phases {
match phase
.steps
.iter()
.find(|step| step.node_id == candidate.node_id)
{
Some(step) => {
if step.from != current {
return Err(MultiphasePlanFailure::NoPattern);
}
path.push((step.from, step.to));
current = step.to;
}
None => path.push((current, current)),
}
}
if current != window.to {
return Err(MultiphasePlanFailure::NoPattern);
}
paths.push(path);
}
validate_phase_paths(request, &paths)
}
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_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 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 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 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: XTransferDeviceIds,
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 key_press_enables_dpms: Cell<bool>,
pub mouse_move_enables_dpms: Cell<bool>,
pub timeout_changed: Cell<bool>,
pub inhibitors: CopyHashMap<IdleInhibitorId, Rc<ZwpIdleInhibitorV1>>,
pub inhibitors_changed: Cell<bool>,
pub backend_idle: Cell<bool>,
pub dpms_off_by_command: 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();
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 input_occurred(self: &Rc<Self>, key_press: bool, mouse_move: bool) {
if self.idle.dpms_off_by_command.get() {
let enable_dpms = key_press && self.idle.key_press_enables_dpms.get()
|| mouse_move && self.idle.mouse_move_enables_dpms.get();
if enable_dpms && let Err(e) = self.set_dpms_active(true) {
log::error!("Could not enable DPMS after input: {}", ErrorFmt(e));
}
}
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.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.security_context_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 set_connectors_active(
self: &Rc<Self>,
active: bool,
) -> Result<(), BackendConnectorTransactionError> {
let mut tran = ConnectorTransaction::new(self);
for connector in self.connectors.lock().values() {
let mut state = connector.state.borrow().clone();
state.active = active;
tran.add(&connector.connector, state)?;
}
tran.prepare()?.apply()?.commit();
self.set_backend_idle(!active);
Ok(())
}
pub fn set_dpms_active(
self: &Rc<Self>,
active: bool,
) -> Result<(), BackendConnectorTransactionError> {
self.set_connectors_active(active)?;
self.idle.dpms_off_by_command.set(!active);
Ok(())
}
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 queue_tiled_animation(
self: &Rc<Self>,
node_id: NodeId,
old: Rect,
new: Rect,
) {
let curve = self
.layout_animation_curve_override
.get()
.unwrap_or_else(|| self.animations.curve.get());
self.queue_layout_animation(
node_id,
old,
new,
curve,
MultiphaseWindowHierarchy::default(),
);
}
pub fn queue_tiled_animation_with_hierarchy(
self: &Rc<Self>,
node_id: NodeId,
old: Rect,
new: Rect,
hierarchy: MultiphaseWindowHierarchy,
) {
let curve = self
.layout_animation_curve_override
.get()
.unwrap_or_else(|| self.animations.curve.get());
self.queue_layout_animation(node_id, old, new, curve, hierarchy);
}
pub fn queue_linear_layout_animation(
self: &Rc<Self>,
node_id: NodeId,
old: Rect,
new: Rect,
) {
self.queue_layout_animation(
node_id,
old,
new,
AnimationCurve::Linear,
MultiphaseWindowHierarchy::default(),
);
}
fn queue_layout_animation(
self: &Rc<Self>,
node_id: NodeId,
old: Rect,
new: Rect,
curve: AnimationCurve,
hierarchy: MultiphaseWindowHierarchy,
) {
if !self.animations.enabled.get()
|| !self.layout_animations_active.get()
|| self.suppress_animations_for_next_layout.get()
{
return;
}
let (old_output, old_scale) = {
let (x, y) = old.center();
let (output, _, _) = self.find_closest_output(x, y);
(output.id, output.global.persistent.scale.get())
};
let (new_output, new_scale) = {
let (x, y) = new.center();
let (output, _, _) = self.find_closest_output(x, y);
(output.id, output.global.persistent.scale.get())
};
if old_output != new_output || old_scale != new_scale {
return;
}
let candidate = LayoutAnimationCandidate {
node_id,
old,
new,
curve,
style: self
.layout_animation_style_override
.get()
.unwrap_or_else(|| self.animations.style.get()),
hierarchy,
};
if let Some(batch) = self.layout_animation_batch.borrow_mut().as_mut() {
batch.push(candidate);
return;
}
self.start_layout_animation_candidate(candidate, self.now_nsec());
}
fn start_layout_animation_candidate(
self: &Rc<Self>,
candidate: LayoutAnimationCandidate,
now_nsec: u64,
) {
let started = self.animations.set_target(
candidate.node_id,
candidate.old,
candidate.new,
None,
now_nsec,
self.animations.duration_ms.get(),
candidate.curve,
);
if started {
self.damage(expand_damage_rect(
candidate.old.union(candidate.new),
self.theme.sizes.border_width.get().max(0),
));
self.ensure_animation_tick();
}
}
pub fn begin_layout_animation_batch(&self) {
self.layout_animation_batch
.borrow_mut()
.get_or_insert_with(Vec::new);
}
pub fn finish_layout_animation_batch(self: &Rc<Self>) {
let Some(candidates) = self.layout_animation_batch.borrow_mut().take() else {
return;
};
let candidates = coalesce_layout_animation_candidates(candidates);
if candidates.is_empty() {
return;
}
let now = self.now_nsec();
let windows: Vec<_> = candidates
.iter()
.map(|candidate| {
MultiphaseWindow::with_hierarchy(
candidate.node_id,
self.animations
.visual_rect(candidate.node_id, candidate.old, now),
candidate.new,
candidate.hierarchy,
)
})
.collect();
for group in partition_motion_groups(&windows, self.layout_animation_clearance()) {
if layout_animation_group_uses_plain(&candidates, &group) {
for idx in group {
self.start_layout_animation_candidate(candidates[idx].clone(), now);
}
continue;
}
if self.start_multiphase_layout_animation(&candidates, &windows, &group, now) {
continue;
}
for idx in group {
self.start_layout_animation_candidate(candidates[idx].clone(), now);
}
}
}
fn layout_animation_clearance(&self) -> i32 {
let border = self.theme.sizes.border_width.get().max(0);
let gap = self.theme.sizes.gap.get().max(0);
if gap == 0 { border } else { gap + 2 * border }
}
fn start_multiphase_layout_animation(
self: &Rc<Self>,
candidates: &[LayoutAnimationCandidate],
windows: &[MultiphaseWindow],
group: &[usize],
now_nsec: u64,
) -> bool {
let request_windows: Vec<_> = group.iter().map(|&idx| windows[idx]).collect();
let Some(first) = request_windows.first() else {
return false;
};
let mut bounds = first.from.union(first.to);
for window in &request_windows[1..] {
bounds = bounds.union(window.from).union(window.to);
}
let request = MultiphaseRequest {
bounds,
windows: request_windows,
clearance: self.layout_animation_clearance(),
};
if self.start_existing_phased_retarget(candidates, windows, group, &request, now_nsec) {
return true;
}
if self.start_bridged_phased_retarget(candidates, windows, group, &request, now_nsec) {
return true;
}
let plan = match plan_no_overlap_with_diagnostics(&request) {
Ok(plan) => plan,
Err(diagnostic) => {
log::debug!(
"falling back to plain layout animation for group {:?}: {:?}",
group,
diagnostic
);
return false;
}
};
self.start_multiphase_plan(candidates, windows, group, &plan.phases, now_nsec)
}
fn start_existing_phased_retarget(
self: &Rc<Self>,
candidates: &[LayoutAnimationCandidate],
windows: &[MultiphaseWindow],
group: &[usize],
request: &MultiphaseRequest,
now_nsec: u64,
) -> bool {
let mut paths = vec![];
for &idx in group {
let candidate = &candidates[idx];
let window = windows[idx];
let Some(path) =
self.animations
.phased_route_to(candidate.node_id, window.to, now_nsec)
else {
return false;
};
paths.push(path);
}
let plan = match validate_phase_paths(request, &paths) {
Ok(plan) => plan,
Err(error) => {
log::debug!(
"existing phased retarget rejected for group {:?}: {:?}",
group,
error
);
return false;
}
};
log::debug!("retargeting active phased animation for group {:?}", group);
self.start_multiphase_plan(candidates, windows, group, &plan.phases, now_nsec)
}
fn start_bridged_phased_retarget(
self: &Rc<Self>,
candidates: &[LayoutAnimationCandidate],
windows: &[MultiphaseWindow],
group: &[usize],
request: &MultiphaseRequest,
now_nsec: u64,
) -> bool {
let mut bridge_paths = vec![];
let mut bridge_phase_count = 0;
let mut has_bridge = false;
for &idx in group {
let candidate = &candidates[idx];
let window = windows[idx];
if window.from == candidate.old {
bridge_paths.push(vec![]);
continue;
}
let Some(path) =
self.animations
.phased_route_to(candidate.node_id, candidate.old, now_nsec)
else {
return false;
};
if !path.is_empty() {
has_bridge = true;
bridge_phase_count = bridge_phase_count.max(path.len());
}
bridge_paths.push(path);
}
if !has_bridge {
return false;
}
let settled_windows: Vec<_> = group
.iter()
.map(|&idx| {
let candidate = &candidates[idx];
MultiphaseWindow::with_hierarchy(
candidate.node_id,
candidate.old,
candidate.new,
candidate.hierarchy,
)
})
.collect();
let Some(first) = settled_windows.first() else {
return false;
};
let mut bounds = first.from.union(first.to);
for window in &settled_windows[1..] {
bounds = bounds.union(window.from).union(window.to);
}
let settled_request = MultiphaseRequest {
bounds,
windows: settled_windows,
clearance: self.layout_animation_clearance(),
};
let follow_plan = match plan_no_overlap_with_diagnostics(&settled_request) {
Ok(plan) => plan,
Err(diagnostic) => {
log::debug!(
"bridged phased retarget follow-up rejected for group {:?}: {:?}",
group,
diagnostic
);
return false;
}
};
let plan = match bridged_retarget_plan(
request,
candidates,
group,
&bridge_paths,
bridge_phase_count,
&follow_plan.phases,
) {
Ok(plan) => plan,
Err(error) => {
log::debug!(
"bridged phased retarget rejected for group {:?}: {:?}",
group,
error
);
return false;
}
};
log::debug!("bridging active phased animation for group {:?}", group);
self.start_multiphase_plan(candidates, windows, group, &plan.phases, now_nsec)
}
fn start_multiphase_plan(
self: &Rc<Self>,
candidates: &[LayoutAnimationCandidate],
windows: &[MultiphaseWindow],
group: &[usize],
plan_phases: &[crate::animation::multiphase::MultiphasePhase],
now_nsec: u64,
) -> bool {
if plan_phases.is_empty() {
return false;
}
let mut entries = vec![];
for &idx in group {
let candidate = &candidates[idx];
let window = windows[idx];
let mut current = window.from;
let mut damage = current.union(window.to);
let mut phases = vec![];
for phase in plan_phases {
match phase
.steps
.iter()
.find(|step| step.node_id == candidate.node_id)
{
Some(step) => {
phases.push((step.from, step.to));
damage = damage.union(step.from).union(step.to);
current = step.to;
}
None => phases.push((current, current)),
}
}
if current != window.to {
return false;
}
entries.push((candidate.clone(), phases, damage));
}
let mut started_any = false;
for (candidate, phases, damage) in entries {
if self.animations.set_phased_target(
candidate.node_id,
phases,
None,
now_nsec,
self.animations.duration_ms.get(),
candidate.curve,
) {
started_any = true;
self.damage(expand_damage_rect(
damage,
self.theme.sizes.border_width.get().max(0),
));
}
}
if started_any {
self.ensure_animation_tick();
}
started_any
}
pub fn queue_spawn_in_animation(
self: &Rc<Self>,
node_id: NodeId,
target: Rect,
) {
if !self.animations.enabled.get() || target.is_empty() {
return;
}
let start = spawn_in_start_rect(target);
let now = self.now_nsec();
let started = self.animations.set_spawn_in(
node_id,
target,
None,
now,
self.animations.duration_ms.get(),
self.animations.curve.get(),
);
if started {
self.damage(expand_damage_rect(
start.union(target),
self.theme.sizes.border_width.get().max(0),
));
self.ensure_animation_tick();
}
}
pub fn queue_spawn_out_animation(
self: &Rc<Self>,
from: Rect,
frame_inset: i32,
retained: Rc<RetainedToplevel>,
active: bool,
layer: RetainedExitLayer,
) {
if !self.animations.enabled.get() || from.is_empty() {
return;
}
let now = self.now_nsec();
let started = self.animations.set_spawn_out(
from,
frame_inset,
retained,
active,
layer,
now,
self.animations.duration_ms.get(),
self.animations.curve.get(),
);
if started {
self.damage(expand_damage_rect(
from,
self.theme.sizes.border_width.get().max(0),
));
self.ensure_animation_tick();
}
}
pub fn set_animations_enabled(&self, enabled: bool) {
if self.animations.enabled.replace(enabled) && !enabled {
self.animations.clear();
self.damage(self.root.extents.get());
}
}
pub fn set_animation_duration_ms(&self, duration_ms: u32) {
self.animations.duration_ms.set(duration_ms);
}
pub fn set_animation_curve(&self, curve: u32) {
self.animations
.curve
.set(AnimationCurve::from_config(curve));
}
pub fn set_animation_style(&self, style: u32) -> bool {
let Some(style) = AnimationStyle::from_config(style) else {
return false;
};
self.animations.style.set(style);
true
}
pub fn set_animation_cubic_bezier(&self, x1: f32, y1: f32, x2: f32, y2: f32) -> bool {
let Some(curve) = AnimationCurve::from_cubic_bezier(x1, y1, x2, y2) else {
return false;
};
self.animations.curve.set(curve);
true
}
pub fn with_layout_animations<T>(&self, f: impl FnOnce() -> T) -> T {
let prev_requested = self.layout_animations_requested.replace(true);
let prev_active = self.layout_animations_active.replace(true);
let res = f();
self.layout_animations_requested.set(prev_requested);
self.layout_animations_active.set(prev_active);
res
}
pub fn with_linear_layout_animations<T>(&self, f: impl FnOnce() -> T) -> T {
let prev_requested = self.layout_animations_requested.replace(true);
let prev_active = self.layout_animations_active.replace(true);
let prev_curve = self
.layout_animation_curve_override
.replace(Some(AnimationCurve::Linear));
let prev_style = self
.layout_animation_style_override
.replace(Some(AnimationStyle::Plain));
let res = f();
self.layout_animations_requested.set(prev_requested);
self.layout_animations_active.set(prev_active);
self.layout_animation_curve_override.set(prev_curve);
self.layout_animation_style_override.set(prev_style);
res
}
fn ensure_animation_tick(self: &Rc<Self>) {
if self.animations.tick_is_active() {
return;
}
let outputs: Vec<_> = self.root.outputs.lock().values().cloned().collect();
if outputs.is_empty() {
return;
}
let tick = Rc::new_cyclic(|weak| AnimationTick::new(self, weak));
for output in &outputs {
tick.attach(output);
}
self.animations.set_tick(tick);
for output in &outputs {
self.damage(output.global.pos.get());
}
}
pub fn output_extents_changed(&self) {
self.root.update_extents();
for seat in self.globals.seats.lock().values() {
seat.output_extents_changed();
}
}
pub fn update_ei_acceptor(self: &Rc<Self>) {
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 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),
})
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::animation::multiphase::MultiphaseHierarchyPosition,
};
fn rect(x1: i32, y1: i32, x2: i32, y2: i32) -> Rect {
Rect::new_saturating(x1, y1, x2, y2)
}
fn hierarchy(
source: MultiphaseHierarchyPosition,
target: MultiphaseHierarchyPosition,
) -> MultiphaseWindowHierarchy {
MultiphaseWindowHierarchy::new(source, target)
}
fn candidate(node_id: u32, style: AnimationStyle) -> LayoutAnimationCandidate {
candidate_rects(
node_id,
rect(0, 0, 100, 100),
rect(100, 0, 200, 100),
style,
)
}
fn candidate_rects(
node_id: u32,
old: Rect,
new: Rect,
style: AnimationStyle,
) -> LayoutAnimationCandidate {
LayoutAnimationCandidate {
node_id: NodeId(node_id),
old,
new,
curve: AnimationCurve::Linear,
style,
hierarchy: MultiphaseWindowHierarchy::default(),
}
}
#[test]
fn plain_style_candidate_forces_group_plain() {
let candidates = vec![
candidate(1, AnimationStyle::Multiphase),
candidate(2, AnimationStyle::Plain),
];
assert!(!layout_animation_group_uses_plain(&candidates, &[0]));
assert!(layout_animation_group_uses_plain(&candidates, &[0, 1]));
}
#[test]
fn bridged_retarget_handles_second_rotation_interrupt() {
let a_left = rect(0, 0, 100, 100);
let c_mid = rect(100, 0, 200, 100);
let c_left = a_left;
let a_mid = c_mid;
let c_current = rect(150, 50, 250, 100);
let c_mid_lane = rect(100, 50, 200, 100);
let candidates = vec![
candidate_rects(1, a_left, a_mid, AnimationStyle::Multiphase),
candidate_rects(3, c_mid, c_left, AnimationStyle::Multiphase),
];
let request = MultiphaseRequest {
bounds: rect(0, 0, 250, 100),
windows: vec![
MultiphaseWindow::new(NodeId(1), a_left, a_mid),
MultiphaseWindow::new(NodeId(3), c_current, c_left),
],
clearance: 0,
};
let settled_request = MultiphaseRequest {
bounds: rect(0, 0, 200, 100),
windows: vec![
MultiphaseWindow::new(NodeId(1), a_left, a_mid),
MultiphaseWindow::new(NodeId(3), c_mid, c_left),
],
clearance: 0,
};
let follow_plan = plan_no_overlap_with_diagnostics(&settled_request).unwrap();
let bridge_paths = vec![vec![], vec![(c_current, c_mid_lane), (c_mid_lane, c_mid)]];
let plan = bridged_retarget_plan(
&request,
&candidates,
&[0, 1],
&bridge_paths,
2,
&follow_plan.phases,
)
.unwrap();
assert!(plan
.phases
.iter()
.any(|phase| phase.steps.iter().any(|step| step.node_id == NodeId(1))));
assert!(plan
.phases
.iter()
.any(|phase| phase.steps.iter().any(|step| step.node_id == NodeId(3))));
}
#[test]
fn layout_animation_candidates_coalesce_duplicate_nodes() {
let source = MultiphaseHierarchyPosition {
parent: Some(NodeId(10).into()),
depth: 2,
sibling_index: Some(1),
..Default::default()
};
let intermediate = MultiphaseHierarchyPosition {
parent: Some(NodeId(11).into()),
depth: 1,
sibling_index: Some(0),
..Default::default()
};
let target = MultiphaseHierarchyPosition {
parent: Some(NodeId(12).into()),
depth: 0,
sibling_index: Some(2),
..Default::default()
};
let second_source = MultiphaseHierarchyPosition {
parent: Some(NodeId(20).into()),
depth: 1,
sibling_index: Some(0),
..Default::default()
};
let second_target = MultiphaseHierarchyPosition {
parent: Some(NodeId(20).into()),
depth: 1,
sibling_index: Some(1),
..Default::default()
};
let candidates = vec![
LayoutAnimationCandidate {
node_id: NodeId(1),
old: rect(0, 0, 100, 100),
new: rect(0, 0, 80, 100),
curve: AnimationCurve::Linear,
style: AnimationStyle::Multiphase,
hierarchy: hierarchy(source, intermediate),
},
LayoutAnimationCandidate {
node_id: NodeId(2),
old: rect(100, 0, 200, 100),
new: rect(120, 0, 220, 100),
curve: AnimationCurve::Linear,
style: AnimationStyle::Multiphase,
hierarchy: hierarchy(second_source, second_target),
},
LayoutAnimationCandidate {
node_id: NodeId(1),
old: rect(0, 0, 80, 100),
new: rect(0, 0, 60, 100),
curve: AnimationCurve::from_config(4),
style: AnimationStyle::Plain,
hierarchy: hierarchy(intermediate, target),
},
];
let merged = coalesce_layout_animation_candidates(candidates);
assert_eq!(merged.len(), 2);
assert_eq!(merged[0].node_id, NodeId(1));
assert_eq!(merged[0].old, rect(0, 0, 100, 100));
assert_eq!(merged[0].new, rect(0, 0, 60, 100));
assert_eq!(merged[0].curve, AnimationCurve::from_config(4));
assert_eq!(merged[0].style, AnimationStyle::Plain);
assert_eq!(merged[0].hierarchy, hierarchy(source, target));
assert_eq!(merged[1].node_id, NodeId(2));
assert_eq!(merged[1].old, rect(100, 0, 200, 100));
assert_eq!(merged[1].new, rect(120, 0, 220, 100));
assert_eq!(merged[1].hierarchy, hierarchy(second_source, second_target));
}
#[test]
fn layout_animation_candidates_keep_coalesced_layout_noops() {
let hierarchy = MultiphaseWindowHierarchy::default();
let candidates = vec![
LayoutAnimationCandidate {
node_id: NodeId(1),
old: rect(0, 0, 100, 100),
new: rect(0, 0, 80, 100),
curve: AnimationCurve::Linear,
style: AnimationStyle::Multiphase,
hierarchy,
},
LayoutAnimationCandidate {
node_id: NodeId(1),
old: rect(0, 0, 80, 100),
new: rect(0, 0, 100, 100),
curve: AnimationCurve::Linear,
style: AnimationStyle::Plain,
hierarchy,
},
LayoutAnimationCandidate {
node_id: NodeId(2),
old: rect(100, 0, 200, 100),
new: rect(120, 0, 220, 100),
curve: AnimationCurve::Linear,
style: AnimationStyle::Multiphase,
hierarchy,
},
];
let merged = coalesce_layout_animation_candidates(candidates);
assert_eq!(merged.len(), 2);
assert_eq!(merged[0].node_id, NodeId(1));
assert_eq!(merged[0].old, rect(0, 0, 100, 100));
assert_eq!(merged[0].new, rect(0, 0, 100, 100));
assert_eq!(merged[0].style, AnimationStyle::Plain);
assert_eq!(merged[1].node_id, NodeId(2));
}
}
#[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),
}