2025-01-04 12:18:58 +03:00
|
|
|
use std::cell::{Cell, OnceCell, RefCell};
|
2023-10-27 16:50:02 +04:00
|
|
|
use std::collections::{HashMap, HashSet};
|
2023-10-10 08:49:47 +04:00
|
|
|
use std::ffi::OsString;
|
2025-01-27 07:55:32 +03:00
|
|
|
use std::os::unix::net::UnixStream;
|
2023-09-20 09:28:23 +04:00
|
|
|
use std::path::PathBuf;
|
2023-09-14 22:28:26 +04:00
|
|
|
use std::rc::Rc;
|
2024-02-11 10:59:57 +04:00
|
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
2024-08-22 14:44:11 +03:00
|
|
|
use std::sync::mpsc::{self, Receiver, Sender};
|
2023-08-15 16:19:05 +04:00
|
|
|
use std::sync::{Arc, Mutex};
|
2023-10-29 14:04:38 +04:00
|
|
|
use std::time::{Duration, Instant};
|
2023-09-30 17:13:56 +04:00
|
|
|
use std::{env, mem, thread};
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2023-09-27 16:08:11 +04:00
|
|
|
use _server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as KdeDecorationsMode;
|
2024-07-30 13:38:25 +10:00
|
|
|
use anyhow::{bail, ensure, Context};
|
2024-01-17 10:23:48 +04:00
|
|
|
use calloop::futures::Scheduler;
|
2025-08-27 10:36:45 +03:00
|
|
|
use niri_config::debug::PreviewRender;
|
2024-06-18 11:01:18 +03:00
|
|
|
use niri_config::{
|
2025-08-27 10:36:45 +03:00
|
|
|
Config, FloatOrInt, Key, Modifiers, OutputName, TrackLayout, WarpMouseToFocusMode,
|
|
|
|
|
WorkspaceReference, Xkb,
|
2024-06-18 11:01:18 +03:00
|
|
|
};
|
2023-08-27 19:34:37 +04:00
|
|
|
use smithay::backend::allocator::Fourcc;
|
2024-02-24 18:17:51 +01:00
|
|
|
use smithay::backend::input::Keycode;
|
2024-05-03 10:25:51 +04:00
|
|
|
use smithay::backend::renderer::damage::OutputDamageTracker;
|
2024-01-23 20:44:47 +01:00
|
|
|
use smithay::backend::renderer::element::memory::MemoryRenderBufferRenderElement;
|
2023-08-15 16:19:05 +04:00
|
|
|
use smithay::backend::renderer::element::surface::{
|
|
|
|
|
render_elements_from_surface_tree, WaylandSurfaceRenderElement,
|
|
|
|
|
};
|
2024-03-08 13:10:55 +01:00
|
|
|
use smithay::backend::renderer::element::utils::{
|
2025-04-25 08:53:16 +03:00
|
|
|
select_dmabuf_feedback, CropRenderElement, Relocate, RelocateRenderElement,
|
|
|
|
|
RescaleRenderElement,
|
2024-03-08 13:10:55 +01:00
|
|
|
};
|
2023-08-27 19:34:37 +04:00
|
|
|
use smithay::backend::renderer::element::{
|
2025-04-25 08:53:16 +03:00
|
|
|
default_primary_scanout_output_compare, Element, Id, Kind, PrimaryScanoutOutput,
|
|
|
|
|
RenderElementStates,
|
2023-08-27 19:34:37 +04:00
|
|
|
};
|
2024-02-06 11:34:36 +04:00
|
|
|
use smithay::backend::renderer::gles::GlesRenderer;
|
2024-08-08 12:52:24 +03:00
|
|
|
use smithay::backend::renderer::sync::SyncPoint;
|
2025-03-09 22:03:14 +03:00
|
|
|
use smithay::backend::renderer::Color32F;
|
2023-08-16 10:59:34 +04:00
|
|
|
use smithay::desktop::utils::{
|
2023-10-02 18:48:41 +04:00
|
|
|
bbox_from_surface_tree, output_update, send_dmabuf_feedback_surface_tree,
|
|
|
|
|
send_frames_surface_tree, surface_presentation_feedback_flags_from_states,
|
|
|
|
|
surface_primary_scanout_output, take_presentation_feedback_surface_tree,
|
2023-10-13 13:30:11 +04:00
|
|
|
under_from_surface_tree, update_surface_primary_scanout_output, OutputPresentationFeedback,
|
2023-08-16 10:59:34 +04:00
|
|
|
};
|
2023-11-17 18:30:24 +04:00
|
|
|
use smithay::desktop::{
|
2025-01-23 11:50:43 +03:00
|
|
|
find_popup_root_surface, layer_map_for_output, LayerMap, LayerSurface, PopupGrab, PopupManager,
|
|
|
|
|
PopupUngrabStrategy, Space, Window, WindowSurfaceType,
|
2023-11-17 18:30:24 +04:00
|
|
|
};
|
2025-08-26 21:07:29 +03:00
|
|
|
use smithay::input::keyboard::{Layout as KeyboardLayout, XkbConfig};
|
2025-03-06 17:24:44 -05:00
|
|
|
use smithay::input::pointer::{
|
|
|
|
|
CursorIcon, CursorImageStatus, CursorImageSurfaceData, Focus,
|
|
|
|
|
GrabStartData as PointerGrabStartData, MotionEvent,
|
|
|
|
|
};
|
2023-08-09 11:03:38 +04:00
|
|
|
use smithay::input::{Seat, SeatState};
|
2025-03-15 11:22:30 +03:00
|
|
|
use smithay::output::{self, Output, OutputModeSource, PhysicalProperties, Subpixel, WeakOutput};
|
2023-08-09 11:03:38 +04:00
|
|
|
use smithay::reexports::calloop::generic::Generic;
|
2023-09-21 13:39:37 +04:00
|
|
|
use smithay::reexports::calloop::timer::{TimeoutAction, Timer};
|
2023-09-29 13:12:50 +04:00
|
|
|
use smithay::reexports::calloop::{
|
2024-03-23 12:46:26 +04:00
|
|
|
Interest, LoopHandle, LoopSignal, Mode, PostAction, RegistrationToken,
|
2023-09-29 13:12:50 +04:00
|
|
|
};
|
2024-02-17 07:47:06 +04:00
|
|
|
use smithay::reexports::wayland_protocols::ext::session_lock::v1::server::ext_session_lock_v1::ExtSessionLockV1;
|
2023-08-10 17:17:17 +04:00
|
|
|
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::WmCapabilities;
|
2023-09-27 16:08:11 +04:00
|
|
|
use smithay::reexports::wayland_protocols_misc::server_decoration as _server_decoration;
|
2024-07-30 13:38:25 +10:00
|
|
|
use smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1;
|
2023-08-13 12:46:53 +04:00
|
|
|
use smithay::reexports::wayland_server::backend::{
|
|
|
|
|
ClientData, ClientId, DisconnectReason, GlobalId,
|
|
|
|
|
};
|
2024-04-22 17:47:12 +04:00
|
|
|
use smithay::reexports::wayland_server::protocol::wl_shm;
|
2023-08-09 11:03:38 +04:00
|
|
|
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
2024-08-22 14:44:11 +03:00
|
|
|
use smithay::reexports::wayland_server::{Client, Display, DisplayHandle, Resource};
|
2023-08-27 19:34:37 +04:00
|
|
|
use smithay::utils::{
|
2024-07-06 18:20:19 +04:00
|
|
|
ClockSource, IsAlive as _, Logical, Monotonic, Physical, Point, Rectangle, Scale, Size,
|
|
|
|
|
Transform, SERIAL_COUNTER,
|
2023-08-27 19:34:37 +04:00
|
|
|
};
|
2023-09-29 13:12:50 +04:00
|
|
|
use smithay::wayland::compositor::{
|
2024-08-22 14:44:11 +03:00
|
|
|
with_states, with_surface_tree_downward, CompositorClientState, CompositorHandler,
|
|
|
|
|
CompositorState, HookId, SurfaceData, TraversalAction,
|
2023-09-29 13:12:50 +04:00
|
|
|
};
|
2023-10-29 14:04:38 +04:00
|
|
|
use smithay::wayland::cursor_shape::CursorShapeManagerState;
|
2024-01-03 18:16:20 +04:00
|
|
|
use smithay::wayland::dmabuf::DmabufState;
|
2024-05-29 14:29:37 +03:00
|
|
|
use smithay::wayland::fractional_scale::FractionalScaleManagerState;
|
2024-02-08 13:51:54 +04:00
|
|
|
use smithay::wayland::idle_inhibit::IdleInhibitManagerState;
|
|
|
|
|
use smithay::wayland::idle_notify::IdleNotifierState;
|
2024-01-23 17:05:08 +04:00
|
|
|
use smithay::wayland::input_method::{InputMethodManagerState, InputMethodSeat};
|
2025-01-18 15:26:42 +01:00
|
|
|
use smithay::wayland::keyboard_shortcuts_inhibit::{
|
|
|
|
|
KeyboardShortcutsInhibitState, KeyboardShortcutsInhibitor,
|
|
|
|
|
};
|
2023-08-09 11:03:38 +04:00
|
|
|
use smithay::wayland::output::OutputManagerState;
|
2023-12-21 16:19:16 +04:00
|
|
|
use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraintsState};
|
2023-09-02 15:09:07 +04:00
|
|
|
use smithay::wayland::pointer_gestures::PointerGesturesState;
|
2023-08-16 10:59:34 +04:00
|
|
|
use smithay::wayland::presentation::PresentationState;
|
2023-12-04 18:12:12 +04:00
|
|
|
use smithay::wayland::relative_pointer::RelativePointerManagerState;
|
2024-01-15 16:02:07 +04:00
|
|
|
use smithay::wayland::security_context::SecurityContextState;
|
2023-10-10 12:11:05 +04:00
|
|
|
use smithay::wayland::selection::data_device::{set_data_device_selection, DataDeviceState};
|
2025-02-14 08:47:26 +03:00
|
|
|
use smithay::wayland::selection::ext_data_control::DataControlState as ExtDataControlState;
|
2023-11-08 22:16:16 +04:00
|
|
|
use smithay::wayland::selection::primary_selection::PrimarySelectionState;
|
2025-02-14 08:35:49 +03:00
|
|
|
use smithay::wayland::selection::wlr_data_control::DataControlState as WlrDataControlState;
|
2023-10-13 13:30:11 +04:00
|
|
|
use smithay::wayland::session_lock::{LockSurface, SessionLockManagerState, SessionLocker};
|
2023-09-27 16:08:11 +04:00
|
|
|
use smithay::wayland::shell::kde::decoration::KdeDecorationState;
|
2024-04-25 08:43:37 +04:00
|
|
|
use smithay::wayland::shell::wlr_layer::{self, Layer, WlrLayerShellState};
|
2023-09-26 13:44:37 +04:00
|
|
|
use smithay::wayland::shell::xdg::decoration::XdgDecorationState;
|
2023-08-09 11:03:38 +04:00
|
|
|
use smithay::wayland::shell::xdg::XdgShellState;
|
|
|
|
|
use smithay::wayland::shm::ShmState;
|
2024-12-20 08:49:18 +03:00
|
|
|
#[cfg(test)]
|
|
|
|
|
use smithay::wayland::single_pixel_buffer::SinglePixelBufferState;
|
2023-08-09 11:03:38 +04:00
|
|
|
use smithay::wayland::socket::ListeningSocketSource;
|
2024-04-09 19:06:13 +04:00
|
|
|
use smithay::wayland::tablet_manager::TabletManagerState;
|
2023-09-30 23:16:20 +04:00
|
|
|
use smithay::wayland::text_input::TextInputManagerState;
|
2024-03-10 21:41:36 +04:00
|
|
|
use smithay::wayland::viewporter::ViewporterState;
|
2023-09-30 23:16:20 +04:00
|
|
|
use smithay::wayland::virtual_keyboard::VirtualKeyboardManagerState;
|
2024-06-09 21:09:28 -07:00
|
|
|
use smithay::wayland::xdg_activation::XdgActivationState;
|
2024-03-08 17:08:58 +04:00
|
|
|
use smithay::wayland::xdg_foreign::XdgForeignState;
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2025-08-21 15:02:25 +03:00
|
|
|
#[cfg(feature = "dbus")]
|
|
|
|
|
use crate::a11y::A11y;
|
2024-11-23 11:27:27 +03:00
|
|
|
use crate::animation::Clock;
|
2024-02-05 13:07:14 +04:00
|
|
|
use crate::backend::tty::SurfaceDmabufFeedback;
|
2024-12-20 08:49:18 +03:00
|
|
|
use crate::backend::{Backend, Headless, RenderResult, Tty, Winit};
|
2023-10-29 14:04:38 +04:00
|
|
|
use crate::cursor::{CursorManager, CursorTextureCache, RenderCursor, XCursor};
|
2023-10-10 10:25:26 +04:00
|
|
|
#[cfg(feature = "dbus")]
|
2025-07-15 15:44:10 +03:00
|
|
|
use crate::dbus::freedesktop_locale1::Locale1ToNiri;
|
|
|
|
|
#[cfg(feature = "dbus")]
|
2024-06-26 10:08:07 +04:00
|
|
|
use crate::dbus::gnome_shell_introspect::{self, IntrospectToNiri, NiriToIntrospect};
|
|
|
|
|
#[cfg(feature = "dbus")]
|
2023-10-10 09:55:44 +04:00
|
|
|
use crate::dbus::gnome_shell_screenshot::{NiriToScreenshot, ScreenshotToNiri};
|
2023-09-30 09:58:34 +04:00
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
2023-10-10 09:55:44 +04:00
|
|
|
use crate::dbus::mutter_screen_cast::{self, ScreenCastToNiri};
|
2023-08-14 15:53:24 +04:00
|
|
|
use crate::frame_clock::FrameClock;
|
2024-11-03 14:50:02 +01:00
|
|
|
use crate::handlers::{configure_lock_surface, XDG_ACTIVATION_TOKEN_TIMEOUT};
|
2025-03-06 17:24:44 -05:00
|
|
|
use crate::input::pick_color_grab::PickColorGrab;
|
2025-04-25 09:48:54 +03:00
|
|
|
use crate::input::scroll_swipe_gesture::ScrollSwipeGesture;
|
2024-05-11 13:21:05 +04:00
|
|
|
use crate::input::scroll_tracker::ScrollTracker;
|
2024-03-23 20:23:21 +04:00
|
|
|
use crate::input::{
|
2024-12-13 02:40:36 +02:00
|
|
|
apply_libinput_settings, mods_with_finger_scroll_binds, mods_with_mouse_binds,
|
|
|
|
|
mods_with_wheel_binds, TabletData,
|
2024-03-23 20:23:21 +04:00
|
|
|
};
|
2024-01-17 10:38:32 +04:00
|
|
|
use crate::ipc::server::IpcServer;
|
2024-11-14 11:33:08 +03:00
|
|
|
use crate::layer::mapped::LayerSurfaceRenderElement;
|
|
|
|
|
use crate::layer::MappedLayer;
|
2024-07-15 15:51:48 +02:00
|
|
|
use crate::layout::tile::TileRenderElement;
|
2025-04-25 09:36:50 +03:00
|
|
|
use crate::layout::workspace::{Workspace, WorkspaceId};
|
2025-02-10 08:03:39 +03:00
|
|
|
use crate::layout::{HitType, Layout, LayoutElement as _, MonitorRenderElement};
|
2024-11-24 09:41:43 +03:00
|
|
|
use crate::niri_render_elements;
|
2025-06-14 16:17:43 +03:00
|
|
|
use crate::protocols::ext_workspace::{self, ExtWorkspaceManagerState};
|
2024-01-29 19:34:12 +04:00
|
|
|
use crate::protocols::foreign_toplevel::{self, ForeignToplevelManagerState};
|
2024-03-03 20:36:13 +01:00
|
|
|
use crate::protocols::gamma_control::GammaControlManagerState;
|
2024-08-13 09:15:57 +03:00
|
|
|
use crate::protocols::mutter_x11_interop::MutterX11InteropManagerState;
|
2024-05-21 22:43:42 +00:00
|
|
|
use crate::protocols::output_management::OutputManagementManagerState;
|
2024-07-30 13:38:25 +10:00
|
|
|
use crate::protocols::screencopy::{Screencopy, ScreencopyBuffer, ScreencopyManagerState};
|
2025-01-18 15:26:42 +01:00
|
|
|
use crate::protocols::virtual_pointer::VirtualPointerManagerState;
|
2023-09-08 17:54:02 +04:00
|
|
|
use crate::pw_utils::{Cast, PipeWire};
|
2024-06-27 11:36:24 +04:00
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
2025-03-15 11:22:30 +03:00
|
|
|
use crate::pw_utils::{CastSizeChange, PwToNiri};
|
2024-05-02 17:52:06 +04:00
|
|
|
use crate::render_helpers::debug::draw_opaque_regions;
|
2024-05-07 22:06:43 +04:00
|
|
|
use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
|
2024-02-06 11:24:50 +04:00
|
|
|
use crate::render_helpers::renderer::NiriRenderer;
|
2025-04-28 07:53:03 +03:00
|
|
|
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
|
2024-06-01 12:27:30 +03:00
|
|
|
use crate::render_helpers::texture::TextureBuffer;
|
2024-04-21 20:10:35 +04:00
|
|
|
use crate::render_helpers::{
|
2025-02-27 08:09:44 +03:00
|
|
|
encompassing_geo, render_to_dmabuf, render_to_encompassing_texture, render_to_shm,
|
|
|
|
|
render_to_texture, render_to_vec, shaders, RenderTarget, SplitElements,
|
2024-04-21 20:10:35 +04:00
|
|
|
};
|
2024-02-21 10:50:30 +04:00
|
|
|
use crate::ui::config_error_notification::ConfigErrorNotification;
|
2025-08-22 07:58:37 +03:00
|
|
|
use crate::ui::exit_confirm_dialog::{ExitConfirmDialog, ExitConfirmDialogRenderElement};
|
2024-02-21 10:50:30 +04:00
|
|
|
use crate::ui::hotkey_overlay::HotkeyOverlay;
|
2024-05-07 22:06:43 +04:00
|
|
|
use crate::ui::screen_transition::{self, ScreenTransition};
|
2024-07-07 09:23:40 +04:00
|
|
|
use crate::ui::screenshot_ui::{OutputScreenshot, ScreenshotUi, ScreenshotUiRenderElement};
|
2024-06-17 09:18:54 +03:00
|
|
|
use crate::utils::scale::{closest_representable_scale, guess_monitor_scale};
|
2025-06-04 08:26:51 +03:00
|
|
|
use crate::utils::spawning::{CHILD_DISPLAY, CHILD_ENV};
|
2025-08-09 16:20:08 +04:00
|
|
|
use crate::utils::watcher::Watcher;
|
2025-06-04 08:26:51 +03:00
|
|
|
use crate::utils::xwayland::satellite::Satellite;
|
2023-12-24 14:30:19 +04:00
|
|
|
use crate::utils::{
|
2025-03-13 18:34:47 +03:00
|
|
|
center, center_f64, expand_home, get_monotonic_time, ipc_transform_to_smithay, is_mapped,
|
|
|
|
|
logical_output, make_screenshot_path, output_matches_name, output_size, send_scale_transform,
|
2025-06-04 08:26:51 +03:00
|
|
|
write_png_rgba8, xwayland,
|
2023-12-24 14:30:19 +04:00
|
|
|
};
|
2025-02-26 14:22:27 +02:00
|
|
|
use crate::window::mapped::MappedId;
|
2024-03-23 10:35:12 +04:00
|
|
|
use crate::window::{InitialConfigureState, Mapped, ResolvedWindowRules, Unmapped, WindowRef};
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2023-10-27 08:34:00 +04:00
|
|
|
const CLEAR_COLOR_LOCKED: [f32; 4] = [0.3, 0.1, 0.1, 1.];
|
2023-10-13 13:18:50 +04:00
|
|
|
|
2024-03-12 10:17:45 +04:00
|
|
|
// We'll try to send frame callbacks at least once a second. We'll make a timer that fires once a
|
|
|
|
|
// second, so with the worst timing the maximum interval between two frame callbacks for a surface
|
|
|
|
|
// should be ~1.995 seconds.
|
|
|
|
|
const FRAME_CALLBACK_THROTTLE: Option<Duration> = Some(Duration::from_millis(995));
|
|
|
|
|
|
2023-08-09 11:03:38 +04:00
|
|
|
pub struct Niri {
|
2023-09-14 22:28:26 +04:00
|
|
|
pub config: Rc<RefCell<Config>>,
|
|
|
|
|
|
2024-05-05 10:19:47 +04:00
|
|
|
/// Output config from the config file.
|
|
|
|
|
///
|
|
|
|
|
/// This does not include transient output config changes done via IPC. It is only used when
|
|
|
|
|
/// reloading the config from disk to determine if the output configuration should be reloaded
|
|
|
|
|
/// (and transient changes dropped).
|
2024-07-04 17:51:11 +04:00
|
|
|
pub config_file_output_config: niri_config::Outputs,
|
2024-05-05 10:19:47 +04:00
|
|
|
|
2025-08-09 16:20:08 +04:00
|
|
|
pub config_file_watcher: Option<Watcher>,
|
|
|
|
|
|
2023-09-24 17:30:06 +04:00
|
|
|
pub event_loop: LoopHandle<'static, State>,
|
2024-01-17 10:23:48 +04:00
|
|
|
pub scheduler: Scheduler<()>,
|
2023-08-09 11:03:38 +04:00
|
|
|
pub stop_signal: LoopSignal,
|
|
|
|
|
pub display_handle: DisplayHandle,
|
2025-01-27 08:28:02 +03:00
|
|
|
|
2025-07-14 07:34:10 -04:00
|
|
|
/// Whether niri was run with `--session`
|
|
|
|
|
pub is_session_instance: bool,
|
|
|
|
|
|
2025-01-27 08:28:02 +03:00
|
|
|
/// Name of the Wayland socket.
|
|
|
|
|
///
|
|
|
|
|
/// This is `None` when creating `Niri` without a Wayland socket.
|
|
|
|
|
pub socket_name: Option<OsString>,
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2023-10-29 14:04:38 +04:00
|
|
|
pub start_time: Instant,
|
|
|
|
|
|
2024-05-16 11:43:13 +04:00
|
|
|
/// Whether the at-startup=true window rules are active.
|
|
|
|
|
pub is_at_startup: bool,
|
|
|
|
|
|
2024-11-23 11:27:27 +03:00
|
|
|
/// Clock for driving animations.
|
|
|
|
|
pub clock: Clock,
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
// Each workspace corresponds to a Space. Each workspace generally has one Output mapped to it,
|
2024-06-09 10:50:22 +00:00
|
|
|
// however it may have none (when there are no outputs connected) or multiple (when mirroring).
|
2024-03-19 14:41:17 +04:00
|
|
|
pub layout: Layout<Mapped>,
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
// This space does not actually contain any windows, but all outputs are mapped into it
|
|
|
|
|
// according to their global position.
|
|
|
|
|
pub global_space: Space<Window>,
|
|
|
|
|
|
2024-12-15 16:04:42 +01:00
|
|
|
/// Mapped outputs, sorted by their name and position.
|
|
|
|
|
pub sorted_outputs: Vec<Output>,
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
// Windows which don't have a buffer attached yet.
|
2024-02-23 13:57:56 +04:00
|
|
|
pub unmapped_windows: HashMap<WlSurface, Unmapped>,
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2024-09-10 10:14:34 +03:00
|
|
|
/// Layer surfaces which don't have a buffer attached yet.
|
|
|
|
|
pub unmapped_layer_surfaces: HashSet<WlSurface>,
|
|
|
|
|
|
2024-11-14 11:33:08 +03:00
|
|
|
/// Extra data for mapped layer surfaces.
|
|
|
|
|
pub mapped_layer_surfaces: HashMap<LayerSurface, MappedLayer>,
|
|
|
|
|
|
2024-04-10 08:53:35 +04:00
|
|
|
// Cached root surface for every surface, so that we can access it in destroyed() where the
|
|
|
|
|
// normal get_parent() is cleared out.
|
|
|
|
|
pub root_surface: HashMap<WlSurface, WlSurface>,
|
|
|
|
|
|
2024-08-18 12:42:50 +03:00
|
|
|
// Dmabuf readiness pre-commit hook for a surface.
|
|
|
|
|
pub dmabuf_pre_commit_hook: HashMap<WlSurface, HookId>,
|
|
|
|
|
|
2024-08-22 14:44:11 +03:00
|
|
|
/// Clients to notify about their blockers being cleared.
|
|
|
|
|
pub blocker_cleared_tx: Sender<Client>,
|
|
|
|
|
pub blocker_cleared_rx: Receiver<Client>,
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
pub output_state: HashMap<Output, OutputState>,
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2023-10-09 18:37:43 +04:00
|
|
|
// When false, we're idling with monitors powered off.
|
|
|
|
|
pub monitors_active: bool,
|
|
|
|
|
|
2024-11-05 09:40:54 +03:00
|
|
|
/// Whether the laptop lid is closed.
|
|
|
|
|
///
|
|
|
|
|
/// Libinput guarantees that the lid switch starts in open state, and if it was closed during
|
|
|
|
|
/// startup, libinput will immediately send a closed event.
|
|
|
|
|
pub is_lid_closed: bool,
|
|
|
|
|
|
2024-01-16 20:28:46 +04:00
|
|
|
pub devices: HashSet<input::Device>,
|
2023-12-03 13:50:07 +04:00
|
|
|
pub tablets: HashMap<input::Device, TabletData>,
|
2024-02-24 18:32:13 +01:00
|
|
|
pub touch: HashSet<input::Device>,
|
2023-12-03 13:50:07 +04:00
|
|
|
|
2023-08-09 11:03:38 +04:00
|
|
|
// Smithay state.
|
|
|
|
|
pub compositor_state: CompositorState,
|
|
|
|
|
pub xdg_shell_state: XdgShellState,
|
2023-09-26 13:44:37 +04:00
|
|
|
pub xdg_decoration_state: XdgDecorationState,
|
2023-09-27 16:08:11 +04:00
|
|
|
pub kde_decoration_state: KdeDecorationState,
|
2023-08-15 12:49:26 +04:00
|
|
|
pub layer_shell_state: WlrLayerShellState,
|
2023-10-13 13:30:11 +04:00
|
|
|
pub session_lock_state: SessionLockManagerState,
|
2024-01-29 19:34:12 +04:00
|
|
|
pub foreign_toplevel_state: ForeignToplevelManagerState,
|
2025-06-14 16:17:43 +03:00
|
|
|
pub ext_workspace_state: ExtWorkspaceManagerState,
|
2024-03-08 13:10:55 +01:00
|
|
|
pub screencopy_state: ScreencopyManagerState,
|
2024-05-21 22:43:42 +00:00
|
|
|
pub output_management_state: OutputManagementManagerState,
|
2024-03-10 21:41:36 +04:00
|
|
|
pub viewporter_state: ViewporterState,
|
2024-03-08 17:08:58 +04:00
|
|
|
pub xdg_foreign_state: XdgForeignState,
|
2023-08-09 11:03:38 +04:00
|
|
|
pub shm_state: ShmState,
|
|
|
|
|
pub output_manager_state: OutputManagerState,
|
2023-12-31 12:02:39 +04:00
|
|
|
pub dmabuf_state: DmabufState,
|
2024-05-29 14:29:37 +03:00
|
|
|
pub fractional_scale_manager_state: FractionalScaleManagerState,
|
2023-09-03 14:10:02 +04:00
|
|
|
pub seat_state: SeatState<State>,
|
2023-08-16 11:43:52 +04:00
|
|
|
pub tablet_state: TabletManagerState,
|
2023-09-30 23:16:20 +04:00
|
|
|
pub text_input_state: TextInputManagerState,
|
|
|
|
|
pub input_method_state: InputMethodManagerState,
|
2025-01-18 15:26:42 +01:00
|
|
|
pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState,
|
2023-09-30 23:16:20 +04:00
|
|
|
pub virtual_keyboard_state: VirtualKeyboardManagerState,
|
2025-01-18 15:26:42 +01:00
|
|
|
pub virtual_pointer_state: VirtualPointerManagerState,
|
2023-09-02 15:09:07 +04:00
|
|
|
pub pointer_gestures_state: PointerGesturesState,
|
2023-12-04 18:12:12 +04:00
|
|
|
pub relative_pointer_state: RelativePointerManagerState,
|
2023-12-21 16:19:16 +04:00
|
|
|
pub pointer_constraints_state: PointerConstraintsState,
|
2024-02-08 13:51:54 +04:00
|
|
|
pub idle_notifier_state: IdleNotifierState<State>,
|
|
|
|
|
pub idle_inhibit_manager_state: IdleInhibitManagerState,
|
2023-08-09 11:03:38 +04:00
|
|
|
pub data_device_state: DataDeviceState,
|
2023-09-26 22:22:11 +04:00
|
|
|
pub primary_selection_state: PrimarySelectionState,
|
2025-02-14 08:35:49 +03:00
|
|
|
pub wlr_data_control_state: WlrDataControlState,
|
2025-02-14 08:47:26 +03:00
|
|
|
pub ext_data_control_state: ExtDataControlState,
|
2023-08-11 08:22:34 +04:00
|
|
|
pub popups: PopupManager,
|
2024-01-12 17:53:35 +04:00
|
|
|
pub popup_grab: Option<PopupGrabState>,
|
2023-08-16 10:59:34 +04:00
|
|
|
pub presentation_state: PresentationState,
|
2024-01-15 16:02:07 +04:00
|
|
|
pub security_context_state: SecurityContextState,
|
2024-03-03 20:36:13 +01:00
|
|
|
pub gamma_control_manager_state: GammaControlManagerState,
|
2024-06-09 21:09:28 -07:00
|
|
|
pub activation_state: XdgActivationState,
|
2024-08-13 09:15:57 +03:00
|
|
|
pub mutter_x11_interop_state: MutterX11InteropManagerState,
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2024-12-20 08:49:18 +03:00
|
|
|
// This will not work as is outside of tests, so it is gated with #[cfg(test)] for now. In
|
|
|
|
|
// particular, shaders will need to learn about the single pixel buffer. Also, it must be
|
|
|
|
|
// verified that a black single-pixel-buffer background lets the foreground surface to be
|
|
|
|
|
// unredirected.
|
|
|
|
|
//
|
|
|
|
|
// https://github.com/YaLTeR/niri/issues/619
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
pub single_pixel_buffer_state: SinglePixelBufferState,
|
|
|
|
|
|
2023-09-03 14:10:02 +04:00
|
|
|
pub seat: Seat<State>,
|
2023-10-27 16:50:02 +04:00
|
|
|
/// Scancodes of the keys to suppress.
|
2024-02-24 18:17:51 +01:00
|
|
|
pub suppressed_keys: HashSet<Keycode>,
|
2024-12-13 02:40:36 +02:00
|
|
|
/// Button codes of the mouse buttons to suppress.
|
|
|
|
|
pub suppressed_buttons: HashSet<u32>,
|
2024-03-22 20:47:40 +04:00
|
|
|
pub bind_cooldown_timers: HashMap<Key, RegistrationToken>,
|
2024-06-30 22:37:44 +05:00
|
|
|
pub bind_repeat_timer: Option<RegistrationToken>,
|
2024-03-17 09:21:03 +04:00
|
|
|
pub keyboard_focus: KeyboardFocus,
|
2024-07-06 18:20:19 +04:00
|
|
|
pub layer_shell_on_demand_focus: Option<LayerSurface>,
|
2024-11-21 12:48:51 +01:00
|
|
|
pub previously_focused_window: Option<Window>,
|
2024-02-08 13:51:54 +04:00
|
|
|
pub idle_inhibiting_surfaces: HashSet<WlSurface>,
|
2024-02-11 10:59:57 +04:00
|
|
|
pub is_fdo_idle_inhibited: Arc<AtomicBool>,
|
2025-01-18 15:26:42 +01:00
|
|
|
pub keyboard_shortcuts_inhibiting_surfaces: HashMap<WlSurface, KeyboardShortcutsInhibitor>,
|
2024-02-08 13:51:54 +04:00
|
|
|
|
2025-07-15 15:44:10 +03:00
|
|
|
/// Most recent XKB settings from org.freedesktop.locale1.
|
|
|
|
|
pub xkb_from_locale1: Option<Xkb>,
|
|
|
|
|
|
2023-10-29 14:04:38 +04:00
|
|
|
pub cursor_manager: CursorManager,
|
|
|
|
|
pub cursor_texture_cache: CursorTextureCache,
|
|
|
|
|
pub cursor_shape_manager_state: CursorShapeManagerState,
|
2024-02-24 18:17:51 +01:00
|
|
|
pub dnd_icon: Option<DndIcon>,
|
2024-11-03 09:16:06 +03:00
|
|
|
/// Contents under pointer.
|
|
|
|
|
///
|
|
|
|
|
/// Periodically updated: on motion and other events and in the loop callback. If you require
|
|
|
|
|
/// the real up-to-date contents somewhere, it's better to recompute on the spot.
|
|
|
|
|
///
|
|
|
|
|
/// This is not pointer focus. I.e. during a click grab, the pointer focus remains on the
|
|
|
|
|
/// client with the grab, but this field will keep updating to the latest contents as if no
|
|
|
|
|
/// grab was active.
|
|
|
|
|
///
|
|
|
|
|
/// This is primarily useful for emitting pointer motion events for surfaces that move
|
|
|
|
|
/// underneath the cursor on their own (i.e. when the tiling layout moves). In this case, not
|
|
|
|
|
/// taking grabs into account is expected, because we pass the information to pointer.motion()
|
|
|
|
|
/// which passes it down through grabs, which decide what to do with it as they see fit.
|
|
|
|
|
pub pointer_contents: PointContents,
|
2025-04-27 06:25:36 +00:00
|
|
|
pub pointer_visibility: PointerVisibility,
|
2024-10-06 20:27:04 +03:00
|
|
|
pub pointer_inactivity_timer: Option<RegistrationToken>,
|
2025-02-17 09:03:42 +03:00
|
|
|
/// Whether the pointer inactivity timer got reset this event loop iteration.
|
|
|
|
|
///
|
|
|
|
|
/// Used for limiting the reset to once per iteration, so that it's not spammed with high
|
|
|
|
|
/// resolution mice.
|
|
|
|
|
pub pointer_inactivity_timer_got_reset: bool,
|
2025-02-17 09:09:48 +03:00
|
|
|
/// Whether the (idle notifier) activity was notified this event loop iteration.
|
|
|
|
|
///
|
|
|
|
|
/// Used for limiting the notify to once per iteration, so that it's not spammed with high
|
|
|
|
|
/// resolution mice.
|
|
|
|
|
pub notified_activity_this_iteration: bool,
|
2025-04-25 10:08:26 +03:00
|
|
|
pub pointer_inside_hot_corner: bool,
|
2023-12-05 10:24:41 +04:00
|
|
|
pub tablet_cursor_location: Option<Point<f64, Logical>>,
|
2024-02-29 08:56:20 +04:00
|
|
|
pub gesture_swipe_3f_cumulative: Option<(f64, f64)>,
|
2025-04-25 09:48:54 +03:00
|
|
|
pub overview_scroll_swipe_gesture: ScrollSwipeGesture,
|
2024-03-23 19:58:11 +04:00
|
|
|
pub vertical_wheel_tracker: ScrollTracker,
|
|
|
|
|
pub horizontal_wheel_tracker: ScrollTracker,
|
2024-12-13 02:40:36 +02:00
|
|
|
pub mods_with_mouse_binds: HashSet<Modifiers>,
|
2024-03-23 19:20:44 +04:00
|
|
|
pub mods_with_wheel_binds: HashSet<Modifiers>,
|
2024-03-23 20:23:21 +04:00
|
|
|
pub vertical_finger_scroll_tracker: ScrollTracker,
|
|
|
|
|
pub horizontal_finger_scroll_tracker: ScrollTracker,
|
|
|
|
|
pub mods_with_finger_scroll_binds: HashSet<Modifiers>,
|
2023-08-27 10:29:06 +04:00
|
|
|
|
2023-10-13 13:30:11 +04:00
|
|
|
pub lock_state: LockState,
|
|
|
|
|
|
2025-07-14 07:34:10 -04:00
|
|
|
// State that we last sent to the logind LockedHint.
|
|
|
|
|
pub locked_hint: Option<bool>,
|
|
|
|
|
|
2023-10-30 20:29:03 +04:00
|
|
|
pub screenshot_ui: ScreenshotUi,
|
2024-01-18 11:15:48 +04:00
|
|
|
pub config_error_notification: ConfigErrorNotification,
|
2024-01-18 19:20:46 +04:00
|
|
|
pub hotkey_overlay: HotkeyOverlay,
|
2025-08-21 08:57:16 +03:00
|
|
|
pub exit_confirm_dialog: ExitConfirmDialog,
|
2023-10-30 20:29:03 +04:00
|
|
|
|
2025-02-26 14:22:27 +02:00
|
|
|
pub pick_window: Option<async_channel::Sender<Option<MappedId>>>,
|
2025-03-06 17:24:44 -05:00
|
|
|
pub pick_color: Option<async_channel::Sender<Option<niri_ipc::PickedColor>>>,
|
2025-02-26 14:22:27 +02:00
|
|
|
|
2024-05-02 17:52:06 +04:00
|
|
|
pub debug_draw_opaque_regions: bool,
|
2024-05-03 10:25:51 +04:00
|
|
|
pub debug_draw_damage: bool,
|
2024-05-02 17:52:06 +04:00
|
|
|
|
2023-10-10 10:25:26 +04:00
|
|
|
#[cfg(feature = "dbus")]
|
|
|
|
|
pub dbus: Option<crate::dbus::DBusServers>,
|
|
|
|
|
#[cfg(feature = "dbus")]
|
2025-07-16 11:54:32 +03:00
|
|
|
pub a11y_keyboard_monitor: Option<crate::dbus::freedesktop_a11y::KeyboardMonitor>,
|
|
|
|
|
#[cfg(feature = "dbus")]
|
2025-08-21 15:02:25 +03:00
|
|
|
pub a11y: A11y,
|
|
|
|
|
#[cfg(feature = "dbus")]
|
2023-09-03 10:28:00 +04:00
|
|
|
pub inhibit_power_key_fd: Option<zbus::zvariant::OwnedFd>,
|
2023-09-08 17:54:02 +04:00
|
|
|
|
2024-01-17 10:38:32 +04:00
|
|
|
pub ipc_server: Option<IpcServer>,
|
2024-03-27 09:46:18 +04:00
|
|
|
pub ipc_outputs_changed: bool,
|
2024-01-17 10:38:32 +04:00
|
|
|
|
2025-06-04 08:26:51 +03:00
|
|
|
pub satellite: Option<Satellite>,
|
|
|
|
|
|
2023-09-08 17:54:02 +04:00
|
|
|
// Casts are dropped before PipeWire to prevent a double-free (yay).
|
|
|
|
|
pub casts: Vec<Cast>,
|
2025-01-04 12:18:58 +03:00
|
|
|
pub pipewire: Option<PipeWire>,
|
|
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
|
|
|
|
pub pw_to_niri: calloop::channel::Sender<PwToNiri>,
|
2024-06-27 11:36:24 +04:00
|
|
|
|
|
|
|
|
// Screencast output for each mapped window.
|
|
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
|
|
|
|
pub mapped_cast_output: HashMap<Window, Output>,
|
2025-03-15 11:23:01 +03:00
|
|
|
|
|
|
|
|
/// Window ID for the "dynamic cast" special window for the xdp-gnome picker.
|
|
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
|
|
|
|
pub dynamic_cast_id_for_portal: MappedId,
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
2023-08-10 14:27:09 +04:00
|
|
|
|
2025-04-27 06:25:36 +00:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
|
|
pub enum PointerVisibility {
|
|
|
|
|
/// The pointer is visible.
|
|
|
|
|
Visible,
|
|
|
|
|
/// The pointer is invisible, but retains its focus.
|
|
|
|
|
///
|
|
|
|
|
/// This state is set temporarily after auto-hiding the pointer to keep tooltips open and grabs
|
|
|
|
|
/// ongoing.
|
|
|
|
|
Hidden,
|
|
|
|
|
/// The pointer is invisible and cannot focus.
|
|
|
|
|
///
|
|
|
|
|
/// Corresponds to a fully disabled pointer, for example after a touchscreen input, or after
|
|
|
|
|
/// the pointer contents changed in a Hidden state.
|
|
|
|
|
Disabled,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PointerVisibility {
|
|
|
|
|
pub fn is_visible(&self) -> bool {
|
|
|
|
|
matches!(self, Self::Visible)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-24 18:17:51 +01:00
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub struct DndIcon {
|
|
|
|
|
pub surface: WlSurface,
|
|
|
|
|
pub offset: Point<i32, Logical>,
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
pub struct OutputState {
|
|
|
|
|
pub global: GlobalId,
|
2023-08-14 15:53:24 +04:00
|
|
|
pub frame_clock: FrameClock,
|
2023-09-30 17:13:56 +04:00
|
|
|
pub redraw_state: RedrawState,
|
2024-08-22 18:58:07 +10:00
|
|
|
pub on_demand_vrr_enabled: bool,
|
2023-09-30 17:13:56 +04:00
|
|
|
// After the last redraw, some ongoing animations still remain.
|
|
|
|
|
pub unfinished_animations_remain: bool,
|
2024-03-02 08:10:05 +04:00
|
|
|
/// Last sequence received in a vblank event.
|
|
|
|
|
pub last_drm_sequence: Option<u32>,
|
|
|
|
|
/// Sequence for frame callback throttling.
|
2023-09-29 13:12:50 +04:00
|
|
|
///
|
2024-03-02 08:10:05 +04:00
|
|
|
/// We want to send frame callbacks for each surface at most once per monitor refresh cycle.
|
2023-09-29 13:12:50 +04:00
|
|
|
///
|
2024-03-02 08:10:05 +04:00
|
|
|
/// Even if a surface commit resulted in empty damage to the monitor, we want to delay the next
|
|
|
|
|
/// frame callback until roughly when a VBlank would occur, had the monitor been damaged. This
|
|
|
|
|
/// is necessary to prevent clients busy-looping with frame callbacks that result in empty
|
|
|
|
|
/// damage.
|
|
|
|
|
///
|
|
|
|
|
/// This counter wrapping-increments by 1 every time we move into the next refresh cycle, as
|
|
|
|
|
/// far as frame callback throttling is concerned. Specifically, it happens:
|
|
|
|
|
///
|
|
|
|
|
/// 1. Upon a successful DRM frame submission. Notably, we don't wait for the VBlank here,
|
|
|
|
|
/// because the client buffers are already "latched" at the point of submission. Even if a
|
|
|
|
|
/// client submits a new buffer right away, we will wait for a VBlank to draw it, which
|
|
|
|
|
/// means that busy looping is avoided.
|
|
|
|
|
/// 2. If a frame resulted in empty damage, a timer is queued to fire roughly when a VBlank
|
|
|
|
|
/// would occur, based on the last presentation time and output refresh interval. Sequence
|
|
|
|
|
/// is incremented in that timer, before attempting a redraw or sending frame callbacks.
|
|
|
|
|
pub frame_callback_sequence: u32,
|
2023-10-27 08:34:00 +04:00
|
|
|
/// Solid color buffer for the background that we use instead of clearing to avoid damage
|
|
|
|
|
/// tracking issues and make screenshots easier.
|
|
|
|
|
pub background_buffer: SolidColorBuffer,
|
2025-04-25 08:53:16 +03:00
|
|
|
pub backdrop_buffer: SolidColorBuffer,
|
2023-10-13 13:30:11 +04:00
|
|
|
pub lock_render_state: LockRenderState,
|
|
|
|
|
pub lock_surface: Option<LockSurface>,
|
|
|
|
|
pub lock_color_buffer: SolidColorBuffer,
|
2024-05-07 22:06:43 +04:00
|
|
|
screen_transition: Option<ScreenTransition>,
|
2024-05-03 10:25:51 +04:00
|
|
|
/// Damage tracker used for the debug damage visualization.
|
|
|
|
|
pub debug_damage_tracker: OutputDamageTracker,
|
2023-09-29 13:12:50 +04:00
|
|
|
}
|
|
|
|
|
|
2024-09-10 10:48:45 +03:00
|
|
|
#[derive(Debug, Default)]
|
2023-09-30 17:13:56 +04:00
|
|
|
pub enum RedrawState {
|
|
|
|
|
/// The compositor is idle.
|
|
|
|
|
#[default]
|
|
|
|
|
Idle,
|
|
|
|
|
/// A redraw is queued.
|
2024-03-23 12:46:26 +04:00
|
|
|
Queued,
|
2023-09-30 17:13:56 +04:00
|
|
|
/// We submitted a frame to the KMS and waiting for it to be presented.
|
|
|
|
|
WaitingForVBlank { redraw_needed: bool },
|
|
|
|
|
/// We did not submit anything to KMS and made a timer to fire at the estimated VBlank.
|
|
|
|
|
WaitingForEstimatedVBlank(RegistrationToken),
|
|
|
|
|
/// A redraw is queued on top of the above.
|
2024-03-23 12:46:26 +04:00
|
|
|
WaitingForEstimatedVBlankAndQueued(RegistrationToken),
|
2023-09-30 17:13:56 +04:00
|
|
|
}
|
|
|
|
|
|
2024-01-12 17:53:35 +04:00
|
|
|
pub struct PopupGrabState {
|
|
|
|
|
pub root: WlSurface,
|
|
|
|
|
pub grab: PopupGrab<State>,
|
2024-11-24 20:12:39 +01:00
|
|
|
pub has_keyboard_grab: bool,
|
2024-01-12 17:53:35 +04:00
|
|
|
}
|
|
|
|
|
|
2024-03-17 09:21:03 +04:00
|
|
|
// The surfaces here are always toplevel surfaces focused as far as niri's logic is concerned, even
|
|
|
|
|
// when popup grabs are active (which means the real keyboard focus is on a popup descending from
|
|
|
|
|
// that toplevel surface).
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
|
|
|
pub enum KeyboardFocus {
|
|
|
|
|
// Layout is focused by default if there's nothing else to focus.
|
|
|
|
|
Layout { surface: Option<WlSurface> },
|
|
|
|
|
LayerShell { surface: WlSurface },
|
|
|
|
|
LockScreen { surface: Option<WlSurface> },
|
|
|
|
|
ScreenshotUi,
|
2025-07-16 14:36:58 +03:00
|
|
|
ExitConfirmDialog,
|
2025-04-25 09:36:50 +03:00
|
|
|
Overview,
|
2024-03-17 09:21:03 +04:00
|
|
|
}
|
|
|
|
|
|
2024-06-17 09:16:28 +03:00
|
|
|
#[derive(Default, Clone, PartialEq)]
|
2024-11-03 08:50:17 +03:00
|
|
|
pub struct PointContents {
|
|
|
|
|
// Output under point.
|
2024-03-18 17:50:31 +04:00
|
|
|
pub output: Option<Output>,
|
2024-11-03 08:50:17 +03:00
|
|
|
// Surface under point and its location in the global coordinate space.
|
2025-02-10 08:03:39 +03:00
|
|
|
//
|
|
|
|
|
// Can be `None` even when `window` is set, for example when the pointer is over the niri
|
|
|
|
|
// border around the window.
|
2024-06-17 09:16:28 +03:00
|
|
|
pub surface: Option<(WlSurface, Point<f64, Logical>)>,
|
2024-03-18 17:50:31 +04:00
|
|
|
// If surface belongs to a window, this is that window.
|
2025-02-10 13:11:50 +03:00
|
|
|
pub window: Option<(Window, HitType)>,
|
2024-07-06 18:17:48 +04:00
|
|
|
// If surface belongs to a layer surface, this is that layer surface.
|
|
|
|
|
pub layer: Option<LayerSurface>,
|
2025-08-21 09:14:23 +03:00
|
|
|
// Pointer is over a hot corner.
|
|
|
|
|
pub hot_corner: bool,
|
2023-10-15 16:30:52 +04:00
|
|
|
}
|
|
|
|
|
|
2025-03-13 18:34:47 +03:00
|
|
|
#[derive(Debug, Default)]
|
2023-10-13 13:30:11 +04:00
|
|
|
pub enum LockState {
|
|
|
|
|
#[default]
|
|
|
|
|
Unlocked,
|
2025-03-13 18:34:47 +03:00
|
|
|
WaitingForSurfaces {
|
|
|
|
|
confirmation: SessionLocker,
|
|
|
|
|
deadline_token: RegistrationToken,
|
|
|
|
|
},
|
2023-10-13 13:30:11 +04:00
|
|
|
Locking(SessionLocker),
|
2024-02-17 07:47:06 +04:00
|
|
|
Locked(ExtSessionLockV1),
|
2023-10-13 13:30:11 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(PartialEq, Eq)]
|
|
|
|
|
pub enum LockRenderState {
|
|
|
|
|
/// The output displays a normal session frame.
|
|
|
|
|
Unlocked,
|
|
|
|
|
/// The output displays a locked frame.
|
|
|
|
|
Locked,
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-29 13:12:50 +04:00
|
|
|
// Not related to the one in Smithay.
|
|
|
|
|
//
|
|
|
|
|
// This state keeps track of when a surface last received a frame callback.
|
|
|
|
|
struct SurfaceFrameThrottlingState {
|
|
|
|
|
/// Output and sequence that the frame callback was last sent at.
|
|
|
|
|
last_sent_at: RefCell<Option<(Output, u32)>>,
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-26 18:47:46 +01:00
|
|
|
pub enum CenterCoords {
|
2024-06-09 10:50:22 +00:00
|
|
|
Separately,
|
2024-02-26 18:47:46 +01:00
|
|
|
Both,
|
2025-03-13 14:55:16 +01:00
|
|
|
// Force centering even if the cursor is already in the rectangle.
|
|
|
|
|
BothAlways,
|
2024-02-26 18:47:46 +01:00
|
|
|
}
|
|
|
|
|
|
2025-03-15 11:22:30 +03:00
|
|
|
#[derive(Clone, PartialEq, Eq)]
|
|
|
|
|
pub enum CastTarget {
|
2025-03-15 11:23:01 +03:00
|
|
|
// Dynamic cast before selecting anything.
|
|
|
|
|
Nothing,
|
2025-03-15 11:22:30 +03:00
|
|
|
Output(WeakOutput),
|
|
|
|
|
Window { id: u64 },
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-23 14:16:36 +04:00
|
|
|
impl RedrawState {
|
|
|
|
|
fn queue_redraw(self) -> Self {
|
|
|
|
|
match self {
|
|
|
|
|
RedrawState::Idle => RedrawState::Queued,
|
|
|
|
|
RedrawState::WaitingForEstimatedVBlank(token) => {
|
|
|
|
|
RedrawState::WaitingForEstimatedVBlankAndQueued(token)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A redraw is already queued.
|
|
|
|
|
value @ (RedrawState::Queued | RedrawState::WaitingForEstimatedVBlankAndQueued(_)) => {
|
|
|
|
|
value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We're waiting for VBlank, request a redraw afterwards.
|
|
|
|
|
RedrawState::WaitingForVBlank { .. } => RedrawState::WaitingForVBlank {
|
|
|
|
|
redraw_needed: true,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-29 13:12:50 +04:00
|
|
|
impl Default for SurfaceFrameThrottlingState {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
last_sent_at: RefCell::new(None),
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
|
|
|
|
|
2024-03-17 09:21:03 +04:00
|
|
|
impl KeyboardFocus {
|
|
|
|
|
pub fn surface(&self) -> Option<&WlSurface> {
|
|
|
|
|
match self {
|
|
|
|
|
KeyboardFocus::Layout { surface } => surface.as_ref(),
|
|
|
|
|
KeyboardFocus::LayerShell { surface } => Some(surface),
|
|
|
|
|
KeyboardFocus::LockScreen { surface } => surface.as_ref(),
|
|
|
|
|
KeyboardFocus::ScreenshotUi => None,
|
2025-07-16 14:36:58 +03:00
|
|
|
KeyboardFocus::ExitConfirmDialog => None,
|
2025-04-25 09:36:50 +03:00
|
|
|
KeyboardFocus::Overview => None,
|
2024-03-17 09:21:03 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn into_surface(self) -> Option<WlSurface> {
|
|
|
|
|
match self {
|
|
|
|
|
KeyboardFocus::Layout { surface } => surface,
|
|
|
|
|
KeyboardFocus::LayerShell { surface } => Some(surface),
|
|
|
|
|
KeyboardFocus::LockScreen { surface } => surface,
|
|
|
|
|
KeyboardFocus::ScreenshotUi => None,
|
2025-07-16 14:36:58 +03:00
|
|
|
KeyboardFocus::ExitConfirmDialog => None,
|
2025-04-25 09:36:50 +03:00
|
|
|
KeyboardFocus::Overview => None,
|
2024-03-17 09:21:03 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn is_layout(&self) -> bool {
|
|
|
|
|
matches!(self, KeyboardFocus::Layout { .. })
|
|
|
|
|
}
|
2025-04-25 09:36:50 +03:00
|
|
|
|
|
|
|
|
pub fn is_overview(&self) -> bool {
|
|
|
|
|
matches!(self, KeyboardFocus::Overview)
|
|
|
|
|
}
|
2024-03-17 09:21:03 +04:00
|
|
|
}
|
|
|
|
|
|
2023-09-03 14:10:02 +04:00
|
|
|
pub struct State {
|
2023-09-03 13:25:43 +04:00
|
|
|
pub backend: Backend,
|
|
|
|
|
pub niri: Niri,
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-03 14:10:02 +04:00
|
|
|
impl State {
|
|
|
|
|
pub fn new(
|
2023-09-05 12:58:51 +04:00
|
|
|
config: Config,
|
2023-09-24 17:30:06 +04:00
|
|
|
event_loop: LoopHandle<'static, State>,
|
2023-09-03 14:10:02 +04:00
|
|
|
stop_signal: LoopSignal,
|
2023-09-24 11:04:30 +04:00
|
|
|
display: Display<State>,
|
2024-12-20 08:49:18 +03:00
|
|
|
headless: bool,
|
2025-01-27 08:28:02 +03:00
|
|
|
create_wayland_socket: bool,
|
2025-07-14 07:34:10 -04:00
|
|
|
is_session_instance: bool,
|
2024-02-01 16:55:46 +04:00
|
|
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
2023-10-10 07:57:22 +04:00
|
|
|
let _span = tracy_client::span!("State::new");
|
|
|
|
|
|
2023-09-14 22:28:26 +04:00
|
|
|
let config = Rc::new(RefCell::new(config));
|
|
|
|
|
|
2025-01-12 22:33:20 +02:00
|
|
|
let has_display = env::var_os("WAYLAND_DISPLAY").is_some()
|
|
|
|
|
|| env::var_os("WAYLAND_SOCKET").is_some()
|
|
|
|
|
|| env::var_os("DISPLAY").is_some();
|
2023-09-03 13:25:43 +04:00
|
|
|
|
2024-12-20 08:49:18 +03:00
|
|
|
let mut backend = if headless {
|
|
|
|
|
let headless = Headless::new();
|
|
|
|
|
Backend::Headless(headless)
|
|
|
|
|
} else if has_display {
|
2024-02-01 16:55:46 +04:00
|
|
|
let winit = Winit::new(config.clone(), event_loop.clone())?;
|
|
|
|
|
Backend::Winit(winit)
|
2023-09-03 13:25:43 +04:00
|
|
|
} else {
|
2024-02-01 16:55:46 +04:00
|
|
|
let tty = Tty::new(config.clone(), event_loop.clone())
|
|
|
|
|
.context("error initializing the TTY backend")?;
|
|
|
|
|
Backend::Tty(tty)
|
2023-09-03 13:25:43 +04:00
|
|
|
};
|
|
|
|
|
|
2025-01-27 08:28:02 +03:00
|
|
|
let mut niri = Niri::new(
|
|
|
|
|
config.clone(),
|
|
|
|
|
event_loop,
|
|
|
|
|
stop_signal,
|
|
|
|
|
display,
|
|
|
|
|
&backend,
|
|
|
|
|
create_wayland_socket,
|
2025-07-14 07:34:10 -04:00
|
|
|
is_session_instance,
|
2025-01-27 08:28:02 +03:00
|
|
|
);
|
2023-09-03 13:25:43 +04:00
|
|
|
backend.init(&mut niri);
|
|
|
|
|
|
2024-06-20 12:04:10 +03:00
|
|
|
let mut state = Self { backend, niri };
|
|
|
|
|
|
2025-01-30 14:50:05 +01:00
|
|
|
// Load the xkb_file config option if set by the user.
|
|
|
|
|
state.load_xkb_file();
|
2024-06-20 12:04:10 +03:00
|
|
|
// Initialize some IPC server state.
|
|
|
|
|
state.ipc_keyboard_layouts_changed();
|
2025-03-29 10:13:59 +00:00
|
|
|
// Focus the default monitor if set by the user.
|
|
|
|
|
state.focus_default_monitor();
|
2024-06-20 12:04:10 +03:00
|
|
|
|
|
|
|
|
Ok(state)
|
2023-09-03 14:10:02 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-10 07:55:31 +04:00
|
|
|
pub fn refresh_and_flush_clients(&mut self) {
|
2024-03-23 14:46:29 +04:00
|
|
|
let _span = tracy_client::span!("State::refresh_and_flush_clients");
|
|
|
|
|
|
|
|
|
|
self.refresh();
|
|
|
|
|
|
2024-11-24 09:41:43 +03:00
|
|
|
// Advance animations to the current time (not target render time) before rendering outputs
|
|
|
|
|
// in order to clear completed animations and render elements. Even if we're not rendering,
|
|
|
|
|
// it's good to advance every now and then so the workspace clean-up and animations don't
|
|
|
|
|
// build up (the 1 second frame callback timer will call this line).
|
|
|
|
|
self.niri.advance_animations();
|
|
|
|
|
|
2024-03-23 14:46:29 +04:00
|
|
|
self.niri.redraw_queued_outputs(&mut self.backend);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
let _span = tracy_client::span!("flush_clients");
|
|
|
|
|
self.niri.display_handle.flush_clients().unwrap();
|
|
|
|
|
}
|
2024-11-24 09:41:43 +03:00
|
|
|
|
2025-07-14 07:34:10 -04:00
|
|
|
#[cfg(feature = "dbus")]
|
|
|
|
|
self.niri.update_locked_hint();
|
|
|
|
|
|
2024-11-24 09:41:43 +03:00
|
|
|
// Clear the time so it's fetched afresh next iteration.
|
|
|
|
|
self.niri.clock.clear();
|
2025-02-17 09:03:42 +03:00
|
|
|
self.niri.pointer_inactivity_timer_got_reset = false;
|
2025-02-17 09:09:48 +03:00
|
|
|
self.niri.notified_activity_this_iteration = false;
|
2024-03-23 14:46:29 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn refresh(&mut self) {
|
|
|
|
|
let _span = tracy_client::span!("State::refresh");
|
2023-10-10 07:55:31 +04:00
|
|
|
|
2024-08-22 14:44:11 +03:00
|
|
|
// Handle commits for surfaces whose blockers cleared this cycle. This should happen before
|
|
|
|
|
// layout.refresh() since this is where these surfaces handle commits.
|
|
|
|
|
self.notify_blocker_cleared();
|
|
|
|
|
|
2023-10-10 07:55:31 +04:00
|
|
|
// These should be called periodically, before flushing the clients.
|
2024-10-15 10:43:58 +03:00
|
|
|
self.niri.popups.cleanup();
|
|
|
|
|
self.refresh_popup_grab();
|
|
|
|
|
self.update_keyboard_focus();
|
|
|
|
|
|
2025-03-13 11:32:54 +03:00
|
|
|
// Should be called before refresh_layout() because that one will refresh other window
|
|
|
|
|
// states and then send a pending configure.
|
|
|
|
|
self.niri.refresh_window_states();
|
|
|
|
|
|
2024-10-15 10:43:58 +03:00
|
|
|
// Needs to be called after updating the keyboard focus.
|
|
|
|
|
self.niri.refresh_layout();
|
|
|
|
|
|
2023-10-29 14:04:38 +04:00
|
|
|
self.niri.cursor_manager.check_cursor_image_surface_alive();
|
2023-10-10 07:55:31 +04:00
|
|
|
self.niri.refresh_pointer_outputs();
|
2024-05-16 18:00:28 +04:00
|
|
|
self.niri.global_space.refresh();
|
2024-02-08 13:51:54 +04:00
|
|
|
self.niri.refresh_idle_inhibit();
|
2024-11-03 09:16:06 +03:00
|
|
|
self.refresh_pointer_contents();
|
2024-01-29 19:34:12 +04:00
|
|
|
foreign_toplevel::refresh(self);
|
2025-06-14 16:17:43 +03:00
|
|
|
ext_workspace::refresh(self);
|
2025-02-11 09:52:05 +03:00
|
|
|
|
|
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
|
|
|
|
self.niri.refresh_mapped_cast_outputs();
|
|
|
|
|
// Should happen before refresh_window_rules(), but after anything that can start or stop
|
|
|
|
|
// screencasts.
|
|
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
|
|
|
|
self.niri.refresh_mapped_cast_window_rules();
|
|
|
|
|
|
2024-03-23 14:38:07 +04:00
|
|
|
self.niri.refresh_window_rules();
|
2024-03-27 14:54:24 +04:00
|
|
|
self.refresh_ipc_outputs();
|
2024-06-20 12:04:10 +03:00
|
|
|
self.ipc_refresh_layout();
|
2024-09-04 20:54:11 +03:00
|
|
|
self.ipc_refresh_keyboard_layout_index();
|
2025-08-21 15:02:25 +03:00
|
|
|
|
|
|
|
|
// Needs to be called after updating the keyboard focus.
|
|
|
|
|
#[cfg(feature = "dbus")]
|
|
|
|
|
self.niri.refresh_a11y();
|
2023-10-10 07:55:31 +04:00
|
|
|
}
|
|
|
|
|
|
2024-08-22 14:44:11 +03:00
|
|
|
fn notify_blocker_cleared(&mut self) {
|
|
|
|
|
let dh = self.niri.display_handle.clone();
|
|
|
|
|
while let Ok(client) = self.niri.blocker_cleared_rx.try_recv() {
|
|
|
|
|
trace!("calling blocker_cleared");
|
|
|
|
|
self.client_compositor_state(&client)
|
|
|
|
|
.blocker_cleared(self, &dh);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-03 14:10:02 +04:00
|
|
|
pub fn move_cursor(&mut self, location: Point<f64, Logical>) {
|
2025-05-23 23:08:27 +03:00
|
|
|
let mut under = match self.niri.pointer_visibility {
|
|
|
|
|
PointerVisibility::Disabled => PointContents::default(),
|
|
|
|
|
_ => self.niri.contents_under(location),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Disable the hidden pointer if the contents underneath have changed.
|
|
|
|
|
if !self.niri.pointer_visibility.is_visible() && self.niri.pointer_contents != under {
|
|
|
|
|
self.niri.pointer_visibility = PointerVisibility::Disabled;
|
|
|
|
|
|
|
|
|
|
// When setting PointerVisibility::Hidden together with pointer contents changing,
|
|
|
|
|
// we can change straight to nothing to avoid one frame of hover. Notably, this can
|
|
|
|
|
// be triggered through warp-mouse-to-focus combined with hide-when-typing.
|
|
|
|
|
under = PointContents::default();
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-03 09:16:06 +03:00
|
|
|
self.niri.pointer_contents.clone_from(&under);
|
2023-10-15 16:30:52 +04:00
|
|
|
|
2023-09-24 11:04:30 +04:00
|
|
|
let pointer = &self.niri.seat.get_pointer().unwrap();
|
|
|
|
|
pointer.motion(
|
2023-09-03 14:10:02 +04:00
|
|
|
self,
|
2024-03-18 17:50:31 +04:00
|
|
|
under.surface,
|
2023-09-03 14:10:02 +04:00
|
|
|
&MotionEvent {
|
|
|
|
|
location,
|
|
|
|
|
serial: SERIAL_COUNTER.next_serial(),
|
|
|
|
|
time: get_monotonic_time().as_millis() as u32,
|
|
|
|
|
},
|
|
|
|
|
);
|
2023-09-24 11:04:30 +04:00
|
|
|
pointer.frame(self);
|
2024-04-02 08:44:29 +04:00
|
|
|
|
2024-11-03 09:32:58 +03:00
|
|
|
self.niri.maybe_activate_pointer_constraint();
|
|
|
|
|
|
2024-10-29 12:55:31 +03:00
|
|
|
// We do not show the pointer on programmatic or keyboard movement.
|
2024-04-02 08:44:29 +04:00
|
|
|
|
2023-09-03 14:10:02 +04:00
|
|
|
// FIXME: granular
|
|
|
|
|
self.niri.queue_redraw_all();
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-26 18:47:46 +01:00
|
|
|
/// Moves cursor within the specified rectangle, only adjusting coordinates if needed.
|
|
|
|
|
fn move_cursor_to_rect(&mut self, rect: Rectangle<f64, Logical>, mode: CenterCoords) -> bool {
|
|
|
|
|
let pointer = &self.niri.seat.get_pointer().unwrap();
|
|
|
|
|
let cur_loc = pointer.current_location();
|
|
|
|
|
let x_in_bound = cur_loc.x >= rect.loc.x && cur_loc.x <= rect.loc.x + rect.size.w;
|
|
|
|
|
let y_in_bound = cur_loc.y >= rect.loc.y && cur_loc.y <= rect.loc.y + rect.size.h;
|
|
|
|
|
|
|
|
|
|
let p = match mode {
|
2024-06-09 10:50:22 +00:00
|
|
|
CenterCoords::Separately => {
|
2024-02-26 18:47:46 +01:00
|
|
|
if x_in_bound && y_in_bound {
|
|
|
|
|
return false;
|
|
|
|
|
} else if y_in_bound {
|
|
|
|
|
// adjust x
|
|
|
|
|
Point::from((rect.loc.x + rect.size.w / 2.0, cur_loc.y))
|
|
|
|
|
} else if x_in_bound {
|
|
|
|
|
// adjust y
|
|
|
|
|
Point::from((cur_loc.x, rect.loc.y + rect.size.h / 2.0))
|
|
|
|
|
} else {
|
|
|
|
|
// adjust x and y
|
|
|
|
|
center_f64(rect)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
CenterCoords::Both => {
|
|
|
|
|
if x_in_bound && y_in_bound {
|
|
|
|
|
return false;
|
|
|
|
|
} else {
|
|
|
|
|
// adjust x and y
|
|
|
|
|
center_f64(rect)
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-13 14:55:16 +01:00
|
|
|
CenterCoords::BothAlways => center_f64(rect),
|
2024-02-26 18:47:46 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
self.move_cursor(p);
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn move_cursor_to_focused_tile(&mut self, mode: CenterCoords) -> bool {
|
2024-03-17 09:23:32 +04:00
|
|
|
if !self.niri.keyboard_focus.is_layout() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-17 09:52:42 +04:00
|
|
|
if self.niri.tablet_cursor_location.is_some() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-26 18:47:46 +01:00
|
|
|
let Some(output) = self.niri.layout.active_output() else {
|
|
|
|
|
return false;
|
|
|
|
|
};
|
2024-09-30 15:24:50 +03:00
|
|
|
let monitor = self.niri.layout.monitor_for_output(output).unwrap();
|
2024-02-26 18:47:46 +01:00
|
|
|
|
|
|
|
|
let mut rv = false;
|
|
|
|
|
let rect = monitor.active_tile_visual_rectangle();
|
|
|
|
|
|
|
|
|
|
if let Some(rect) = rect {
|
2024-09-30 15:24:50 +03:00
|
|
|
let output_geo = self.niri.global_space.output_geometry(output).unwrap();
|
2024-02-26 18:47:46 +01:00
|
|
|
let mut rect = rect;
|
2024-06-17 09:16:28 +03:00
|
|
|
rect.loc += output_geo.loc.to_f64();
|
|
|
|
|
rv = self.move_cursor_to_rect(rect, mode);
|
2024-02-26 18:47:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rv
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-29 10:13:59 +00:00
|
|
|
pub fn focus_default_monitor(&mut self) {
|
|
|
|
|
// Our default target is the first output in sorted order.
|
|
|
|
|
let Some(mut target) = self.niri.sorted_outputs.first().cloned() else {
|
|
|
|
|
// No outputs are connected.
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let config = self.niri.config.borrow();
|
|
|
|
|
for config in &config.outputs.0 {
|
|
|
|
|
if !config.focus_at_startup {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if let Some(output) = self.niri.output_by_name_match(&config.name) {
|
|
|
|
|
target = output.clone();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
drop(config);
|
|
|
|
|
|
|
|
|
|
self.niri.layout.focus_output(&target);
|
|
|
|
|
self.move_cursor_to_output(&target);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-21 12:48:51 +01:00
|
|
|
/// Focus a specific window, taking care of a potential active output change and cursor
|
|
|
|
|
/// warp.
|
|
|
|
|
pub fn focus_window(&mut self, window: &Window) {
|
|
|
|
|
let active_output = self.niri.layout.active_output().cloned();
|
|
|
|
|
|
|
|
|
|
self.niri.layout.activate_window(window);
|
|
|
|
|
|
|
|
|
|
let new_active = self.niri.layout.active_output().cloned();
|
|
|
|
|
#[allow(clippy::collapsible_if)]
|
|
|
|
|
if new_active != active_output {
|
|
|
|
|
if !self.maybe_warp_cursor_to_focus_centered() {
|
|
|
|
|
self.move_cursor_to_output(&new_active.unwrap());
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
self.maybe_warp_cursor_to_focus();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FIXME: granular
|
|
|
|
|
self.niri.queue_redraw_all();
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-26 18:47:46 +01:00
|
|
|
pub fn maybe_warp_cursor_to_focus(&mut self) -> bool {
|
2025-03-13 14:55:16 +01:00
|
|
|
let focused = match self.niri.config.borrow().input.warp_mouse_to_focus {
|
|
|
|
|
None => return false,
|
|
|
|
|
Some(inner) => match inner.mode {
|
|
|
|
|
None => CenterCoords::Separately,
|
|
|
|
|
Some(WarpMouseToFocusMode::CenterXy) => CenterCoords::Both,
|
|
|
|
|
Some(WarpMouseToFocusMode::CenterXyAlways) => CenterCoords::BothAlways,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
self.move_cursor_to_focused_tile(focused)
|
2024-02-26 18:47:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn maybe_warp_cursor_to_focus_centered(&mut self) -> bool {
|
2025-03-13 14:55:16 +01:00
|
|
|
let focused = match self.niri.config.borrow().input.warp_mouse_to_focus {
|
|
|
|
|
None => return false,
|
|
|
|
|
Some(inner) => match inner.mode {
|
|
|
|
|
None => CenterCoords::Both,
|
|
|
|
|
Some(WarpMouseToFocusMode::CenterXy) => CenterCoords::Both,
|
|
|
|
|
Some(WarpMouseToFocusMode::CenterXyAlways) => CenterCoords::BothAlways,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
self.move_cursor_to_focused_tile(focused)
|
2024-02-26 18:47:46 +01:00
|
|
|
}
|
|
|
|
|
|
2024-11-03 09:16:06 +03:00
|
|
|
pub fn refresh_pointer_contents(&mut self) {
|
|
|
|
|
let _span = tracy_client::span!("Niri::refresh_pointer_contents");
|
2023-10-15 16:30:52 +04:00
|
|
|
|
|
|
|
|
let pointer = &self.niri.seat.get_pointer().unwrap();
|
|
|
|
|
let location = pointer.current_location();
|
|
|
|
|
|
2025-07-16 14:36:58 +03:00
|
|
|
if !self.niri.exit_confirm_dialog.is_open()
|
|
|
|
|
&& !self.niri.is_locked()
|
|
|
|
|
&& !self.niri.screenshot_ui.is_open()
|
|
|
|
|
{
|
2023-10-30 20:19:51 +04:00
|
|
|
// Don't refresh cursor focus during transitions.
|
|
|
|
|
if let Some((output, _)) = self.niri.output_under(location) {
|
|
|
|
|
let monitor = self.niri.layout.monitor_for_output(output).unwrap();
|
|
|
|
|
if monitor.are_transitions_ongoing() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-10-15 18:58:36 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-03 09:16:06 +03:00
|
|
|
if !self.update_pointer_contents() {
|
2023-10-21 20:48:00 +04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pointer.frame(self);
|
2024-11-03 09:20:00 +03:00
|
|
|
|
|
|
|
|
// Pointer motion from a surface to nothing triggers a cursor change to default, which
|
|
|
|
|
// means we may need to redraw.
|
|
|
|
|
|
2023-10-21 20:48:00 +04:00
|
|
|
// FIXME: granular
|
|
|
|
|
self.niri.queue_redraw_all();
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-03 09:16:06 +03:00
|
|
|
pub fn update_pointer_contents(&mut self) -> bool {
|
|
|
|
|
let _span = tracy_client::span!("Niri::update_pointer_contents");
|
2023-10-21 20:48:00 +04:00
|
|
|
|
|
|
|
|
let pointer = &self.niri.seat.get_pointer().unwrap();
|
|
|
|
|
let location = pointer.current_location();
|
2025-05-23 22:59:04 +03:00
|
|
|
let mut under = match self.niri.pointer_visibility {
|
2025-04-27 06:25:36 +00:00
|
|
|
PointerVisibility::Disabled => PointContents::default(),
|
|
|
|
|
_ => self.niri.contents_under(location),
|
2024-04-02 08:44:29 +04:00
|
|
|
};
|
2023-10-15 16:30:52 +04:00
|
|
|
|
2024-11-03 09:16:06 +03:00
|
|
|
// We're not changing the global cursor location here, so if the contents did not change,
|
|
|
|
|
// then nothing changed.
|
|
|
|
|
if self.niri.pointer_contents == under {
|
2023-10-21 20:48:00 +04:00
|
|
|
return false;
|
2023-10-15 16:30:52 +04:00
|
|
|
}
|
|
|
|
|
|
2025-04-27 06:25:36 +00:00
|
|
|
// Disable the hidden pointer if the contents underneath have changed.
|
|
|
|
|
if !self.niri.pointer_visibility.is_visible() {
|
|
|
|
|
self.niri.pointer_visibility = PointerVisibility::Disabled;
|
2025-05-23 22:59:04 +03:00
|
|
|
|
|
|
|
|
// When setting PointerVisibility::Hidden together with pointer contents changing,
|
|
|
|
|
// we can change straight to nothing to avoid one frame of hover. Notably, this can
|
|
|
|
|
// be triggered through warp-mouse-to-focus combined with hide-when-typing.
|
|
|
|
|
under = PointContents::default();
|
|
|
|
|
if self.niri.pointer_contents == under {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2025-04-27 06:25:36 +00:00
|
|
|
}
|
|
|
|
|
|
2024-11-03 09:16:06 +03:00
|
|
|
self.niri.pointer_contents.clone_from(&under);
|
2023-10-15 16:30:52 +04:00
|
|
|
|
|
|
|
|
pointer.motion(
|
|
|
|
|
self,
|
2024-03-18 17:50:31 +04:00
|
|
|
under.surface,
|
2023-10-15 16:30:52 +04:00
|
|
|
&MotionEvent {
|
|
|
|
|
location,
|
|
|
|
|
serial: SERIAL_COUNTER.next_serial(),
|
|
|
|
|
time: get_monotonic_time().as_millis() as u32,
|
|
|
|
|
},
|
|
|
|
|
);
|
2023-10-21 20:48:00 +04:00
|
|
|
|
2024-11-03 09:32:58 +03:00
|
|
|
self.niri.maybe_activate_pointer_constraint();
|
|
|
|
|
|
2023-10-21 20:48:00 +04:00
|
|
|
true
|
2023-10-15 16:30:52 +04:00
|
|
|
}
|
|
|
|
|
|
2023-09-03 14:10:02 +04:00
|
|
|
pub fn move_cursor_to_output(&mut self, output: &Output) {
|
|
|
|
|
let geo = self.niri.global_space.output_geometry(output).unwrap();
|
|
|
|
|
self.move_cursor(center(geo).to_f64());
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-23 17:05:08 +04:00
|
|
|
pub fn refresh_popup_grab(&mut self) {
|
|
|
|
|
let keyboard_grabbed = self.niri.seat.input_method().keyboard_grabbed();
|
|
|
|
|
|
|
|
|
|
if let Some(grab) = &mut self.niri.popup_grab {
|
|
|
|
|
if grab.grab.has_ended() {
|
|
|
|
|
self.niri.popup_grab = None;
|
|
|
|
|
} else if keyboard_grabbed {
|
|
|
|
|
// HACK: remove popup grab if IME grabbed the keyboard, because we can't yet do
|
|
|
|
|
// popup grabs together with an IME grab.
|
|
|
|
|
// FIXME: do this properly.
|
|
|
|
|
grab.grab.ungrab(PopupUngrabStrategy::All);
|
|
|
|
|
self.niri.seat.get_pointer().unwrap().unset_grab(
|
|
|
|
|
self,
|
|
|
|
|
SERIAL_COUNTER.next_serial(),
|
|
|
|
|
get_monotonic_time().as_millis() as u32,
|
|
|
|
|
);
|
|
|
|
|
self.niri.popup_grab = None;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-12 16:53:00 +04:00
|
|
|
pub fn update_keyboard_focus(&mut self) {
|
2024-07-06 18:20:19 +04:00
|
|
|
// Clean up on-demand layer surface focus if necessary.
|
|
|
|
|
if let Some(surface) = &self.niri.layer_shell_on_demand_focus {
|
|
|
|
|
// Still alive and has on-demand interactivity.
|
2025-05-06 16:51:18 +03:00
|
|
|
let mut good = surface.alive()
|
2024-07-06 18:20:19 +04:00
|
|
|
&& surface.cached_state().keyboard_interactivity
|
|
|
|
|
== wlr_layer::KeyboardInteractivity::OnDemand;
|
2025-05-06 16:51:18 +03:00
|
|
|
|
|
|
|
|
if let Some(mapped) = self.niri.mapped_layer_surfaces.get(surface) {
|
2025-05-19 09:17:40 +03:00
|
|
|
// Check if it moved to the overview backdrop.
|
2025-05-06 16:51:18 +03:00
|
|
|
if mapped.place_within_backdrop() {
|
|
|
|
|
good = false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2025-05-19 09:17:40 +03:00
|
|
|
// The layer surface is alive but it got unmapped.
|
2025-05-06 16:51:18 +03:00
|
|
|
good = false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-06 18:20:19 +04:00
|
|
|
if !good {
|
|
|
|
|
self.niri.layer_shell_on_demand_focus = None;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compute the current focus.
|
2025-07-16 14:36:58 +03:00
|
|
|
let focus = if self.niri.exit_confirm_dialog.is_open() {
|
|
|
|
|
KeyboardFocus::ExitConfirmDialog
|
|
|
|
|
} else if self.niri.is_locked() {
|
2024-03-17 09:21:03 +04:00
|
|
|
KeyboardFocus::LockScreen {
|
|
|
|
|
surface: self.niri.lock_surface_focus(),
|
|
|
|
|
}
|
2023-10-30 20:29:03 +04:00
|
|
|
} else if self.niri.screenshot_ui.is_open() {
|
2024-03-17 09:21:03 +04:00
|
|
|
KeyboardFocus::ScreenshotUi
|
2023-11-17 18:30:24 +04:00
|
|
|
} else if let Some(output) = self.niri.layout.active_output() {
|
|
|
|
|
let mon = self.niri.layout.monitor_for_output(output).unwrap();
|
|
|
|
|
let layers = layer_map_for_output(output);
|
|
|
|
|
|
2024-01-12 17:53:35 +04:00
|
|
|
// Explicitly check for layer-shell popup grabs here, our keyboard focus will stay on
|
|
|
|
|
// the root layer surface while it has grabs.
|
|
|
|
|
let layer_grab = self.niri.popup_grab.as_ref().and_then(|g| {
|
|
|
|
|
layers
|
|
|
|
|
.layer_for_surface(&g.root, WindowSurfaceType::TOPLEVEL)
|
2024-11-24 20:12:39 +01:00
|
|
|
.and_then(|l| l.can_receive_keyboard_focus().then(|| (&g.root, l.layer())))
|
2024-01-12 17:53:35 +04:00
|
|
|
});
|
|
|
|
|
let grab_on_layer = |layer: Layer| {
|
2024-03-17 09:21:03 +04:00
|
|
|
layer_grab
|
|
|
|
|
.and_then(move |(s, l)| if l == layer { Some(s.clone()) } else { None })
|
|
|
|
|
.map(|surface| KeyboardFocus::LayerShell { surface })
|
2024-01-12 17:53:35 +04:00
|
|
|
};
|
|
|
|
|
|
2023-11-17 18:30:24 +04:00
|
|
|
let layout_focus = || {
|
2023-10-13 13:30:11 +04:00
|
|
|
self.niri
|
|
|
|
|
.layout
|
|
|
|
|
.focus()
|
2024-03-19 14:41:17 +04:00
|
|
|
.map(|win| win.toplevel().wl_surface().clone())
|
2024-03-17 09:21:03 +04:00
|
|
|
.map(|surface| KeyboardFocus::Layout {
|
|
|
|
|
surface: Some(surface),
|
|
|
|
|
})
|
2023-11-17 18:30:24 +04:00
|
|
|
};
|
2024-09-10 09:10:03 +03:00
|
|
|
|
|
|
|
|
let excl_focus_on_layer = |layer| {
|
|
|
|
|
layers.layers_on(layer).find_map(|surface| {
|
2025-05-06 16:51:18 +03:00
|
|
|
if surface.cached_state().keyboard_interactivity
|
|
|
|
|
!= wlr_layer::KeyboardInteractivity::Exclusive
|
|
|
|
|
{
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-06 16:51:18 +03:00
|
|
|
let mapped = self.niri.mapped_layer_surfaces.get(surface)?;
|
|
|
|
|
if mapped.place_within_backdrop() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-06 16:51:18 +03:00
|
|
|
let surface = surface.wl_surface().clone();
|
|
|
|
|
Some(KeyboardFocus::LayerShell { surface })
|
2024-09-10 09:10:03 +03:00
|
|
|
})
|
2024-07-06 18:20:19 +04:00
|
|
|
};
|
2024-09-10 09:10:03 +03:00
|
|
|
|
|
|
|
|
let on_d_focus_on_layer = |layer| {
|
|
|
|
|
layers.layers_on(layer).find_map(|surface| {
|
|
|
|
|
let is_on_demand_surface =
|
|
|
|
|
Some(surface) == self.niri.layer_shell_on_demand_focus.as_ref();
|
|
|
|
|
is_on_demand_surface
|
|
|
|
|
.then(|| surface.wl_surface().clone())
|
|
|
|
|
.map(|surface| KeyboardFocus::LayerShell { surface })
|
|
|
|
|
})
|
2023-11-17 18:30:24 +04:00
|
|
|
};
|
|
|
|
|
|
2024-09-10 09:10:03 +03:00
|
|
|
// Prefer exclusive focus on a layer, then check on-demand focus.
|
|
|
|
|
let focus_on_layer =
|
|
|
|
|
|layer| excl_focus_on_layer(layer).or_else(|| on_d_focus_on_layer(layer));
|
|
|
|
|
|
2025-04-25 09:36:50 +03:00
|
|
|
let is_overview_open = self.niri.layout.is_overview_open();
|
|
|
|
|
|
2024-01-12 17:53:35 +04:00
|
|
|
let mut surface = grab_on_layer(Layer::Overlay);
|
|
|
|
|
// FIXME: we shouldn't prioritize the top layer grabs over regular overlay input or a
|
|
|
|
|
// fullscreen layout window. This will need tracking in grab() to avoid handing it out
|
|
|
|
|
// in the first place. Or a better way to structure this code.
|
|
|
|
|
surface = surface.or_else(|| grab_on_layer(Layer::Top));
|
2025-04-25 09:36:50 +03:00
|
|
|
|
|
|
|
|
if !is_overview_open {
|
|
|
|
|
surface = surface.or_else(|| grab_on_layer(Layer::Bottom));
|
|
|
|
|
surface = surface.or_else(|| grab_on_layer(Layer::Background));
|
|
|
|
|
}
|
2024-01-12 17:53:35 +04:00
|
|
|
|
2024-09-10 09:10:03 +03:00
|
|
|
surface = surface.or_else(|| focus_on_layer(Layer::Overlay));
|
2024-01-12 17:53:35 +04:00
|
|
|
|
2023-11-17 18:30:24 +04:00
|
|
|
if mon.render_above_top_layer() {
|
|
|
|
|
surface = surface.or_else(layout_focus);
|
2024-09-10 09:10:03 +03:00
|
|
|
surface = surface.or_else(|| focus_on_layer(Layer::Top));
|
2025-01-03 16:41:27 +03:00
|
|
|
surface = surface.or_else(|| focus_on_layer(Layer::Bottom));
|
|
|
|
|
surface = surface.or_else(|| focus_on_layer(Layer::Background));
|
2023-11-17 18:30:24 +04:00
|
|
|
} else {
|
2024-09-10 09:10:03 +03:00
|
|
|
surface = surface.or_else(|| focus_on_layer(Layer::Top));
|
2025-04-25 09:36:50 +03:00
|
|
|
|
|
|
|
|
if is_overview_open {
|
|
|
|
|
surface = Some(surface.unwrap_or(KeyboardFocus::Overview));
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-03 16:41:27 +03:00
|
|
|
surface = surface.or_else(|| on_d_focus_on_layer(Layer::Bottom));
|
|
|
|
|
surface = surface.or_else(|| on_d_focus_on_layer(Layer::Background));
|
2023-11-17 18:30:24 +04:00
|
|
|
surface = surface.or_else(layout_focus);
|
|
|
|
|
|
2025-01-03 16:41:27 +03:00
|
|
|
// Bottom and background layers can only receive exclusive focus when there are no
|
|
|
|
|
// layout windows.
|
|
|
|
|
surface = surface.or_else(|| excl_focus_on_layer(Layer::Bottom));
|
|
|
|
|
surface = surface.or_else(|| excl_focus_on_layer(Layer::Background));
|
|
|
|
|
}
|
2024-07-06 18:20:19 +04:00
|
|
|
|
2024-03-17 09:21:03 +04:00
|
|
|
surface.unwrap_or(KeyboardFocus::Layout { surface: None })
|
2023-11-17 18:30:24 +04:00
|
|
|
} else {
|
2024-03-17 09:21:03 +04:00
|
|
|
KeyboardFocus::Layout { surface: None }
|
2023-10-13 13:30:11 +04:00
|
|
|
};
|
|
|
|
|
|
2023-09-03 14:10:02 +04:00
|
|
|
let keyboard = self.niri.seat.get_keyboard().unwrap();
|
2024-01-12 17:14:18 +04:00
|
|
|
if self.niri.keyboard_focus != focus {
|
2024-01-12 17:53:35 +04:00
|
|
|
trace!(
|
|
|
|
|
"keyboard focus changed from {:?} to {:?}",
|
|
|
|
|
self.niri.keyboard_focus,
|
|
|
|
|
focus
|
|
|
|
|
);
|
|
|
|
|
|
2024-03-23 16:16:52 +04:00
|
|
|
// Tell the windows their new focus state for window rule purposes.
|
2024-11-21 12:48:51 +01:00
|
|
|
let mut previous_focus = None;
|
2024-03-23 16:16:52 +04:00
|
|
|
if let KeyboardFocus::Layout {
|
|
|
|
|
surface: Some(surface),
|
|
|
|
|
} = &self.niri.keyboard_focus
|
|
|
|
|
{
|
|
|
|
|
if let Some((mapped, _)) = self.niri.layout.find_window_and_output_mut(surface) {
|
|
|
|
|
mapped.set_is_focused(false);
|
2024-11-21 12:48:51 +01:00
|
|
|
previous_focus = Some(mapped.window.clone());
|
2024-03-23 16:16:52 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if let KeyboardFocus::Layout {
|
|
|
|
|
surface: Some(surface),
|
|
|
|
|
} = &focus
|
|
|
|
|
{
|
|
|
|
|
if let Some((mapped, _)) = self.niri.layout.find_window_and_output_mut(surface) {
|
|
|
|
|
mapped.set_is_focused(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-21 12:48:51 +01:00
|
|
|
// Update the previous focus but only when staying focused on the layout.
|
|
|
|
|
//
|
|
|
|
|
// Case 1: opening and closing exclusive-keyboard layer-shell (e.g. app launcher). This
|
|
|
|
|
// involves going from Layout to LayerShell, then from LayerShell to Layout. The
|
|
|
|
|
// previously focused window should stay unchanged.
|
|
|
|
|
//
|
|
|
|
|
// Case 1.5: opening layer-shell, in the background switching layout focus, closing
|
|
|
|
|
// layer-shell. With the current logic, this won't update the previously focused
|
|
|
|
|
// window, which is incorrect. But this case should be rare.
|
|
|
|
|
//
|
|
|
|
|
// Case 2: switching to an empty workspace, then hitting FocusWindowPrevious. The focus
|
|
|
|
|
// should go to the window that was just focused. The keyboard focus goes from Layout
|
|
|
|
|
// (with Some surface) to Layout (with None surface), so we update the previously
|
|
|
|
|
// focused window.
|
|
|
|
|
//
|
|
|
|
|
// FIXME: Ideally this should happen inside Layout itself, then there wouldn't be any
|
2025-04-25 09:36:50 +03:00
|
|
|
// problems with layer-shell, etc. Or a similar problem now with the Overview where we
|
|
|
|
|
// don't update the previously focused window because the keyboard focus is on the
|
|
|
|
|
// Overview rather than on the Layout.
|
2024-11-21 12:48:51 +01:00
|
|
|
if matches!(self.niri.keyboard_focus, KeyboardFocus::Layout { .. })
|
|
|
|
|
&& matches!(focus, KeyboardFocus::Layout { .. })
|
|
|
|
|
{
|
|
|
|
|
self.niri.previously_focused_window = previous_focus;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-12 17:53:35 +04:00
|
|
|
if let Some(grab) = self.niri.popup_grab.as_mut() {
|
2024-11-24 20:12:39 +01:00
|
|
|
if grab.has_keyboard_grab && Some(&grab.root) != focus.surface() {
|
2024-01-12 17:53:35 +04:00
|
|
|
trace!(
|
|
|
|
|
"grab root {:?} is not the new focus {:?}, ungrabbing",
|
|
|
|
|
grab.root,
|
|
|
|
|
focus
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
grab.grab.ungrab(PopupUngrabStrategy::All);
|
2024-04-19 18:46:39 +04:00
|
|
|
keyboard.unset_grab(self);
|
2024-01-12 17:53:35 +04:00
|
|
|
self.niri.seat.get_pointer().unwrap().unset_grab(
|
|
|
|
|
self,
|
|
|
|
|
SERIAL_COUNTER.next_serial(),
|
|
|
|
|
get_monotonic_time().as_millis() as u32,
|
|
|
|
|
);
|
|
|
|
|
self.niri.popup_grab = None;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-02 00:10:22 +04:00
|
|
|
if self.niri.config.borrow().input.keyboard.track_layout == TrackLayout::Window {
|
2024-09-30 15:24:50 +03:00
|
|
|
let current_layout = keyboard.with_xkb_state(self, |context| {
|
|
|
|
|
let xkb = context.xkb().lock().unwrap();
|
|
|
|
|
xkb.active_layout()
|
|
|
|
|
});
|
2023-11-02 00:10:22 +04:00
|
|
|
|
|
|
|
|
let mut new_layout = current_layout;
|
|
|
|
|
// Store the currently active layout for the surface.
|
2024-03-17 09:21:03 +04:00
|
|
|
if let Some(current_focus) = self.niri.keyboard_focus.surface() {
|
2023-11-02 00:10:22 +04:00
|
|
|
with_states(current_focus, |data| {
|
|
|
|
|
let cell = data
|
|
|
|
|
.data_map
|
|
|
|
|
.get_or_insert::<Cell<KeyboardLayout>, _>(Cell::default);
|
|
|
|
|
cell.set(current_layout);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-17 09:21:03 +04:00
|
|
|
if let Some(focus) = focus.surface() {
|
2023-11-02 00:10:22 +04:00
|
|
|
new_layout = with_states(focus, |data| {
|
|
|
|
|
let cell = data.data_map.get_or_insert::<Cell<KeyboardLayout>, _>(|| {
|
|
|
|
|
// The default layout is effectively the first layout in the
|
|
|
|
|
// keymap, so use it for new windows.
|
|
|
|
|
Cell::new(KeyboardLayout::default())
|
|
|
|
|
});
|
|
|
|
|
cell.get()
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-03-17 09:21:03 +04:00
|
|
|
if new_layout != current_layout && focus.surface().is_some() {
|
2023-11-02 00:10:22 +04:00
|
|
|
keyboard.set_focus(self, None, SERIAL_COUNTER.next_serial());
|
2023-11-24 11:49:07 +04:00
|
|
|
keyboard.with_xkb_state(self, |mut context| {
|
2023-11-02 00:10:22 +04:00
|
|
|
context.set_layout(new_layout);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-12 09:07:59 +04:00
|
|
|
self.niri.keyboard_focus.clone_from(&focus);
|
2024-03-17 09:21:03 +04:00
|
|
|
keyboard.set_focus(self, focus.into_surface(), SERIAL_COUNTER.next_serial());
|
2023-11-02 00:10:22 +04:00
|
|
|
|
2023-09-03 14:10:02 +04:00
|
|
|
// FIXME: can be more granular.
|
|
|
|
|
self.niri.queue_redraw_all();
|
2023-09-03 13:25:43 +04:00
|
|
|
}
|
|
|
|
|
}
|
2023-09-26 19:24:50 +04:00
|
|
|
|
2025-01-30 14:50:05 +01:00
|
|
|
/// Loads the xkb keymap from a file config setting.
|
|
|
|
|
fn set_xkb_file(&mut self, xkb_file: String) -> anyhow::Result<()> {
|
|
|
|
|
let xkb_file = PathBuf::from(xkb_file);
|
|
|
|
|
let xkb_file = expand_home(&xkb_file)
|
|
|
|
|
.context("failed to expand ~")?
|
|
|
|
|
.unwrap_or(xkb_file);
|
|
|
|
|
|
|
|
|
|
let keymap = std::fs::read_to_string(xkb_file).context("failed to read xkb_file")?;
|
|
|
|
|
|
|
|
|
|
let xkb = self.niri.seat.get_keyboard().unwrap();
|
|
|
|
|
xkb.set_keymap_from_string(self, keymap)
|
|
|
|
|
.context("failed to set keymap")?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn load_xkb_file(&mut self) {
|
|
|
|
|
let xkb_file = self.niri.config.borrow().input.keyboard.xkb.file.clone();
|
|
|
|
|
if let Some(xkb_file) = xkb_file {
|
|
|
|
|
if let Err(err) = self.set_xkb_file(xkb_file) {
|
|
|
|
|
warn!("error loading xkb_file: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 21:07:29 +03:00
|
|
|
fn set_xkb_config(&mut self, xkb: XkbConfig) {
|
|
|
|
|
let keyboard = self.niri.seat.get_keyboard().unwrap();
|
|
|
|
|
let num_lock = keyboard.modifier_state().num_lock;
|
|
|
|
|
if let Err(err) = keyboard.set_xkb_config(self, xkb) {
|
|
|
|
|
warn!("error updating xkb config: {err:?}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Restore num lock to its previous value.
|
|
|
|
|
let mut mods_state = keyboard.modifier_state();
|
|
|
|
|
if mods_state.num_lock != num_lock {
|
|
|
|
|
mods_state.num_lock = num_lock;
|
|
|
|
|
keyboard.set_modifier_state(mods_state);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-12 20:53:19 +03:00
|
|
|
pub fn reload_config(&mut self, config: Result<Config, ()>) {
|
2023-09-26 19:24:50 +04:00
|
|
|
let _span = tracy_client::span!("State::reload_config");
|
|
|
|
|
|
2025-02-12 20:53:19 +03:00
|
|
|
let mut config = match config {
|
2024-01-18 11:02:15 +04:00
|
|
|
Ok(config) => config,
|
2025-02-12 20:53:19 +03:00
|
|
|
Err(()) => {
|
2024-01-18 11:15:48 +04:00
|
|
|
self.niri.config_error_notification.show();
|
|
|
|
|
self.niri.queue_redraw_all();
|
2025-08-21 15:02:25 +03:00
|
|
|
|
|
|
|
|
#[cfg(feature = "dbus")]
|
|
|
|
|
self.niri.a11y_announce_config_error();
|
|
|
|
|
|
2023-09-26 19:24:50 +04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-01-18 11:15:48 +04:00
|
|
|
self.niri.config_error_notification.hide();
|
|
|
|
|
|
2024-05-11 22:40:30 +02:00
|
|
|
// Find & orphan removed named workspaces.
|
|
|
|
|
let mut removed_workspaces: Vec<String> = vec![];
|
|
|
|
|
for ws in &self.niri.config.borrow().workspaces {
|
|
|
|
|
if !config.workspaces.iter().any(|w| w.name == ws.name) {
|
|
|
|
|
removed_workspaces.push(ws.name.0.clone());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for name in removed_workspaces {
|
|
|
|
|
self.niri.layout.unname_workspace(&name);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
self.niri.layout.update_config(&config);
|
2025-01-21 09:40:00 +03:00
|
|
|
for mapped in self.niri.mapped_layer_surfaces.values_mut() {
|
|
|
|
|
mapped.update_config(&config);
|
|
|
|
|
}
|
2024-02-07 17:05:15 +04:00
|
|
|
|
2024-05-11 22:40:30 +02:00
|
|
|
// Create new named workspaces.
|
|
|
|
|
for ws_config in &config.workspaces {
|
|
|
|
|
self.niri.layout.ensure_named_workspace(ws_config);
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-09 14:02:17 +03:00
|
|
|
let rate = 1.0 / config.animations.slowdown.0.max(0.001);
|
2024-11-24 09:41:43 +03:00
|
|
|
self.niri.clock.set_rate(rate);
|
|
|
|
|
self.niri
|
|
|
|
|
.clock
|
|
|
|
|
.set_complete_instantly(config.animations.off);
|
2023-10-05 09:25:07 +04:00
|
|
|
|
2024-02-24 10:08:56 +04:00
|
|
|
*CHILD_ENV.write().unwrap() = mem::take(&mut config.environment);
|
|
|
|
|
|
2023-12-08 07:58:03 +04:00
|
|
|
let mut reload_xkb = None;
|
2024-01-16 20:28:46 +04:00
|
|
|
let mut libinput_config_changed = false;
|
2024-01-16 08:44:04 +04:00
|
|
|
let mut output_config_changed = false;
|
2024-05-05 10:19:47 +04:00
|
|
|
let mut preserved_output_config = None;
|
2024-03-19 15:20:03 +04:00
|
|
|
let mut window_rules_changed = false;
|
2024-11-14 11:33:08 +03:00
|
|
|
let mut layer_rules_changed = false;
|
2024-05-03 21:21:58 +04:00
|
|
|
let mut shaders_changed = false;
|
2024-10-06 20:27:04 +03:00
|
|
|
let mut cursor_inactivity_timeout_changed = false;
|
2025-06-04 08:26:51 +03:00
|
|
|
let mut xwls_changed = false;
|
2023-10-01 17:42:56 +04:00
|
|
|
let mut old_config = self.niri.config.borrow_mut();
|
|
|
|
|
|
2023-12-08 07:58:03 +04:00
|
|
|
// Reload the cursor.
|
2023-10-01 17:42:56 +04:00
|
|
|
if config.cursor != old_config.cursor {
|
2023-10-29 14:04:38 +04:00
|
|
|
self.niri
|
|
|
|
|
.cursor_manager
|
|
|
|
|
.reload(&config.cursor.xcursor_theme, config.cursor.xcursor_size);
|
|
|
|
|
self.niri.cursor_texture_cache.clear();
|
2023-10-01 17:42:56 +04:00
|
|
|
}
|
|
|
|
|
|
2023-12-08 07:58:03 +04:00
|
|
|
// We need &mut self to reload the xkb config, so just store it here.
|
|
|
|
|
if config.input.keyboard.xkb != old_config.input.keyboard.xkb {
|
|
|
|
|
reload_xkb = Some(config.input.keyboard.xkb.clone());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reload the repeat info.
|
|
|
|
|
if config.input.keyboard.repeat_rate != old_config.input.keyboard.repeat_rate
|
|
|
|
|
|| config.input.keyboard.repeat_delay != old_config.input.keyboard.repeat_delay
|
|
|
|
|
{
|
|
|
|
|
let keyboard = self.niri.seat.get_keyboard().unwrap();
|
|
|
|
|
keyboard.change_repeat_info(
|
|
|
|
|
config.input.keyboard.repeat_rate.into(),
|
|
|
|
|
config.input.keyboard.repeat_delay.into(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-16 20:28:46 +04:00
|
|
|
if config.input.touchpad != old_config.input.touchpad
|
|
|
|
|
|| config.input.mouse != old_config.input.mouse
|
2025-08-07 17:14:37 +03:00
|
|
|
|| config.input.trackball != old_config.input.trackball
|
2024-02-14 16:24:46 +01:00
|
|
|
|| config.input.trackpoint != old_config.input.trackpoint
|
2025-08-07 17:14:37 +03:00
|
|
|
|| config.input.tablet != old_config.input.tablet
|
|
|
|
|
|| config.input.touch != old_config.input.touch
|
2024-01-16 20:28:46 +04:00
|
|
|
{
|
|
|
|
|
libinput_config_changed = true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-05 10:19:47 +04:00
|
|
|
if config.outputs != self.niri.config_file_output_config {
|
2024-01-16 08:44:04 +04:00
|
|
|
output_config_changed = true;
|
2024-05-05 10:19:47 +04:00
|
|
|
self.niri
|
|
|
|
|
.config_file_output_config
|
|
|
|
|
.clone_from(&config.outputs);
|
|
|
|
|
} else {
|
|
|
|
|
// Output config did not change from the last disk load, so we need to preserve the
|
|
|
|
|
// transient changes.
|
|
|
|
|
preserved_output_config = Some(mem::take(&mut old_config.outputs));
|
2024-01-16 08:44:04 +04:00
|
|
|
}
|
|
|
|
|
|
2025-02-05 09:34:25 -05:00
|
|
|
let new_mod_key = self.backend.mod_key(&config);
|
|
|
|
|
if new_mod_key != self.backend.mod_key(&old_config) || config.binds != old_config.binds {
|
|
|
|
|
self.niri
|
|
|
|
|
.hotkey_overlay
|
|
|
|
|
.on_hotkey_config_updated(new_mod_key);
|
|
|
|
|
self.niri.mods_with_mouse_binds = mods_with_mouse_binds(new_mod_key, &config.binds);
|
|
|
|
|
self.niri.mods_with_wheel_binds = mods_with_wheel_binds(new_mod_key, &config.binds);
|
2024-03-23 20:23:21 +04:00
|
|
|
self.niri.mods_with_finger_scroll_binds =
|
2025-02-05 09:34:25 -05:00
|
|
|
mods_with_finger_scroll_binds(new_mod_key, &config.binds);
|
2024-01-18 19:20:46 +04:00
|
|
|
}
|
|
|
|
|
|
2024-03-19 15:20:03 +04:00
|
|
|
if config.window_rules != old_config.window_rules {
|
|
|
|
|
window_rules_changed = true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-14 11:33:08 +03:00
|
|
|
if config.layer_rules != old_config.layer_rules {
|
|
|
|
|
layer_rules_changed = true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-21 20:10:35 +04:00
|
|
|
if config.animations.window_resize.custom_shader
|
|
|
|
|
!= old_config.animations.window_resize.custom_shader
|
|
|
|
|
{
|
|
|
|
|
let src = config.animations.window_resize.custom_shader.as_deref();
|
|
|
|
|
self.backend.with_primary_renderer(|renderer| {
|
|
|
|
|
shaders::set_custom_resize_program(renderer, src);
|
|
|
|
|
});
|
2024-05-03 21:21:58 +04:00
|
|
|
shaders_changed = true;
|
2024-04-21 20:10:35 +04:00
|
|
|
}
|
|
|
|
|
|
2024-05-12 09:52:21 +04:00
|
|
|
if config.animations.window_close.custom_shader
|
|
|
|
|
!= old_config.animations.window_close.custom_shader
|
|
|
|
|
{
|
|
|
|
|
let src = config.animations.window_close.custom_shader.as_deref();
|
|
|
|
|
self.backend.with_primary_renderer(|renderer| {
|
|
|
|
|
shaders::set_custom_close_program(renderer, src);
|
|
|
|
|
});
|
|
|
|
|
shaders_changed = true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-15 19:38:29 +04:00
|
|
|
if config.animations.window_open.custom_shader
|
|
|
|
|
!= old_config.animations.window_open.custom_shader
|
|
|
|
|
{
|
|
|
|
|
let src = config.animations.window_open.custom_shader.as_deref();
|
|
|
|
|
self.backend.with_primary_renderer(|renderer| {
|
|
|
|
|
shaders::set_custom_open_program(renderer, src);
|
|
|
|
|
});
|
|
|
|
|
shaders_changed = true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-06 20:27:04 +03:00
|
|
|
if config.cursor.hide_after_inactive_ms != old_config.cursor.hide_after_inactive_ms {
|
|
|
|
|
cursor_inactivity_timeout_changed = true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-04 09:09:24 +03:00
|
|
|
if config.debug.keep_laptop_panel_on_when_lid_is_closed
|
|
|
|
|
!= old_config.debug.keep_laptop_panel_on_when_lid_is_closed
|
|
|
|
|
{
|
|
|
|
|
output_config_changed = true;
|
2024-04-25 22:10:52 +04:00
|
|
|
}
|
|
|
|
|
|
2025-04-28 07:53:03 +03:00
|
|
|
// FIXME: move backdrop rendering into layout::Monitor, then this will become unnecessary.
|
|
|
|
|
if config.overview.backdrop_color != old_config.overview.backdrop_color {
|
|
|
|
|
output_config_changed = true;
|
|
|
|
|
}
|
2025-05-06 17:12:07 +03:00
|
|
|
if config.layout.background_color != old_config.layout.background_color {
|
|
|
|
|
output_config_changed = true;
|
|
|
|
|
}
|
2025-04-28 07:53:03 +03:00
|
|
|
|
2025-06-04 08:26:51 +03:00
|
|
|
if config.xwayland_satellite != old_config.xwayland_satellite {
|
|
|
|
|
xwls_changed = true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-01 17:42:56 +04:00
|
|
|
*old_config = config;
|
|
|
|
|
|
2024-05-05 10:19:47 +04:00
|
|
|
if let Some(outputs) = preserved_output_config {
|
|
|
|
|
old_config.outputs = outputs;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-01 17:42:56 +04:00
|
|
|
// Release the borrow.
|
|
|
|
|
drop(old_config);
|
|
|
|
|
|
2023-12-08 07:58:03 +04:00
|
|
|
// Now with a &mut self we can reload the xkb config.
|
2025-01-30 14:50:05 +01:00
|
|
|
if let Some(mut xkb) = reload_xkb {
|
|
|
|
|
let mut set_xkb_config = true;
|
|
|
|
|
|
|
|
|
|
// It's fine to .take() the xkb file, as this is a
|
|
|
|
|
// clone and the file field is not used in the XkbConfig.
|
|
|
|
|
if let Some(xkb_file) = xkb.file.take() {
|
|
|
|
|
if let Err(err) = self.set_xkb_file(xkb_file) {
|
|
|
|
|
warn!("error reloading xkb_file: {err:?}");
|
|
|
|
|
} else {
|
|
|
|
|
// We successfully set xkb file so we don't need to fallback to XkbConfig.
|
|
|
|
|
set_xkb_config = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if set_xkb_config {
|
2025-07-15 15:44:10 +03:00
|
|
|
// If xkb is unset in the niri config, use settings from locale1.
|
|
|
|
|
if xkb == Xkb::default() {
|
|
|
|
|
trace!("using xkb from locale1");
|
|
|
|
|
xkb = self.niri.xkb_from_locale1.clone().unwrap_or_default();
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 21:07:29 +03:00
|
|
|
self.set_xkb_config(xkb.to_xkb_config());
|
2023-12-08 07:58:03 +04:00
|
|
|
}
|
2024-06-20 12:04:10 +03:00
|
|
|
|
|
|
|
|
self.ipc_keyboard_layouts_changed();
|
2023-12-08 07:58:03 +04:00
|
|
|
}
|
|
|
|
|
|
2024-01-16 20:28:46 +04:00
|
|
|
if libinput_config_changed {
|
|
|
|
|
let config = self.niri.config.borrow();
|
|
|
|
|
for mut device in self.niri.devices.iter().cloned() {
|
|
|
|
|
apply_libinput_settings(&config.input, &mut device);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-16 08:44:04 +04:00
|
|
|
if output_config_changed {
|
2024-05-05 10:19:47 +04:00
|
|
|
self.reload_output_config();
|
2024-01-16 08:44:04 +04:00
|
|
|
}
|
|
|
|
|
|
2024-03-19 15:20:03 +04:00
|
|
|
if window_rules_changed {
|
2024-05-16 11:43:13 +04:00
|
|
|
self.niri.recompute_window_rules();
|
2024-03-19 15:20:03 +04:00
|
|
|
}
|
|
|
|
|
|
2024-11-14 11:33:08 +03:00
|
|
|
if layer_rules_changed {
|
|
|
|
|
self.niri.recompute_layer_rules();
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-03 21:21:58 +04:00
|
|
|
if shaders_changed {
|
2025-01-21 09:40:00 +03:00
|
|
|
self.niri.update_shaders();
|
2024-05-03 21:21:58 +04:00
|
|
|
}
|
|
|
|
|
|
2024-10-06 20:27:04 +03:00
|
|
|
if cursor_inactivity_timeout_changed {
|
2025-02-17 09:03:42 +03:00
|
|
|
// Force reset due to timeout change.
|
|
|
|
|
self.niri.pointer_inactivity_timer_got_reset = false;
|
2024-10-06 20:27:04 +03:00
|
|
|
self.niri.reset_pointer_inactivity_timer();
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-04 08:26:51 +03:00
|
|
|
if xwls_changed {
|
|
|
|
|
// If xwl-s was previously working and is now off, we don't try to kill it or stop
|
|
|
|
|
// watching the sockets, for simplicity's sake.
|
|
|
|
|
let was_working = self.niri.satellite.is_some();
|
|
|
|
|
|
|
|
|
|
// Try to start, or restart in case the user corrected the path or something.
|
|
|
|
|
xwayland::satellite::setup(self);
|
|
|
|
|
|
|
|
|
|
let config = self.niri.config.borrow();
|
|
|
|
|
let display_name = (!config.xwayland_satellite.off)
|
|
|
|
|
.then_some(self.niri.satellite.as_ref())
|
|
|
|
|
.flatten()
|
|
|
|
|
.map(|satellite| satellite.display_name().to_owned());
|
|
|
|
|
|
|
|
|
|
if let Some(name) = &display_name {
|
|
|
|
|
if !was_working {
|
|
|
|
|
info!("listening on X11 socket: {name}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This won't change the systemd environment, but oh well.
|
|
|
|
|
*CHILD_DISPLAY.write().unwrap() = display_name;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-16 20:42:39 +04:00
|
|
|
// Can't really update xdg-decoration settings since we have to hide the globals for CSD
|
|
|
|
|
// due to the SDL2 bug... I don't imagine clients are prepared for the xdg-decoration
|
|
|
|
|
// global suddenly appearing? Either way, right now it's live-reloaded in a sense that new
|
|
|
|
|
// clients will use the new xdg-decoration setting.
|
|
|
|
|
|
2023-09-26 19:24:50 +04:00
|
|
|
self.niri.queue_redraw_all();
|
|
|
|
|
}
|
2023-10-10 08:49:47 +04:00
|
|
|
|
2024-05-21 22:43:42 +00:00
|
|
|
pub fn reload_output_config(&mut self) {
|
2024-05-05 10:19:47 +04:00
|
|
|
let mut resized_outputs = vec![];
|
2024-07-25 23:41:33 +05:30
|
|
|
let mut recolored_outputs = vec![];
|
|
|
|
|
|
2024-05-05 10:19:47 +04:00
|
|
|
for output in self.niri.global_space.outputs() {
|
2024-09-03 12:13:04 +03:00
|
|
|
let name = output.user_data().get::<OutputName>().unwrap();
|
2025-04-28 07:53:03 +03:00
|
|
|
let full_config = self.niri.config.borrow_mut();
|
|
|
|
|
let config = full_config.outputs.find(name);
|
2024-05-05 10:19:47 +04:00
|
|
|
|
2024-06-18 11:01:18 +03:00
|
|
|
let scale = config
|
|
|
|
|
.and_then(|c| c.scale)
|
|
|
|
|
.map(|s| s.0)
|
|
|
|
|
.unwrap_or_else(|| {
|
|
|
|
|
let size_mm = output.physical_properties().size;
|
|
|
|
|
let resolution = output.current_mode().unwrap().size;
|
|
|
|
|
guess_monitor_scale(size_mm, resolution)
|
|
|
|
|
});
|
2024-06-17 09:21:37 +03:00
|
|
|
let scale = closest_representable_scale(scale.clamp(0.1, 10.));
|
2024-05-05 10:19:47 +04:00
|
|
|
|
|
|
|
|
let mut transform = config
|
|
|
|
|
.map(|c| ipc_transform_to_smithay(c.transform))
|
|
|
|
|
.unwrap_or(Transform::Normal);
|
|
|
|
|
// FIXME: fix winit damage on other transforms.
|
2024-09-03 12:13:04 +03:00
|
|
|
if name.connector == "winit" {
|
2024-05-05 10:19:47 +04:00
|
|
|
transform = Transform::Flipped180;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-29 14:29:37 +03:00
|
|
|
if output.current_scale().fractional_scale() != scale
|
2024-05-05 10:19:47 +04:00
|
|
|
|| output.current_transform() != transform
|
|
|
|
|
{
|
|
|
|
|
output.change_current_state(
|
|
|
|
|
None,
|
|
|
|
|
Some(transform),
|
2024-05-29 14:29:37 +03:00
|
|
|
Some(output::Scale::Fractional(scale)),
|
2024-05-05 10:19:47 +04:00
|
|
|
None,
|
|
|
|
|
);
|
|
|
|
|
self.niri.ipc_outputs_changed = true;
|
|
|
|
|
resized_outputs.push(output.clone());
|
|
|
|
|
}
|
2024-07-25 23:41:33 +05:30
|
|
|
|
2025-05-06 16:51:18 +03:00
|
|
|
let background_color = config
|
2025-05-06 17:12:07 +03:00
|
|
|
.and_then(|c| c.background_color)
|
|
|
|
|
.unwrap_or(full_config.layout.background_color)
|
2024-07-25 23:41:33 +05:30
|
|
|
.to_array_unpremul();
|
2024-08-20 11:43:32 +03:00
|
|
|
let background_color = Color32F::from(background_color);
|
2024-07-25 23:41:33 +05:30
|
|
|
|
2025-04-25 08:53:16 +03:00
|
|
|
let mut backdrop_color = config
|
2025-04-28 07:53:03 +03:00
|
|
|
.and_then(|c| c.backdrop_color)
|
|
|
|
|
.unwrap_or(full_config.overview.backdrop_color)
|
2025-04-25 08:53:16 +03:00
|
|
|
.to_array_unpremul();
|
|
|
|
|
backdrop_color[3] = 1.;
|
|
|
|
|
let backdrop_color = Color32F::from(backdrop_color);
|
|
|
|
|
|
2024-07-25 23:41:33 +05:30
|
|
|
if let Some(state) = self.niri.output_state.get_mut(output) {
|
|
|
|
|
if state.background_buffer.color() != background_color {
|
|
|
|
|
state.background_buffer.set_color(background_color);
|
|
|
|
|
recolored_outputs.push(output.clone());
|
|
|
|
|
}
|
2025-04-25 08:53:16 +03:00
|
|
|
if state.backdrop_buffer.color() != backdrop_color {
|
|
|
|
|
state.backdrop_buffer.set_color(backdrop_color);
|
|
|
|
|
recolored_outputs.push(output.clone());
|
|
|
|
|
}
|
2024-07-25 23:41:33 +05:30
|
|
|
}
|
2024-05-05 10:19:47 +04:00
|
|
|
}
|
2024-07-25 23:41:33 +05:30
|
|
|
|
2024-05-05 10:19:47 +04:00
|
|
|
for output in resized_outputs {
|
|
|
|
|
self.niri.output_resized(&output);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 23:41:33 +05:30
|
|
|
for output in recolored_outputs {
|
|
|
|
|
self.niri.queue_redraw(&output);
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-05 10:19:47 +04:00
|
|
|
self.backend.on_output_config_changed(&mut self.niri);
|
|
|
|
|
|
|
|
|
|
self.niri.reposition_outputs(None);
|
|
|
|
|
|
|
|
|
|
if let Some(touch) = self.niri.seat.get_touch() {
|
|
|
|
|
touch.cancel(self);
|
|
|
|
|
}
|
2024-05-21 22:43:42 +00:00
|
|
|
|
|
|
|
|
let config = self.niri.config.borrow().outputs.clone();
|
|
|
|
|
self.niri.output_management_state.on_config_changed(config);
|
2024-05-05 10:19:47 +04:00
|
|
|
}
|
|
|
|
|
|
2025-01-01 03:07:47 -03:00
|
|
|
pub fn modify_output_config<F>(&mut self, name: &str, fun: F)
|
|
|
|
|
where
|
|
|
|
|
F: FnOnce(&mut niri_config::Output),
|
|
|
|
|
{
|
|
|
|
|
// Try hard to find the output config section corresponding to the output set by the
|
|
|
|
|
// user. Since if we add a new section and some existing section also matches the
|
|
|
|
|
// output, then our new section won't do anything.
|
|
|
|
|
let temp;
|
|
|
|
|
let match_name = if let Some(output) = self.niri.output_by_name_match(name) {
|
|
|
|
|
output.user_data().get::<OutputName>().unwrap()
|
|
|
|
|
} else if let Some(output_name) = self
|
|
|
|
|
.backend
|
|
|
|
|
.tty_checked()
|
|
|
|
|
.and_then(|tty| tty.disconnected_connector_name_by_name_match(name))
|
2024-05-05 10:19:47 +04:00
|
|
|
{
|
2025-01-01 03:07:47 -03:00
|
|
|
temp = output_name;
|
|
|
|
|
&temp
|
|
|
|
|
} else {
|
|
|
|
|
// Even if name is "make model serial", matching will work fine this way.
|
|
|
|
|
temp = OutputName {
|
|
|
|
|
connector: name.to_owned(),
|
|
|
|
|
make: None,
|
|
|
|
|
model: None,
|
|
|
|
|
serial: None,
|
2024-09-03 12:13:04 +03:00
|
|
|
};
|
2025-01-01 03:07:47 -03:00
|
|
|
&temp
|
|
|
|
|
};
|
2024-09-03 12:13:04 +03:00
|
|
|
|
2025-01-01 03:07:47 -03:00
|
|
|
let mut config = self.niri.config.borrow_mut();
|
|
|
|
|
let config = if let Some(config) = config.outputs.find_mut(match_name) {
|
|
|
|
|
config
|
|
|
|
|
} else {
|
|
|
|
|
config.outputs.0.push(niri_config::Output {
|
|
|
|
|
// Save name as set by the user.
|
|
|
|
|
name: String::from(name),
|
|
|
|
|
..Default::default()
|
|
|
|
|
});
|
|
|
|
|
config.outputs.0.last_mut().unwrap()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fun(config);
|
|
|
|
|
}
|
2024-05-05 10:19:47 +04:00
|
|
|
|
2025-01-01 03:07:47 -03:00
|
|
|
pub fn apply_transient_output_config(&mut self, name: &str, action: niri_ipc::OutputAction) {
|
|
|
|
|
self.modify_output_config(name, move |config| match action {
|
|
|
|
|
niri_ipc::OutputAction::Off => config.off = true,
|
|
|
|
|
niri_ipc::OutputAction::On => config.off = false,
|
|
|
|
|
niri_ipc::OutputAction::Mode { mode } => {
|
|
|
|
|
config.mode = match mode {
|
|
|
|
|
niri_ipc::ModeToSet::Automatic => None,
|
|
|
|
|
niri_ipc::ModeToSet::Specific(mode) => Some(mode),
|
2024-05-05 10:19:47 +04:00
|
|
|
}
|
2025-01-01 03:07:47 -03:00
|
|
|
}
|
|
|
|
|
niri_ipc::OutputAction::Scale { scale } => {
|
|
|
|
|
config.scale = match scale {
|
|
|
|
|
niri_ipc::ScaleToSet::Automatic => None,
|
|
|
|
|
niri_ipc::ScaleToSet::Specific(scale) => Some(FloatOrInt(scale)),
|
2024-05-05 12:39:20 +04:00
|
|
|
}
|
2025-01-01 03:07:47 -03:00
|
|
|
}
|
|
|
|
|
niri_ipc::OutputAction::Transform { transform } => config.transform = transform,
|
|
|
|
|
niri_ipc::OutputAction::Position { position } => {
|
|
|
|
|
config.position = match position {
|
|
|
|
|
niri_ipc::PositionToSet::Automatic => None,
|
|
|
|
|
niri_ipc::PositionToSet::Specific(position) => Some(niri_config::Position {
|
|
|
|
|
x: position.x,
|
|
|
|
|
y: position.y,
|
|
|
|
|
}),
|
2024-05-05 10:19:47 +04:00
|
|
|
}
|
2025-01-01 03:07:47 -03:00
|
|
|
}
|
|
|
|
|
niri_ipc::OutputAction::Vrr { vrr } => {
|
|
|
|
|
config.variable_refresh_rate = if vrr.vrr {
|
|
|
|
|
Some(niri_config::Vrr {
|
|
|
|
|
on_demand: vrr.on_demand,
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
None
|
2024-05-05 10:19:47 +04:00
|
|
|
}
|
|
|
|
|
}
|
2025-01-01 03:07:47 -03:00
|
|
|
});
|
2024-05-05 10:19:47 +04:00
|
|
|
|
|
|
|
|
self.reload_output_config();
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-27 14:54:24 +04:00
|
|
|
pub fn refresh_ipc_outputs(&mut self) {
|
|
|
|
|
if !self.niri.ipc_outputs_changed {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
self.niri.ipc_outputs_changed = false;
|
|
|
|
|
|
|
|
|
|
let _span = tracy_client::span!("State::refresh_ipc_outputs");
|
|
|
|
|
|
2024-07-04 13:49:33 +04:00
|
|
|
for ipc_output in self.backend.ipc_outputs().lock().unwrap().values_mut() {
|
2024-03-27 14:54:24 +04:00
|
|
|
let logical = self
|
|
|
|
|
.niri
|
|
|
|
|
.global_space
|
|
|
|
|
.outputs()
|
2024-07-04 13:49:33 +04:00
|
|
|
.find(|output| output.name() == ipc_output.name)
|
2024-03-27 14:54:24 +04:00
|
|
|
.map(logical_output);
|
|
|
|
|
ipc_output.logical = logical;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "dbus")]
|
|
|
|
|
self.niri.on_ipc_outputs_changed();
|
2024-05-21 22:43:42 +00:00
|
|
|
|
|
|
|
|
let new_config = self.backend.ipc_outputs().lock().unwrap().clone();
|
|
|
|
|
self.niri.output_management_state.notify_changes(new_config);
|
2024-03-27 14:54:24 +04:00
|
|
|
}
|
|
|
|
|
|
2025-03-09 20:55:30 +00:00
|
|
|
pub fn open_screenshot_ui(&mut self, show_pointer: bool) {
|
2024-07-07 09:54:19 +04:00
|
|
|
if self.niri.is_locked() || self.niri.screenshot_ui.is_open() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let default_output = self
|
|
|
|
|
.niri
|
|
|
|
|
.output_under_cursor()
|
|
|
|
|
.or_else(|| self.niri.layout.active_output().cloned());
|
|
|
|
|
let Some(default_output) = default_output else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
2024-08-23 12:48:05 +03:00
|
|
|
self.niri.update_render_elements(None);
|
2024-07-07 09:54:19 +04:00
|
|
|
|
|
|
|
|
let Some(screenshots) = self
|
|
|
|
|
.backend
|
|
|
|
|
.with_primary_renderer(|renderer| self.niri.capture_screenshots(renderer).collect())
|
|
|
|
|
else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Now that we captured the screenshots, clear grabs like drag-and-drop, etc.
|
|
|
|
|
self.niri.seat.get_pointer().unwrap().unset_grab(
|
|
|
|
|
self,
|
|
|
|
|
SERIAL_COUNTER.next_serial(),
|
|
|
|
|
get_monotonic_time().as_millis() as u32,
|
|
|
|
|
);
|
2025-05-09 09:18:22 +03:00
|
|
|
if let Some(touch) = self.niri.seat.get_touch() {
|
|
|
|
|
touch.unset_grab(self);
|
|
|
|
|
}
|
2024-07-07 09:54:19 +04:00
|
|
|
|
2024-07-08 10:54:21 +04:00
|
|
|
self.backend.with_primary_renderer(|renderer| {
|
|
|
|
|
self.niri
|
|
|
|
|
.screenshot_ui
|
2025-03-09 20:55:30 +00:00
|
|
|
.open(renderer, screenshots, default_output, show_pointer)
|
2024-07-08 10:54:21 +04:00
|
|
|
});
|
|
|
|
|
|
2024-07-07 09:54:19 +04:00
|
|
|
self.niri
|
|
|
|
|
.cursor_manager
|
|
|
|
|
.set_cursor_image(CursorImageStatus::Named(CursorIcon::Crosshair));
|
|
|
|
|
self.niri.queue_redraw_all();
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-06 17:24:44 -05:00
|
|
|
pub fn handle_pick_color(&mut self, tx: async_channel::Sender<Option<niri_ipc::PickedColor>>) {
|
|
|
|
|
let pointer = self.niri.seat.get_pointer().unwrap();
|
|
|
|
|
let start_data = PointerGrabStartData {
|
|
|
|
|
focus: None,
|
|
|
|
|
button: 0,
|
|
|
|
|
location: pointer.current_location(),
|
|
|
|
|
};
|
|
|
|
|
let grab = PickColorGrab::new(start_data);
|
|
|
|
|
pointer.set_grab(self, grab, SERIAL_COUNTER.next_serial(), Focus::Clear);
|
|
|
|
|
self.niri.pick_color = Some(tx);
|
|
|
|
|
self.niri
|
|
|
|
|
.cursor_manager
|
|
|
|
|
.set_cursor_image(CursorImageStatus::Named(CursorIcon::Crosshair));
|
|
|
|
|
self.niri.queue_redraw_all();
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-09 15:41:57 +03:00
|
|
|
pub fn confirm_screenshot(&mut self, write_to_disk: bool) {
|
|
|
|
|
if !self.niri.screenshot_ui.is_open() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.backend.with_primary_renderer(|renderer| {
|
|
|
|
|
match self.niri.screenshot_ui.capture(renderer) {
|
|
|
|
|
Ok((size, pixels)) => {
|
|
|
|
|
if let Err(err) = self.niri.save_screenshot(size, pixels, write_to_disk) {
|
|
|
|
|
warn!("error saving screenshot: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
warn!("error capturing screenshot: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
self.niri.screenshot_ui.close();
|
|
|
|
|
self.niri
|
|
|
|
|
.cursor_manager
|
|
|
|
|
.set_cursor_image(CursorImageStatus::default_named());
|
|
|
|
|
self.niri.queue_redraw_all();
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-10 08:49:47 +04:00
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
2024-06-27 11:36:24 +04:00
|
|
|
pub fn on_pw_msg(&mut self, msg: PwToNiri) {
|
|
|
|
|
match msg {
|
|
|
|
|
PwToNiri::StopCast { session_id } => self.niri.stop_cast(session_id),
|
2025-03-15 10:23:00 +03:00
|
|
|
PwToNiri::Redraw { stream_id } => self.redraw_cast(stream_id),
|
2025-01-04 12:18:58 +03:00
|
|
|
PwToNiri::FatalError => {
|
|
|
|
|
warn!("stopping PipeWire due to fatal error");
|
|
|
|
|
if let Some(pw) = self.niri.pipewire.take() {
|
|
|
|
|
let ids: Vec<_> = self.niri.casts.iter().map(|cast| cast.session_id).collect();
|
|
|
|
|
for id in ids {
|
|
|
|
|
self.niri.stop_cast(id);
|
|
|
|
|
}
|
|
|
|
|
self.niri.event_loop.remove(pw.token);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-06-27 11:36:24 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-15 10:23:00 +03:00
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
|
|
|
|
fn redraw_cast(&mut self, stream_id: usize) {
|
|
|
|
|
let _span = tracy_client::span!("State::redraw_cast");
|
|
|
|
|
|
|
|
|
|
let casts = &mut self.niri.casts;
|
|
|
|
|
let Some(cast) = casts.iter_mut().find(|cast| cast.stream_id == stream_id) else {
|
|
|
|
|
warn!("cast to redraw is missing");
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match &cast.target {
|
2025-03-15 11:23:01 +03:00
|
|
|
CastTarget::Nothing => {
|
|
|
|
|
self.backend.with_primary_renderer(|renderer| {
|
2025-08-03 13:29:48 +03:00
|
|
|
if cast.dequeue_buffer_and_clear(renderer) {
|
2025-03-15 11:23:01 +03:00
|
|
|
cast.last_frame_time = get_monotonic_time();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-03-15 10:23:00 +03:00
|
|
|
CastTarget::Output(weak) => {
|
|
|
|
|
if let Some(output) = weak.upgrade() {
|
|
|
|
|
self.niri.queue_redraw(&output);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
CastTarget::Window { id } => {
|
|
|
|
|
let mut windows = self.niri.layout.windows();
|
|
|
|
|
let Some((_, mapped)) = windows.find(|(_, mapped)| mapped.id().get() == *id) else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Use the cached output since it will be present even if the output was
|
|
|
|
|
// currently disconnected.
|
|
|
|
|
let Some(output) = self.niri.mapped_cast_output.get(&mapped.window) else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let scale = Scale::from(output.current_scale().fractional_scale());
|
|
|
|
|
let bbox = mapped
|
|
|
|
|
.window
|
|
|
|
|
.bbox_with_popups()
|
|
|
|
|
.to_physical_precise_up(scale);
|
|
|
|
|
|
|
|
|
|
match cast.ensure_size(bbox.size) {
|
|
|
|
|
Ok(CastSizeChange::Ready) => (),
|
|
|
|
|
Ok(CastSizeChange::Pending) => return,
|
|
|
|
|
Err(err) => {
|
|
|
|
|
warn!("error updating stream size, stopping screencast: {err:?}");
|
|
|
|
|
drop(windows);
|
|
|
|
|
let session_id = cast.session_id;
|
|
|
|
|
self.niri.stop_cast(session_id);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.backend.with_primary_renderer(|renderer| {
|
|
|
|
|
// FIXME: pointer.
|
|
|
|
|
let elements = mapped
|
|
|
|
|
.render_for_screen_cast(renderer, scale)
|
|
|
|
|
.rev()
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
2025-08-03 13:29:48 +03:00
|
|
|
if cast.dequeue_buffer_and_render(renderer, &elements, bbox.size, scale) {
|
2025-03-15 10:23:00 +03:00
|
|
|
cast.last_frame_time = get_monotonic_time();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-15 11:23:01 +03:00
|
|
|
#[cfg(not(feature = "xdp-gnome-screencast"))]
|
|
|
|
|
pub fn set_dynamic_cast_target(&mut self, _target: CastTarget) {}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
|
|
|
|
pub fn set_dynamic_cast_target(&mut self, target: CastTarget) {
|
|
|
|
|
let _span = tracy_client::span!("State::set_dynamic_cast_target");
|
|
|
|
|
|
|
|
|
|
let mut refresh = None;
|
|
|
|
|
match &target {
|
|
|
|
|
// Leave refresh as is when clearing. Chances are, the next refresh will match it,
|
|
|
|
|
// then we'll avoid reconfiguring.
|
|
|
|
|
CastTarget::Nothing => (),
|
|
|
|
|
CastTarget::Output(output) => {
|
|
|
|
|
if let Some(output) = output.upgrade() {
|
|
|
|
|
refresh = Some(output.current_mode().unwrap().refresh as u32);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
CastTarget::Window { id } => {
|
|
|
|
|
let mut windows = self.niri.layout.windows();
|
|
|
|
|
if let Some((_, mapped)) = windows.find(|(_, mapped)| mapped.id().get() == *id) {
|
|
|
|
|
if let Some(output) = self.niri.mapped_cast_output.get(&mapped.window) {
|
|
|
|
|
refresh = Some(output.current_mode().unwrap().refresh as u32);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut to_redraw = Vec::new();
|
|
|
|
|
let mut to_stop = Vec::new();
|
|
|
|
|
for cast in &mut self.niri.casts {
|
|
|
|
|
if !cast.dynamic_target {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(refresh) = refresh {
|
|
|
|
|
if let Err(err) = cast.set_refresh(refresh) {
|
|
|
|
|
warn!("error changing cast FPS: {err:?}");
|
|
|
|
|
to_stop.push(cast.session_id);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cast.target = target.clone();
|
|
|
|
|
to_redraw.push(cast.stream_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for id in to_redraw {
|
|
|
|
|
self.redraw_cast(id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-27 11:36:24 +04:00
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
|
|
|
|
pub fn on_screen_cast_msg(&mut self, msg: ScreenCastToNiri) {
|
2024-12-17 17:04:23 +03:00
|
|
|
use smithay::reexports::gbm::Modifier;
|
|
|
|
|
|
2024-06-27 11:36:24 +04:00
|
|
|
use crate::dbus::mutter_screen_cast::StreamTargetId;
|
|
|
|
|
|
2023-10-10 08:49:47 +04:00
|
|
|
match msg {
|
2023-10-10 08:55:54 +04:00
|
|
|
ScreenCastToNiri::StartCast {
|
2023-10-10 08:49:47 +04:00
|
|
|
session_id,
|
2025-03-15 10:23:00 +03:00
|
|
|
stream_id,
|
2024-06-27 11:36:24 +04:00
|
|
|
target,
|
2023-10-10 08:49:47 +04:00
|
|
|
cursor_mode,
|
|
|
|
|
signal_ctx,
|
|
|
|
|
} => {
|
|
|
|
|
let _span = tracy_client::span!("StartCast");
|
|
|
|
|
|
2025-03-15 10:23:00 +03:00
|
|
|
debug!(session_id, stream_id, "StartCast");
|
2023-10-10 08:49:47 +04:00
|
|
|
|
2025-01-10 09:08:35 +03:00
|
|
|
let Some(gbm) = self.backend.gbm_device() else {
|
|
|
|
|
warn!("error starting screencast: no GBM device available");
|
|
|
|
|
self.niri.stop_cast(session_id);
|
|
|
|
|
return;
|
2023-10-10 08:49:47 +04:00
|
|
|
};
|
|
|
|
|
|
2025-01-04 12:18:58 +03:00
|
|
|
let pw = if let Some(pw) = &self.niri.pipewire {
|
|
|
|
|
pw
|
|
|
|
|
} else {
|
2025-08-03 13:29:48 +03:00
|
|
|
match PipeWire::new(self.niri.event_loop.clone(), self.niri.pw_to_niri.clone())
|
|
|
|
|
{
|
2025-01-04 12:18:58 +03:00
|
|
|
Ok(pipewire) => self.niri.pipewire.insert(pipewire),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
warn!(
|
|
|
|
|
"error starting screencast: PipeWire failed to initialize: {err:?}"
|
|
|
|
|
);
|
|
|
|
|
self.niri.stop_cast(session_id);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-03-19 08:59:28 +04:00
|
|
|
};
|
|
|
|
|
|
2025-03-15 11:23:01 +03:00
|
|
|
let mut dynamic_target = false;
|
2024-06-28 08:58:32 +04:00
|
|
|
let (target, size, refresh, alpha) = match target {
|
2024-06-27 11:36:24 +04:00
|
|
|
StreamTargetId::Output { name } => {
|
|
|
|
|
let global_space = &self.niri.global_space;
|
|
|
|
|
let output = global_space.outputs().find(|out| out.name() == name);
|
|
|
|
|
let Some(output) = output else {
|
|
|
|
|
warn!("error starting screencast: requested output is missing");
|
|
|
|
|
self.niri.stop_cast(session_id);
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mode = output.current_mode().unwrap();
|
|
|
|
|
let transform = output.current_transform();
|
|
|
|
|
let size = transform.transform_size(mode.size);
|
|
|
|
|
let refresh = mode.refresh as u32;
|
2024-06-28 08:58:32 +04:00
|
|
|
(CastTarget::Output(output.downgrade()), size, refresh, false)
|
2024-06-27 11:36:24 +04:00
|
|
|
}
|
2025-03-15 11:23:01 +03:00
|
|
|
StreamTargetId::Window { id }
|
|
|
|
|
if id == self.niri.dynamic_cast_id_for_portal.get() =>
|
|
|
|
|
{
|
|
|
|
|
dynamic_target = true;
|
|
|
|
|
|
|
|
|
|
// All dynamic casts start as Nothing to avoid surprises and exposing
|
|
|
|
|
// sensitive info.
|
|
|
|
|
(CastTarget::Nothing, Size::from((1, 1)), 1000, true)
|
|
|
|
|
}
|
2024-06-27 11:36:24 +04:00
|
|
|
StreamTargetId::Window { id } => {
|
2025-03-15 11:18:54 +03:00
|
|
|
let Some(window) = self.niri.layout.windows().find_map(|(_, mapped)| {
|
|
|
|
|
(mapped.id().get() == id).then_some(&mapped.window)
|
|
|
|
|
}) else {
|
2024-06-27 11:36:24 +04:00
|
|
|
warn!("error starting screencast: requested window is missing");
|
|
|
|
|
self.niri.stop_cast(session_id);
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Use the cached output since it will be present even if the output was
|
|
|
|
|
// currently disconnected.
|
2025-03-15 11:18:54 +03:00
|
|
|
let Some(output) = self.niri.mapped_cast_output.get(window) else {
|
2024-06-27 11:36:24 +04:00
|
|
|
warn!("error starting screencast: requested window is missing");
|
|
|
|
|
self.niri.stop_cast(session_id);
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let scale = Scale::from(output.current_scale().fractional_scale());
|
2024-06-28 12:35:12 +04:00
|
|
|
let bbox = window.bbox_with_popups().to_physical_precise_up(scale);
|
2024-06-27 11:36:24 +04:00
|
|
|
let refresh = output.current_mode().unwrap().refresh as u32;
|
|
|
|
|
|
2024-06-28 12:35:12 +04:00
|
|
|
(CastTarget::Window { id }, bbox.size, refresh, true)
|
2024-06-27 11:36:24 +04:00
|
|
|
}
|
2024-03-27 14:54:24 +04:00
|
|
|
};
|
|
|
|
|
|
2024-12-17 17:04:23 +03:00
|
|
|
let mut render_formats = self
|
2024-08-06 15:13:58 +03:00
|
|
|
.backend
|
|
|
|
|
.with_primary_renderer(|renderer| {
|
|
|
|
|
renderer.egl_context().dmabuf_render_formats().clone()
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
|
2024-12-17 17:04:23 +03:00
|
|
|
{
|
|
|
|
|
let config = self.niri.config.borrow();
|
|
|
|
|
if config.debug.force_pipewire_invalid_modifier {
|
|
|
|
|
render_formats = render_formats
|
|
|
|
|
.into_iter()
|
|
|
|
|
.filter(|f| f.modifier == Modifier::Invalid)
|
|
|
|
|
.collect();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-27 11:36:24 +04:00
|
|
|
let res = pw.start_cast(
|
2023-10-10 08:49:47 +04:00
|
|
|
gbm,
|
2024-08-06 15:13:58 +03:00
|
|
|
render_formats,
|
2023-10-10 08:49:47 +04:00
|
|
|
session_id,
|
2025-03-15 10:23:00 +03:00
|
|
|
stream_id,
|
2024-06-27 11:36:24 +04:00
|
|
|
target,
|
2025-03-15 11:23:01 +03:00
|
|
|
dynamic_target,
|
2024-06-27 11:36:24 +04:00
|
|
|
size,
|
|
|
|
|
refresh,
|
2024-06-28 08:58:32 +04:00
|
|
|
alpha,
|
2023-10-10 08:49:47 +04:00
|
|
|
cursor_mode,
|
|
|
|
|
signal_ctx,
|
2024-06-27 11:36:24 +04:00
|
|
|
);
|
|
|
|
|
match res {
|
2023-10-10 08:49:47 +04:00
|
|
|
Ok(cast) => {
|
|
|
|
|
self.niri.casts.push(cast);
|
|
|
|
|
}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
warn!("error starting screencast: {err:?}");
|
|
|
|
|
self.niri.stop_cast(session_id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-10 08:55:54 +04:00
|
|
|
ScreenCastToNiri::StopCast { session_id } => self.niri.stop_cast(session_id),
|
2023-10-10 08:49:47 +04:00
|
|
|
}
|
|
|
|
|
}
|
2023-10-10 08:54:15 +04:00
|
|
|
|
2023-10-10 10:25:26 +04:00
|
|
|
#[cfg(feature = "dbus")]
|
2023-10-10 09:55:44 +04:00
|
|
|
pub fn on_screen_shot_msg(
|
2023-10-10 08:54:15 +04:00
|
|
|
&mut self,
|
|
|
|
|
to_screenshot: &async_channel::Sender<NiriToScreenshot>,
|
|
|
|
|
msg: ScreenshotToNiri,
|
|
|
|
|
) {
|
2025-03-06 17:24:44 -05:00
|
|
|
match msg {
|
|
|
|
|
ScreenshotToNiri::TakeScreenshot { include_cursor } => {
|
|
|
|
|
self.handle_take_screenshot(to_screenshot, include_cursor);
|
|
|
|
|
}
|
|
|
|
|
ScreenshotToNiri::PickColor(tx) => {
|
|
|
|
|
self.handle_pick_color(tx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "dbus")]
|
|
|
|
|
fn handle_take_screenshot(
|
|
|
|
|
&mut self,
|
|
|
|
|
to_screenshot: &async_channel::Sender<NiriToScreenshot>,
|
|
|
|
|
include_cursor: bool,
|
|
|
|
|
) {
|
2023-10-10 08:54:15 +04:00
|
|
|
let _span = tracy_client::span!("TakeScreenshot");
|
|
|
|
|
|
2024-01-03 11:33:24 +04:00
|
|
|
let rv = self.backend.with_primary_renderer(|renderer| {
|
|
|
|
|
let on_done = {
|
|
|
|
|
let to_screenshot = to_screenshot.clone();
|
|
|
|
|
move |path| {
|
|
|
|
|
let msg = NiriToScreenshot::ScreenshotResult(Some(path));
|
|
|
|
|
if let Err(err) = to_screenshot.send_blocking(msg) {
|
|
|
|
|
warn!("error sending path to screenshot: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let res = self
|
|
|
|
|
.niri
|
|
|
|
|
.screenshot_all_outputs(renderer, include_cursor, on_done);
|
2023-10-10 08:54:15 +04:00
|
|
|
|
2024-01-03 11:33:24 +04:00
|
|
|
if let Err(err) = res {
|
|
|
|
|
warn!("error taking a screenshot: {err:?}");
|
|
|
|
|
|
|
|
|
|
let msg = NiriToScreenshot::ScreenshotResult(None);
|
2023-10-10 08:54:15 +04:00
|
|
|
if let Err(err) = to_screenshot.send_blocking(msg) {
|
2024-01-03 11:33:24 +04:00
|
|
|
warn!("error sending None to screenshot: {err:?}");
|
2023-10-10 08:54:15 +04:00
|
|
|
}
|
|
|
|
|
}
|
2024-01-03 11:33:24 +04:00
|
|
|
});
|
2023-10-10 08:54:15 +04:00
|
|
|
|
2024-01-03 11:33:24 +04:00
|
|
|
if rv.is_none() {
|
2023-10-10 08:54:15 +04:00
|
|
|
let msg = NiriToScreenshot::ScreenshotResult(None);
|
|
|
|
|
if let Err(err) = to_screenshot.send_blocking(msg) {
|
|
|
|
|
warn!("error sending None to screenshot: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-06-26 10:08:07 +04:00
|
|
|
|
|
|
|
|
#[cfg(feature = "dbus")]
|
|
|
|
|
pub fn on_introspect_msg(
|
|
|
|
|
&mut self,
|
|
|
|
|
to_introspect: &async_channel::Sender<NiriToIntrospect>,
|
|
|
|
|
msg: IntrospectToNiri,
|
|
|
|
|
) {
|
2024-11-02 10:35:05 +03:00
|
|
|
use crate::utils::with_toplevel_role;
|
2024-06-26 10:08:07 +04:00
|
|
|
|
|
|
|
|
let IntrospectToNiri::GetWindows = msg;
|
|
|
|
|
let _span = tracy_client::span!("GetWindows");
|
|
|
|
|
|
|
|
|
|
let mut windows = HashMap::new();
|
|
|
|
|
|
2025-03-15 11:23:01 +03:00
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
|
|
|
|
windows.insert(
|
|
|
|
|
self.niri.dynamic_cast_id_for_portal.get(),
|
|
|
|
|
gnome_shell_introspect::WindowProperties {
|
|
|
|
|
title: String::from("niri Dynamic Cast Target"),
|
2025-06-13 09:54:11 +03:00
|
|
|
app_id: String::from("rs.bxt.niri.desktop"),
|
2025-03-15 11:23:01 +03:00
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
2025-08-16 01:42:08 -07:00
|
|
|
self.niri.layout.with_windows(|mapped, _, _, _| {
|
2024-08-31 10:22:57 +03:00
|
|
|
let id = mapped.id().get();
|
2024-11-02 10:35:05 +03:00
|
|
|
let props = with_toplevel_role(mapped.toplevel(), |role| {
|
2024-06-26 10:08:07 +04:00
|
|
|
gnome_shell_introspect::WindowProperties {
|
|
|
|
|
title: role.title.clone().unwrap_or_default(),
|
2025-06-13 09:54:11 +03:00
|
|
|
app_id: role
|
|
|
|
|
.app_id
|
|
|
|
|
.as_ref()
|
|
|
|
|
// We don't do proper .desktop file tracking (it's quite involved), and
|
|
|
|
|
// Wayland windows can set any app id they want. However, this seems to
|
|
|
|
|
// work well enough in practice.
|
|
|
|
|
.map(|app_id| format!("{app_id}.desktop"))
|
|
|
|
|
.unwrap_or_default(),
|
2024-06-26 10:08:07 +04:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
windows.insert(id, props);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let msg = NiriToIntrospect::Windows(windows);
|
|
|
|
|
if let Err(err) = to_introspect.send_blocking(msg) {
|
|
|
|
|
warn!("error sending windows to introspect: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-15 15:44:10 +03:00
|
|
|
|
|
|
|
|
#[cfg(feature = "dbus")]
|
|
|
|
|
pub fn on_locale1_msg(&mut self, msg: Locale1ToNiri) {
|
|
|
|
|
let Locale1ToNiri::XkbChanged(xkb) = msg;
|
|
|
|
|
|
|
|
|
|
trace!("locale1 xkb settings changed: {xkb:?}");
|
|
|
|
|
let xkb = self.niri.xkb_from_locale1.insert(xkb);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
let config = self.niri.config.borrow();
|
|
|
|
|
if config.input.keyboard.xkb != Xkb::default() {
|
|
|
|
|
trace!("ignoring locale1 xkb change because niri config has xkb settings");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let xkb = xkb.clone();
|
2025-08-26 21:07:29 +03:00
|
|
|
self.set_xkb_config(xkb.to_xkb_config());
|
2025-07-15 15:44:10 +03:00
|
|
|
self.ipc_keyboard_layouts_changed();
|
|
|
|
|
}
|
2023-09-03 13:25:43 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-09 11:03:38 +04:00
|
|
|
impl Niri {
|
|
|
|
|
pub fn new(
|
2023-09-14 22:28:26 +04:00
|
|
|
config: Rc<RefCell<Config>>,
|
2023-09-24 17:30:06 +04:00
|
|
|
event_loop: LoopHandle<'static, State>,
|
2023-08-09 11:03:38 +04:00
|
|
|
stop_signal: LoopSignal,
|
2023-09-24 11:04:30 +04:00
|
|
|
display: Display<State>,
|
2023-09-08 17:54:02 +04:00
|
|
|
backend: &Backend,
|
2025-01-27 08:28:02 +03:00
|
|
|
create_wayland_socket: bool,
|
2025-07-14 07:34:10 -04:00
|
|
|
is_session_instance: bool,
|
2023-08-09 11:03:38 +04:00
|
|
|
) -> Self {
|
2023-10-09 17:36:58 +04:00
|
|
|
let _span = tracy_client::span!("Niri::new");
|
|
|
|
|
|
2024-01-17 10:23:48 +04:00
|
|
|
let (executor, scheduler) = calloop::futures::executor().unwrap();
|
|
|
|
|
event_loop.insert_source(executor, |_, _, _| ()).unwrap();
|
|
|
|
|
|
2023-08-09 11:03:38 +04:00
|
|
|
let display_handle = display.handle();
|
2023-09-14 22:28:26 +04:00
|
|
|
let config_ = config.borrow();
|
2024-05-05 10:19:47 +04:00
|
|
|
let config_file_output_config = config_.outputs.clone();
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2024-11-24 09:41:43 +03:00
|
|
|
let mut animation_clock = Clock::default();
|
|
|
|
|
|
2025-06-09 14:02:17 +03:00
|
|
|
let rate = 1.0 / config_.animations.slowdown.0.max(0.001);
|
2024-11-24 09:41:43 +03:00
|
|
|
animation_clock.set_rate(rate);
|
|
|
|
|
animation_clock.set_complete_instantly(config_.animations.off);
|
|
|
|
|
|
|
|
|
|
let layout = Layout::new(animation_clock.clone(), &config_);
|
2023-10-05 09:25:07 +04:00
|
|
|
|
2024-08-22 14:44:11 +03:00
|
|
|
let (blocker_cleared_tx, blocker_cleared_rx) = mpsc::channel();
|
|
|
|
|
|
2025-01-30 00:02:54 +02:00
|
|
|
fn client_is_unrestricted(client: &Client) -> bool {
|
|
|
|
|
!client.get_data::<ClientState>().unwrap().restricted
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-26 00:15:46 +04:00
|
|
|
let compositor_state = CompositorState::new_v6::<State>(&display_handle);
|
2023-09-03 14:10:02 +04:00
|
|
|
let xdg_shell_state = XdgShellState::new_with_capabilities::<State>(
|
2023-08-10 17:17:17 +04:00
|
|
|
&display_handle,
|
2023-08-16 07:10:44 +04:00
|
|
|
[WmCapabilities::Fullscreen],
|
2023-08-10 17:17:17 +04:00
|
|
|
);
|
2024-01-15 16:01:01 +04:00
|
|
|
let xdg_decoration_state =
|
|
|
|
|
XdgDecorationState::new_with_filter::<State, _>(&display_handle, |client| {
|
|
|
|
|
client
|
|
|
|
|
.get_data::<ClientState>()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.can_view_decoration_globals
|
|
|
|
|
});
|
|
|
|
|
let kde_decoration_state = KdeDecorationState::new_with_filter::<State, _>(
|
2023-09-27 16:08:11 +04:00
|
|
|
&display_handle,
|
2024-01-15 16:01:01 +04:00
|
|
|
// If we want CSD we will hide the global.
|
|
|
|
|
KdeDecorationsMode::Server,
|
|
|
|
|
|client| {
|
|
|
|
|
client
|
|
|
|
|
.get_data::<ClientState>()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.can_view_decoration_globals
|
2023-09-27 16:08:11 +04:00
|
|
|
},
|
|
|
|
|
);
|
2025-01-30 00:02:54 +02:00
|
|
|
let layer_shell_state = WlrLayerShellState::new_with_filter::<State, _>(
|
|
|
|
|
&display_handle,
|
|
|
|
|
client_is_unrestricted,
|
|
|
|
|
);
|
2023-10-13 13:30:11 +04:00
|
|
|
let session_lock_state =
|
2025-01-30 00:02:54 +02:00
|
|
|
SessionLockManagerState::new::<State, _>(&display_handle, client_is_unrestricted);
|
2024-04-22 17:47:12 +04:00
|
|
|
let shm_state = ShmState::new::<State>(
|
|
|
|
|
&display_handle,
|
|
|
|
|
vec![wl_shm::Format::Xbgr8888, wl_shm::Format::Abgr8888],
|
|
|
|
|
);
|
2023-09-03 14:10:02 +04:00
|
|
|
let output_manager_state =
|
|
|
|
|
OutputManagerState::new_with_xdg_output::<State>(&display_handle);
|
2023-12-31 12:02:39 +04:00
|
|
|
let dmabuf_state = DmabufState::new();
|
2024-05-29 14:29:37 +03:00
|
|
|
let fractional_scale_manager_state =
|
|
|
|
|
FractionalScaleManagerState::new::<State>(&display_handle);
|
2023-08-09 11:03:38 +04:00
|
|
|
let mut seat_state = SeatState::new();
|
2023-09-03 14:10:02 +04:00
|
|
|
let tablet_state = TabletManagerState::new::<State>(&display_handle);
|
|
|
|
|
let pointer_gestures_state = PointerGesturesState::new::<State>(&display_handle);
|
2023-12-04 18:12:12 +04:00
|
|
|
let relative_pointer_state = RelativePointerManagerState::new::<State>(&display_handle);
|
2023-12-21 16:19:16 +04:00
|
|
|
let pointer_constraints_state = PointerConstraintsState::new::<State>(&display_handle);
|
2024-02-08 13:51:54 +04:00
|
|
|
let idle_notifier_state = IdleNotifierState::new(&display_handle, event_loop.clone());
|
|
|
|
|
let idle_inhibit_manager_state = IdleInhibitManagerState::new::<State>(&display_handle);
|
2023-09-03 14:10:02 +04:00
|
|
|
let data_device_state = DataDeviceState::new::<State>(&display_handle);
|
2025-01-22 00:00:35 -05:00
|
|
|
let primary_selection_state =
|
|
|
|
|
PrimarySelectionState::new_with_filter::<State, _>(&display_handle, |client| {
|
|
|
|
|
!client
|
|
|
|
|
.get_data::<ClientState>()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.primary_selection_disabled
|
|
|
|
|
});
|
2025-02-14 08:35:49 +03:00
|
|
|
let wlr_data_control_state = WlrDataControlState::new::<State, _>(
|
2023-09-30 21:05:13 +04:00
|
|
|
&display_handle,
|
|
|
|
|
Some(&primary_selection_state),
|
2025-01-30 00:02:54 +02:00
|
|
|
client_is_unrestricted,
|
2023-09-30 21:05:13 +04:00
|
|
|
);
|
2025-02-14 08:47:26 +03:00
|
|
|
let ext_data_control_state = ExtDataControlState::new::<State, _>(
|
|
|
|
|
&display_handle,
|
|
|
|
|
Some(&primary_selection_state),
|
|
|
|
|
client_is_unrestricted,
|
|
|
|
|
);
|
2023-08-16 10:59:34 +04:00
|
|
|
let presentation_state =
|
2023-10-26 00:15:46 +04:00
|
|
|
PresentationState::new::<State>(&display_handle, Monotonic::ID as u32);
|
2024-01-15 16:02:07 +04:00
|
|
|
let security_context_state =
|
2025-01-30 00:02:54 +02:00
|
|
|
SecurityContextState::new::<State, _>(&display_handle, client_is_unrestricted);
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2023-09-30 23:16:20 +04:00
|
|
|
let text_input_state = TextInputManagerState::new::<State>(&display_handle);
|
2023-10-24 15:05:14 +04:00
|
|
|
let input_method_state =
|
2025-01-30 00:02:54 +02:00
|
|
|
InputMethodManagerState::new::<State, _>(&display_handle, client_is_unrestricted);
|
2025-01-18 15:26:42 +01:00
|
|
|
let keyboard_shortcuts_inhibit_state =
|
|
|
|
|
KeyboardShortcutsInhibitState::new::<State>(&display_handle);
|
2023-09-30 23:16:20 +04:00
|
|
|
let virtual_keyboard_state =
|
2025-01-30 00:02:54 +02:00
|
|
|
VirtualKeyboardManagerState::new::<State, _>(&display_handle, client_is_unrestricted);
|
2025-01-18 15:26:42 +01:00
|
|
|
let virtual_pointer_state =
|
2025-01-30 00:02:54 +02:00
|
|
|
VirtualPointerManagerState::new::<State, _>(&display_handle, client_is_unrestricted);
|
2024-01-29 19:34:12 +04:00
|
|
|
let foreign_toplevel_state =
|
2025-01-30 00:02:54 +02:00
|
|
|
ForeignToplevelManagerState::new::<State, _>(&display_handle, client_is_unrestricted);
|
2025-06-14 16:17:43 +03:00
|
|
|
let ext_workspace_state =
|
|
|
|
|
ExtWorkspaceManagerState::new::<State, _>(&display_handle, client_is_unrestricted);
|
2024-05-21 22:43:42 +00:00
|
|
|
let mut output_management_state =
|
2025-01-30 00:02:54 +02:00
|
|
|
OutputManagementManagerState::new::<State, _>(&display_handle, client_is_unrestricted);
|
2024-05-21 22:43:42 +00:00
|
|
|
output_management_state.on_config_changed(config_.outputs.clone());
|
2025-01-30 00:02:54 +02:00
|
|
|
let screencopy_state =
|
|
|
|
|
ScreencopyManagerState::new::<State, _>(&display_handle, client_is_unrestricted);
|
2024-03-10 21:41:36 +04:00
|
|
|
let viewporter_state = ViewporterState::new::<State>(&display_handle);
|
2024-03-08 17:08:58 +04:00
|
|
|
let xdg_foreign_state = XdgForeignState::new::<State>(&display_handle);
|
2024-01-29 19:34:12 +04:00
|
|
|
|
2024-03-14 08:32:23 +04:00
|
|
|
let is_tty = matches!(backend, Backend::Tty(_));
|
|
|
|
|
let gamma_control_manager_state =
|
|
|
|
|
GammaControlManagerState::new::<State, _>(&display_handle, move |client| {
|
|
|
|
|
is_tty && !client.get_data::<ClientState>().unwrap().restricted
|
|
|
|
|
});
|
2024-06-09 21:09:28 -07:00
|
|
|
let activation_state = XdgActivationState::new::<State>(&display_handle);
|
2024-11-03 14:50:02 +01:00
|
|
|
event_loop
|
|
|
|
|
.insert_source(
|
|
|
|
|
Timer::from_duration(XDG_ACTIVATION_TOKEN_TIMEOUT),
|
|
|
|
|
|_, _, state| {
|
|
|
|
|
state.niri.activation_state.retain_tokens(|_, token_data| {
|
|
|
|
|
token_data.timestamp.elapsed() < XDG_ACTIVATION_TOKEN_TIMEOUT
|
|
|
|
|
});
|
|
|
|
|
TimeoutAction::ToDuration(XDG_ACTIVATION_TOKEN_TIMEOUT)
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
2024-08-13 09:15:57 +03:00
|
|
|
let mutter_x11_interop_state =
|
|
|
|
|
MutterX11InteropManagerState::new::<State, _>(&display_handle, move |_| true);
|
2024-03-03 20:36:13 +01:00
|
|
|
|
2024-12-20 08:49:18 +03:00
|
|
|
#[cfg(test)]
|
|
|
|
|
let single_pixel_buffer_state = SinglePixelBufferState::new::<State>(&display_handle);
|
|
|
|
|
|
2023-09-08 17:54:02 +04:00
|
|
|
let mut seat: Seat<State> = seat_state.new_wl_seat(&display_handle, backend.seat_name());
|
2025-04-20 11:30:40 +02:00
|
|
|
let keyboard = match seat.add_keyboard(
|
2023-12-05 08:04:46 +04:00
|
|
|
config_.input.keyboard.xkb.to_xkb_config(),
|
|
|
|
|
config_.input.keyboard.repeat_delay.into(),
|
|
|
|
|
config_.input.keyboard.repeat_rate.into(),
|
2025-03-13 21:37:52 +03:00
|
|
|
) {
|
2025-04-20 11:30:40 +02:00
|
|
|
Err(err) => {
|
|
|
|
|
if let smithay::input::keyboard::Error::BadKeymap = err {
|
|
|
|
|
warn!("error loading the configured xkb keymap, trying default");
|
|
|
|
|
} else {
|
|
|
|
|
warn!("error adding keyboard: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
seat.add_keyboard(
|
|
|
|
|
Default::default(),
|
|
|
|
|
config_.input.keyboard.repeat_delay.into(),
|
|
|
|
|
config_.input.keyboard.repeat_rate.into(),
|
|
|
|
|
)
|
|
|
|
|
.unwrap()
|
2025-03-13 21:37:52 +03:00
|
|
|
}
|
2025-04-20 11:30:40 +02:00
|
|
|
Ok(keyboard) => keyboard,
|
|
|
|
|
};
|
|
|
|
|
if config_.input.keyboard.numlock {
|
|
|
|
|
let mut modifier_state = keyboard.modifier_state();
|
|
|
|
|
modifier_state.num_lock = true;
|
|
|
|
|
keyboard.set_modifier_state(modifier_state);
|
2025-03-13 21:37:52 +03:00
|
|
|
}
|
2023-08-09 11:03:38 +04:00
|
|
|
seat.add_pointer();
|
|
|
|
|
|
2023-10-29 14:04:38 +04:00
|
|
|
let cursor_shape_manager_state = CursorShapeManagerState::new::<State>(&display_handle);
|
|
|
|
|
let cursor_manager =
|
|
|
|
|
CursorManager::new(&config_.cursor.xcursor_theme, config_.cursor.xcursor_size);
|
2023-10-01 07:59:28 +04:00
|
|
|
|
2025-02-05 09:34:25 -05:00
|
|
|
let mod_key = backend.mod_key(&config.borrow());
|
|
|
|
|
let mods_with_mouse_binds = mods_with_mouse_binds(mod_key, &config_.binds);
|
|
|
|
|
let mods_with_wheel_binds = mods_with_wheel_binds(mod_key, &config_.binds);
|
|
|
|
|
let mods_with_finger_scroll_binds = mods_with_finger_scroll_binds(mod_key, &config_.binds);
|
2024-03-23 19:20:44 +04:00
|
|
|
|
2024-11-24 09:41:43 +03:00
|
|
|
let screenshot_ui = ScreenshotUi::new(animation_clock.clone(), config.clone());
|
|
|
|
|
let config_error_notification =
|
|
|
|
|
ConfigErrorNotification::new(animation_clock.clone(), config.clone());
|
2024-01-20 08:31:05 +04:00
|
|
|
|
2025-02-05 09:34:25 -05:00
|
|
|
let mut hotkey_overlay = HotkeyOverlay::new(config.clone(), mod_key);
|
2024-01-20 08:31:05 +04:00
|
|
|
if !config_.hotkey_overlay.skip_at_startup {
|
|
|
|
|
hotkey_overlay.show();
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-22 08:50:52 +03:00
|
|
|
let exit_confirm_dialog = ExitConfirmDialog::new(animation_clock.clone(), config.clone());
|
2023-10-30 20:29:03 +04:00
|
|
|
|
2025-08-21 15:02:25 +03:00
|
|
|
#[cfg(feature = "dbus")]
|
|
|
|
|
let a11y = A11y::new(event_loop.clone());
|
|
|
|
|
|
2024-03-12 10:17:45 +04:00
|
|
|
event_loop
|
|
|
|
|
.insert_source(
|
|
|
|
|
Timer::from_duration(Duration::from_secs(1)),
|
|
|
|
|
|_, _, state| {
|
|
|
|
|
state.niri.send_frame_callbacks_on_fallback_timer();
|
|
|
|
|
TimeoutAction::ToDuration(Duration::from_secs(1))
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
2025-01-27 08:28:02 +03:00
|
|
|
let socket_name = create_wayland_socket.then(|| {
|
|
|
|
|
let socket_source = ListeningSocketSource::new_auto().unwrap();
|
|
|
|
|
let socket_name = socket_source.socket_name().to_os_string();
|
|
|
|
|
event_loop
|
|
|
|
|
.insert_source(socket_source, move |client, _, state| {
|
|
|
|
|
state.niri.insert_client(NewClient {
|
|
|
|
|
client,
|
|
|
|
|
restricted: false,
|
|
|
|
|
credentials_unknown: false,
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
socket_name
|
|
|
|
|
});
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2025-01-27 08:28:02 +03:00
|
|
|
let ipc_server = match IpcServer::start(&event_loop, socket_name.as_deref()) {
|
2024-01-17 10:38:32 +04:00
|
|
|
Ok(server) => Some(server),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
warn!("error starting IPC server: {err:?}");
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-01-04 12:18:58 +03:00
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
|
|
|
|
let pw_to_niri = {
|
|
|
|
|
let (pw_to_niri, from_pipewire) = calloop::channel::channel();
|
|
|
|
|
event_loop
|
|
|
|
|
.insert_source(from_pipewire, move |event, _, state| match event {
|
|
|
|
|
calloop::channel::Event::Msg(msg) => state.on_pw_msg(msg),
|
|
|
|
|
calloop::channel::Event::Closed => (),
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
pw_to_niri
|
|
|
|
|
};
|
2023-09-08 17:54:02 +04:00
|
|
|
|
2023-10-10 08:49:47 +04:00
|
|
|
let display_source = Generic::new(display, Interest::READ, Mode::Level);
|
|
|
|
|
event_loop
|
|
|
|
|
.insert_source(display_source, |_, display, state| {
|
|
|
|
|
// SAFETY: we don't drop the display.
|
|
|
|
|
unsafe {
|
|
|
|
|
display.get_mut().dispatch_clients(state).unwrap();
|
|
|
|
|
}
|
|
|
|
|
Ok(PostAction::Continue)
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
2024-05-16 11:43:13 +04:00
|
|
|
event_loop
|
|
|
|
|
.insert_source(
|
|
|
|
|
Timer::from_duration(Duration::from_secs(60)),
|
|
|
|
|
|_, _, state| {
|
|
|
|
|
let _span = tracy_client::span!("startup timeout");
|
|
|
|
|
state.niri.is_at_startup = false;
|
|
|
|
|
state.niri.recompute_window_rules();
|
2024-11-14 11:33:08 +03:00
|
|
|
state.niri.recompute_layer_rules();
|
2024-05-16 11:43:13 +04:00
|
|
|
TimeoutAction::Drop
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
2023-10-10 08:49:47 +04:00
|
|
|
drop(config_);
|
2024-10-06 20:27:04 +03:00
|
|
|
let mut niri = Self {
|
2023-10-10 08:49:47 +04:00
|
|
|
config,
|
2024-05-05 10:19:47 +04:00
|
|
|
config_file_output_config,
|
2025-08-09 16:20:08 +04:00
|
|
|
config_file_watcher: None,
|
2023-10-10 08:49:47 +04:00
|
|
|
|
|
|
|
|
event_loop,
|
2024-01-17 10:23:48 +04:00
|
|
|
scheduler,
|
2023-10-10 08:49:47 +04:00
|
|
|
stop_signal,
|
|
|
|
|
socket_name,
|
2023-10-29 14:04:38 +04:00
|
|
|
display_handle,
|
2025-07-14 07:34:10 -04:00
|
|
|
is_session_instance,
|
2023-10-29 14:04:38 +04:00
|
|
|
start_time: Instant::now(),
|
2024-05-16 11:43:13 +04:00
|
|
|
is_at_startup: true,
|
2024-11-24 09:41:43 +03:00
|
|
|
clock: animation_clock,
|
2023-10-10 08:49:47 +04:00
|
|
|
|
|
|
|
|
layout,
|
|
|
|
|
global_space: Space::default(),
|
2024-12-15 16:04:42 +01:00
|
|
|
sorted_outputs: Vec::default(),
|
2023-10-10 08:49:47 +04:00
|
|
|
output_state: HashMap::new(),
|
|
|
|
|
unmapped_windows: HashMap::new(),
|
2024-09-10 10:14:34 +03:00
|
|
|
unmapped_layer_surfaces: HashSet::new(),
|
2024-11-14 11:33:08 +03:00
|
|
|
mapped_layer_surfaces: HashMap::new(),
|
2024-04-10 08:53:35 +04:00
|
|
|
root_surface: HashMap::new(),
|
2024-08-18 12:42:50 +03:00
|
|
|
dmabuf_pre_commit_hook: HashMap::new(),
|
2024-08-22 14:44:11 +03:00
|
|
|
blocker_cleared_tx,
|
|
|
|
|
blocker_cleared_rx,
|
2023-10-10 08:49:47 +04:00
|
|
|
monitors_active: true,
|
2024-11-05 09:40:54 +03:00
|
|
|
is_lid_closed: false,
|
2023-10-10 08:49:47 +04:00
|
|
|
|
2024-01-16 20:28:46 +04:00
|
|
|
devices: HashSet::new(),
|
2023-12-03 13:50:07 +04:00
|
|
|
tablets: HashMap::new(),
|
2024-02-24 18:32:13 +01:00
|
|
|
touch: HashSet::new(),
|
2023-12-03 13:50:07 +04:00
|
|
|
|
2023-10-10 08:49:47 +04:00
|
|
|
compositor_state,
|
|
|
|
|
xdg_shell_state,
|
|
|
|
|
xdg_decoration_state,
|
|
|
|
|
kde_decoration_state,
|
|
|
|
|
layer_shell_state,
|
2023-10-13 13:30:11 +04:00
|
|
|
session_lock_state,
|
2024-01-29 19:34:12 +04:00
|
|
|
foreign_toplevel_state,
|
2025-06-14 16:17:43 +03:00
|
|
|
ext_workspace_state,
|
2024-05-21 22:43:42 +00:00
|
|
|
output_management_state,
|
2024-03-08 13:10:55 +01:00
|
|
|
screencopy_state,
|
2024-03-10 21:41:36 +04:00
|
|
|
viewporter_state,
|
2024-03-08 17:08:58 +04:00
|
|
|
xdg_foreign_state,
|
2023-10-10 08:49:47 +04:00
|
|
|
text_input_state,
|
|
|
|
|
input_method_state,
|
2025-01-18 15:26:42 +01:00
|
|
|
keyboard_shortcuts_inhibit_state,
|
2023-10-10 08:49:47 +04:00
|
|
|
virtual_keyboard_state,
|
2025-01-18 15:26:42 +01:00
|
|
|
virtual_pointer_state,
|
2023-10-10 08:49:47 +04:00
|
|
|
shm_state,
|
|
|
|
|
output_manager_state,
|
2023-12-31 12:02:39 +04:00
|
|
|
dmabuf_state,
|
2024-05-29 14:29:37 +03:00
|
|
|
fractional_scale_manager_state,
|
2023-10-10 08:49:47 +04:00
|
|
|
seat_state,
|
|
|
|
|
tablet_state,
|
|
|
|
|
pointer_gestures_state,
|
2023-12-04 18:12:12 +04:00
|
|
|
relative_pointer_state,
|
2023-12-21 16:19:16 +04:00
|
|
|
pointer_constraints_state,
|
2024-02-08 13:51:54 +04:00
|
|
|
idle_notifier_state,
|
|
|
|
|
idle_inhibit_manager_state,
|
2023-10-10 08:49:47 +04:00
|
|
|
data_device_state,
|
|
|
|
|
primary_selection_state,
|
2025-02-14 08:35:49 +03:00
|
|
|
wlr_data_control_state,
|
2025-02-14 08:47:26 +03:00
|
|
|
ext_data_control_state,
|
2023-10-10 08:49:47 +04:00
|
|
|
popups: PopupManager::default(),
|
2024-01-12 17:53:35 +04:00
|
|
|
popup_grab: None,
|
2023-10-27 16:50:02 +04:00
|
|
|
suppressed_keys: HashSet::new(),
|
2024-12-13 02:40:36 +02:00
|
|
|
suppressed_buttons: HashSet::new(),
|
2024-03-22 20:47:40 +04:00
|
|
|
bind_cooldown_timers: HashMap::new(),
|
2024-06-30 22:37:44 +05:00
|
|
|
bind_repeat_timer: Option::default(),
|
2023-10-10 08:49:47 +04:00
|
|
|
presentation_state,
|
2024-01-15 16:02:07 +04:00
|
|
|
security_context_state,
|
2024-03-03 20:36:13 +01:00
|
|
|
gamma_control_manager_state,
|
2024-06-09 21:09:28 -07:00
|
|
|
activation_state,
|
2024-08-13 09:15:57 +03:00
|
|
|
mutter_x11_interop_state,
|
2024-12-20 08:49:18 +03:00
|
|
|
#[cfg(test)]
|
|
|
|
|
single_pixel_buffer_state,
|
2023-10-10 08:49:47 +04:00
|
|
|
|
|
|
|
|
seat,
|
2024-03-17 09:21:03 +04:00
|
|
|
keyboard_focus: KeyboardFocus::Layout { surface: None },
|
2024-07-06 18:20:19 +04:00
|
|
|
layer_shell_on_demand_focus: None,
|
2024-11-21 12:48:51 +01:00
|
|
|
previously_focused_window: None,
|
2024-02-08 13:51:54 +04:00
|
|
|
idle_inhibiting_surfaces: HashSet::new(),
|
2024-02-11 10:59:57 +04:00
|
|
|
is_fdo_idle_inhibited: Arc::new(AtomicBool::new(false)),
|
2025-01-18 15:26:42 +01:00
|
|
|
keyboard_shortcuts_inhibiting_surfaces: HashMap::new(),
|
2025-07-15 15:44:10 +03:00
|
|
|
xkb_from_locale1: None,
|
2023-10-29 14:04:38 +04:00
|
|
|
cursor_manager,
|
|
|
|
|
cursor_texture_cache: Default::default(),
|
|
|
|
|
cursor_shape_manager_state,
|
2023-10-10 08:49:47 +04:00
|
|
|
dnd_icon: None,
|
2024-11-03 09:16:06 +03:00
|
|
|
pointer_contents: PointContents::default(),
|
2025-04-27 06:25:36 +00:00
|
|
|
pointer_visibility: PointerVisibility::Visible,
|
2024-10-06 20:27:04 +03:00
|
|
|
pointer_inactivity_timer: None,
|
2025-02-17 09:03:42 +03:00
|
|
|
pointer_inactivity_timer_got_reset: false,
|
2025-02-17 09:09:48 +03:00
|
|
|
notified_activity_this_iteration: false,
|
2025-04-25 10:08:26 +03:00
|
|
|
pointer_inside_hot_corner: false,
|
2023-12-05 10:24:41 +04:00
|
|
|
tablet_cursor_location: None,
|
2024-02-29 08:56:20 +04:00
|
|
|
gesture_swipe_3f_cumulative: None,
|
2025-04-25 09:48:54 +03:00
|
|
|
overview_scroll_swipe_gesture: ScrollSwipeGesture::new(),
|
2024-03-23 19:58:11 +04:00
|
|
|
vertical_wheel_tracker: ScrollTracker::new(120),
|
|
|
|
|
horizontal_wheel_tracker: ScrollTracker::new(120),
|
2024-12-13 02:40:36 +02:00
|
|
|
mods_with_mouse_binds,
|
2024-03-23 19:20:44 +04:00
|
|
|
mods_with_wheel_binds,
|
2023-10-10 08:49:47 +04:00
|
|
|
|
2024-03-23 20:23:21 +04:00
|
|
|
// 10 is copied from Clutter: DISCRETE_SCROLL_STEP.
|
|
|
|
|
vertical_finger_scroll_tracker: ScrollTracker::new(10),
|
|
|
|
|
horizontal_finger_scroll_tracker: ScrollTracker::new(10),
|
|
|
|
|
mods_with_finger_scroll_binds,
|
|
|
|
|
|
2023-10-13 13:30:11 +04:00
|
|
|
lock_state: LockState::Unlocked,
|
2025-07-14 07:34:10 -04:00
|
|
|
locked_hint: None,
|
2023-10-13 13:30:11 +04:00
|
|
|
|
2023-10-30 20:29:03 +04:00
|
|
|
screenshot_ui,
|
2024-01-18 11:15:48 +04:00
|
|
|
config_error_notification,
|
2024-01-18 19:20:46 +04:00
|
|
|
hotkey_overlay,
|
2024-01-19 08:33:54 +04:00
|
|
|
exit_confirm_dialog,
|
2023-10-30 20:29:03 +04:00
|
|
|
|
2025-02-26 14:22:27 +02:00
|
|
|
pick_window: None,
|
2025-03-06 17:24:44 -05:00
|
|
|
pick_color: None,
|
2025-02-26 14:22:27 +02:00
|
|
|
|
2024-05-02 17:52:06 +04:00
|
|
|
debug_draw_opaque_regions: false,
|
2024-05-03 10:25:51 +04:00
|
|
|
debug_draw_damage: false,
|
2024-05-02 17:52:06 +04:00
|
|
|
|
2023-10-10 10:25:26 +04:00
|
|
|
#[cfg(feature = "dbus")]
|
2023-10-10 09:55:44 +04:00
|
|
|
dbus: None,
|
2023-10-10 10:25:26 +04:00
|
|
|
#[cfg(feature = "dbus")]
|
2025-07-16 11:54:32 +03:00
|
|
|
a11y_keyboard_monitor: None,
|
|
|
|
|
#[cfg(feature = "dbus")]
|
2025-08-21 15:02:25 +03:00
|
|
|
a11y,
|
|
|
|
|
#[cfg(feature = "dbus")]
|
2023-10-10 08:49:47 +04:00
|
|
|
inhibit_power_key_fd: None,
|
2023-10-10 10:25:26 +04:00
|
|
|
|
2024-01-17 10:38:32 +04:00
|
|
|
ipc_server,
|
2024-03-27 09:46:18 +04:00
|
|
|
ipc_outputs_changed: false,
|
2024-01-17 10:38:32 +04:00
|
|
|
|
2025-06-04 08:26:51 +03:00
|
|
|
satellite: None,
|
|
|
|
|
|
2025-01-04 12:18:58 +03:00
|
|
|
pipewire: None,
|
2023-10-10 08:49:47 +04:00
|
|
|
casts: vec![],
|
2025-01-04 12:18:58 +03:00
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
|
|
|
|
pw_to_niri,
|
2024-06-27 11:36:24 +04:00
|
|
|
|
|
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
|
|
|
|
mapped_cast_output: HashMap::new(),
|
2025-03-15 11:23:01 +03:00
|
|
|
|
|
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
|
|
|
|
dynamic_cast_id_for_portal: MappedId::next(),
|
2024-10-06 20:27:04 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
niri.reset_pointer_inactivity_timer();
|
|
|
|
|
|
|
|
|
|
niri
|
2023-10-10 08:49:47 +04:00
|
|
|
}
|
|
|
|
|
|
2025-01-27 07:55:32 +03:00
|
|
|
pub fn insert_client(&mut self, client: NewClient) {
|
|
|
|
|
let NewClient {
|
|
|
|
|
client,
|
|
|
|
|
restricted,
|
|
|
|
|
credentials_unknown,
|
|
|
|
|
} = client;
|
|
|
|
|
|
|
|
|
|
let config = self.config.borrow();
|
|
|
|
|
let data = Arc::new(ClientState {
|
|
|
|
|
compositor_state: Default::default(),
|
|
|
|
|
can_view_decoration_globals: config.prefer_no_csd,
|
|
|
|
|
primary_selection_disabled: config.clipboard.disable_primary,
|
|
|
|
|
restricted,
|
|
|
|
|
credentials_unknown,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if let Err(err) = self.display_handle.insert_client(client, data) {
|
|
|
|
|
warn!("error inserting client: {err}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-10 10:25:26 +04:00
|
|
|
#[cfg(feature = "dbus")]
|
2023-10-10 09:02:33 +04:00
|
|
|
pub fn inhibit_power_key(&mut self) -> anyhow::Result<()> {
|
2024-10-17 08:38:44 +03:00
|
|
|
use std::os::fd::{AsRawFd, BorrowedFd};
|
|
|
|
|
|
|
|
|
|
use smithay::reexports::rustix::io::{fcntl_setfd, FdFlags};
|
|
|
|
|
|
2024-12-10 01:58:26 +02:00
|
|
|
let conn = zbus::blocking::Connection::system()?;
|
2023-10-10 09:02:33 +04:00
|
|
|
|
|
|
|
|
let message = conn.call_method(
|
|
|
|
|
Some("org.freedesktop.login1"),
|
|
|
|
|
"/org/freedesktop/login1",
|
|
|
|
|
Some("org.freedesktop.login1.Manager"),
|
|
|
|
|
"Inhibit",
|
|
|
|
|
&("handle-power-key", "niri", "Power key handling", "block"),
|
|
|
|
|
)?;
|
|
|
|
|
|
2024-12-10 01:58:26 +02:00
|
|
|
let fd: zbus::zvariant::OwnedFd = message.body().deserialize()?;
|
2024-10-17 08:38:44 +03:00
|
|
|
|
|
|
|
|
// Don't leak the fd to child processes.
|
|
|
|
|
let borrowed = unsafe { BorrowedFd::borrow_raw(fd.as_raw_fd()) };
|
|
|
|
|
if let Err(err) = fcntl_setfd(borrowed, FdFlags::CLOEXEC) {
|
|
|
|
|
warn!("error setting CLOEXEC on inhibit fd: {err:?}");
|
|
|
|
|
};
|
|
|
|
|
|
2023-10-10 09:02:33 +04:00
|
|
|
self.inhibit_power_key_fd = Some(fd);
|
|
|
|
|
|
|
|
|
|
Ok(())
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2024-01-16 08:43:28 +04:00
|
|
|
/// Repositions all outputs, optionally adding a new output.
|
|
|
|
|
pub fn reposition_outputs(&mut self, new_output: Option<&Output>) {
|
|
|
|
|
let _span = tracy_client::span!("Niri::reposition_outputs");
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
struct Data {
|
|
|
|
|
output: Output,
|
2024-09-05 20:10:01 +03:00
|
|
|
name: OutputName,
|
2024-01-16 08:43:28 +04:00
|
|
|
position: Option<Point<i32, Logical>>,
|
|
|
|
|
config: Option<niri_config::Position>,
|
|
|
|
|
}
|
2023-09-21 13:39:37 +04:00
|
|
|
|
2024-01-16 08:43:28 +04:00
|
|
|
let config = self.config.borrow();
|
|
|
|
|
let mut outputs = vec![];
|
|
|
|
|
for output in self.global_space.outputs().chain(new_output) {
|
2024-09-03 12:13:04 +03:00
|
|
|
let name = output.user_data().get::<OutputName>().unwrap();
|
2024-01-16 08:43:28 +04:00
|
|
|
let position = self.global_space.output_geometry(output).map(|geo| geo.loc);
|
2024-09-03 12:13:04 +03:00
|
|
|
let config = config.outputs.find(name).and_then(|c| c.position);
|
2024-01-16 08:43:28 +04:00
|
|
|
|
|
|
|
|
outputs.push(Data {
|
|
|
|
|
output: output.clone(),
|
2024-09-05 20:10:01 +03:00
|
|
|
name: name.clone(),
|
2024-01-16 08:43:28 +04:00
|
|
|
position,
|
|
|
|
|
config,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
drop(config);
|
2023-09-30 11:33:02 +04:00
|
|
|
|
2024-01-16 08:43:28 +04:00
|
|
|
for Data { output, .. } in &outputs {
|
|
|
|
|
self.global_space.unmap_output(output);
|
|
|
|
|
}
|
2023-09-30 11:33:02 +04:00
|
|
|
|
2024-01-16 08:43:28 +04:00
|
|
|
// Connectors can appear in udev in any order. If we sort by name then we get output
|
|
|
|
|
// positioning that does not depend on the order they appeared.
|
|
|
|
|
//
|
2024-09-05 20:10:01 +03:00
|
|
|
// This sorting first compares by make/model/serial so that it is stable regardless of the
|
|
|
|
|
// connector name. However, if make/model/serial is equal or unknown, then it does fall
|
|
|
|
|
// back to comparing the connector name, which should always be unique.
|
|
|
|
|
outputs.sort_unstable_by(|a, b| a.name.compare(&b.name));
|
2024-01-16 08:43:28 +04:00
|
|
|
|
|
|
|
|
// Place all outputs with explicitly configured position first, then the unconfigured ones.
|
|
|
|
|
outputs.sort_by_key(|d| d.config.is_none());
|
2023-09-30 11:33:02 +04:00
|
|
|
|
2024-01-16 08:43:28 +04:00
|
|
|
trace!(
|
|
|
|
|
"placing outputs in order: {:?}",
|
2024-09-05 20:10:01 +03:00
|
|
|
outputs.iter().map(|d| &d.name.connector)
|
2023-09-30 11:33:02 +04:00
|
|
|
);
|
2024-01-16 08:43:28 +04:00
|
|
|
|
2024-12-15 16:04:42 +01:00
|
|
|
self.sorted_outputs = outputs
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|Data { output, .. }| output.clone())
|
|
|
|
|
.collect();
|
|
|
|
|
|
2024-01-16 08:43:28 +04:00
|
|
|
for data in outputs.into_iter() {
|
|
|
|
|
let Data {
|
|
|
|
|
output,
|
|
|
|
|
name,
|
|
|
|
|
position,
|
|
|
|
|
config,
|
|
|
|
|
} = data;
|
|
|
|
|
|
2024-06-17 09:16:28 +03:00
|
|
|
let size = output_size(&output).to_i32_round();
|
2024-01-16 08:43:28 +04:00
|
|
|
|
|
|
|
|
let new_position = config
|
|
|
|
|
.map(|pos| Point::from((pos.x, pos.y)))
|
|
|
|
|
.filter(|pos| {
|
|
|
|
|
// Ensure that the requested position does not overlap any existing output.
|
2025-01-04 10:14:51 +03:00
|
|
|
let target_geom = Rectangle::new(*pos, size);
|
2024-01-16 08:43:28 +04:00
|
|
|
|
|
|
|
|
let overlap = self
|
|
|
|
|
.global_space
|
|
|
|
|
.outputs()
|
|
|
|
|
.map(|output| self.global_space.output_geometry(output).unwrap())
|
|
|
|
|
.find(|geom| geom.overlaps(target_geom));
|
|
|
|
|
|
|
|
|
|
if let Some(overlap) = overlap {
|
|
|
|
|
warn!(
|
2024-09-05 20:10:01 +03:00
|
|
|
"output {} at x={} y={} sized {}x{} \
|
2024-01-16 08:43:28 +04:00
|
|
|
overlaps an existing output at x={} y={} sized {}x{}, \
|
|
|
|
|
falling back to automatic placement",
|
2024-09-05 20:10:01 +03:00
|
|
|
name.connector,
|
2024-01-16 08:43:28 +04:00
|
|
|
pos.x,
|
|
|
|
|
pos.y,
|
|
|
|
|
size.w,
|
|
|
|
|
size.h,
|
|
|
|
|
overlap.loc.x,
|
|
|
|
|
overlap.loc.y,
|
|
|
|
|
overlap.size.w,
|
|
|
|
|
overlap.size.h,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
false
|
|
|
|
|
} else {
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or_else(|| {
|
|
|
|
|
let x = self
|
|
|
|
|
.global_space
|
|
|
|
|
.outputs()
|
|
|
|
|
.map(|output| self.global_space.output_geometry(output).unwrap())
|
|
|
|
|
.map(|geom| geom.loc.x + geom.size.w)
|
|
|
|
|
.max()
|
|
|
|
|
.unwrap_or(0);
|
|
|
|
|
|
|
|
|
|
Point::from((x, 0))
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
self.global_space.map_output(&output, new_position);
|
|
|
|
|
|
|
|
|
|
// By passing new_output as an Option, rather than mapping it into a bogus location
|
|
|
|
|
// in global_space, we ensure that this branch always runs for it.
|
|
|
|
|
if Some(new_position) != position {
|
|
|
|
|
debug!(
|
2024-09-05 20:10:01 +03:00
|
|
|
"putting output {} at x={} y={}",
|
|
|
|
|
name.connector, new_position.x, new_position.y
|
2024-01-16 08:43:28 +04:00
|
|
|
);
|
|
|
|
|
output.change_current_state(None, None, None, Some(new_position));
|
2024-03-27 09:46:18 +04:00
|
|
|
self.ipc_outputs_changed = true;
|
2024-03-23 14:16:36 +04:00
|
|
|
self.queue_redraw(&output);
|
2024-01-16 08:43:28 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-14 09:23:15 +04:00
|
|
|
pub fn add_output(&mut self, output: Output, refresh_interval: Option<Duration>, vrr: bool) {
|
2024-01-16 08:43:28 +04:00
|
|
|
let global = output.create_global::<State>(&self.display_handle);
|
|
|
|
|
|
2024-09-03 12:13:04 +03:00
|
|
|
let name = output.user_data().get::<OutputName>().unwrap();
|
2024-01-28 14:25:40 +01:00
|
|
|
|
|
|
|
|
let config = self.config.borrow();
|
2024-09-03 12:13:04 +03:00
|
|
|
let c = config.outputs.find(name);
|
2024-06-18 11:01:18 +03:00
|
|
|
let scale = c.and_then(|c| c.scale).map(|s| s.0).unwrap_or_else(|| {
|
2024-04-24 10:04:19 +04:00
|
|
|
let size_mm = output.physical_properties().size;
|
|
|
|
|
let resolution = output.current_mode().unwrap().size;
|
|
|
|
|
guess_monitor_scale(size_mm, resolution)
|
|
|
|
|
});
|
2024-06-17 09:21:37 +03:00
|
|
|
let scale = closest_representable_scale(scale.clamp(0.1, 10.));
|
2024-06-17 09:18:54 +03:00
|
|
|
|
2024-03-27 17:03:17 +04:00
|
|
|
let mut transform = c
|
|
|
|
|
.map(|c| ipc_transform_to_smithay(c.transform))
|
|
|
|
|
.unwrap_or(Transform::Normal);
|
2024-07-25 23:41:33 +05:30
|
|
|
|
2025-05-06 16:51:18 +03:00
|
|
|
let background_color = c
|
2025-05-06 17:12:07 +03:00
|
|
|
.and_then(|c| c.background_color)
|
|
|
|
|
.unwrap_or(config.layout.background_color)
|
2024-07-25 23:41:33 +05:30
|
|
|
.to_array_unpremul();
|
|
|
|
|
|
2025-04-25 08:53:16 +03:00
|
|
|
let mut backdrop_color = c
|
2025-04-28 07:53:03 +03:00
|
|
|
.and_then(|c| c.backdrop_color)
|
|
|
|
|
.unwrap_or(config.overview.backdrop_color)
|
2025-04-25 08:53:16 +03:00
|
|
|
.to_array_unpremul();
|
|
|
|
|
backdrop_color[3] = 1.;
|
|
|
|
|
|
2024-01-29 10:17:26 +04:00
|
|
|
// FIXME: fix winit damage on other transforms.
|
2024-09-03 12:13:04 +03:00
|
|
|
if name.connector == "winit" {
|
2024-01-29 10:17:26 +04:00
|
|
|
transform = Transform::Flipped180;
|
|
|
|
|
}
|
2024-01-28 14:25:40 +01:00
|
|
|
drop(config);
|
2024-01-16 09:42:31 +04:00
|
|
|
|
2024-01-28 14:25:40 +01:00
|
|
|
// Set scale and transform before adding to the layout since that will read the output size.
|
|
|
|
|
output.change_current_state(
|
|
|
|
|
None,
|
|
|
|
|
Some(transform),
|
2024-05-29 14:29:37 +03:00
|
|
|
Some(output::Scale::Fractional(scale)),
|
2024-01-28 14:25:40 +01:00
|
|
|
None,
|
|
|
|
|
);
|
2024-01-16 09:42:31 +04:00
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
self.layout.add_output(output.clone());
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-10-13 13:30:11 +04:00
|
|
|
let lock_render_state = if self.is_locked() {
|
|
|
|
|
// We haven't rendered anything yet so it's as good as locked.
|
|
|
|
|
LockRenderState::Locked
|
|
|
|
|
} else {
|
|
|
|
|
LockRenderState::Unlocked
|
|
|
|
|
};
|
|
|
|
|
|
2025-04-28 07:53:03 +03:00
|
|
|
let size = output_size(&output);
|
2023-08-13 12:46:53 +04:00
|
|
|
let state = OutputState {
|
2023-09-21 13:39:37 +04:00
|
|
|
global,
|
2023-09-30 17:13:56 +04:00
|
|
|
redraw_state: RedrawState::Idle,
|
2024-08-22 18:58:07 +10:00
|
|
|
on_demand_vrr_enabled: false,
|
2023-09-30 17:13:56 +04:00
|
|
|
unfinished_animations_remain: false,
|
2024-04-14 09:23:15 +04:00
|
|
|
frame_clock: FrameClock::new(refresh_interval, vrr),
|
2024-03-02 08:10:05 +04:00
|
|
|
last_drm_sequence: None,
|
|
|
|
|
frame_callback_sequence: 0,
|
2024-07-25 23:41:33 +05:30
|
|
|
background_buffer: SolidColorBuffer::new(size, background_color),
|
2025-04-25 08:53:16 +03:00
|
|
|
backdrop_buffer: SolidColorBuffer::new(size, backdrop_color),
|
2023-10-13 13:30:11 +04:00
|
|
|
lock_render_state,
|
|
|
|
|
lock_surface: None,
|
|
|
|
|
lock_color_buffer: SolidColorBuffer::new(size, CLEAR_COLOR_LOCKED),
|
2024-05-07 22:06:43 +04:00
|
|
|
screen_transition: None,
|
2024-05-03 10:25:51 +04:00
|
|
|
debug_damage_tracker: OutputDamageTracker::from_output(&output),
|
2023-08-13 12:46:53 +04:00
|
|
|
};
|
2023-10-03 17:02:07 +04:00
|
|
|
let rv = self.output_state.insert(output.clone(), state);
|
|
|
|
|
assert!(rv.is_none(), "output was already tracked");
|
2024-01-16 08:43:28 +04:00
|
|
|
|
|
|
|
|
// Must be last since it will call queue_redraw(output) which needs things to be filled-in.
|
|
|
|
|
self.reposition_outputs(Some(&output));
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn remove_output(&mut self, output: &Output) {
|
2024-01-16 13:28:29 +04:00
|
|
|
for layer in layer_map_for_output(output).layers() {
|
|
|
|
|
layer.layer_surface().send_close();
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
self.layout.remove_output(output);
|
2023-09-21 13:39:37 +04:00
|
|
|
self.global_space.unmap_output(output);
|
2024-01-16 08:43:28 +04:00
|
|
|
self.reposition_outputs(None);
|
2024-03-14 08:32:23 +04:00
|
|
|
self.gamma_control_manager_state.output_removed(output);
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-09-30 17:13:56 +04:00
|
|
|
let state = self.output_state.remove(output).unwrap();
|
2023-10-03 17:02:07 +04:00
|
|
|
|
2023-09-30 17:13:56 +04:00
|
|
|
match state.redraw_state {
|
|
|
|
|
RedrawState::Idle => (),
|
2024-03-23 12:46:26 +04:00
|
|
|
RedrawState::Queued => (),
|
2023-09-30 17:13:56 +04:00
|
|
|
RedrawState::WaitingForVBlank { .. } => (),
|
|
|
|
|
RedrawState::WaitingForEstimatedVBlank(token) => self.event_loop.remove(token),
|
2024-03-23 12:46:26 +04:00
|
|
|
RedrawState::WaitingForEstimatedVBlankAndQueued(token) => self.event_loop.remove(token),
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2024-06-27 11:36:24 +04:00
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
|
|
|
|
self.stop_casts_for_target(CastTarget::Output(output.downgrade()));
|
|
|
|
|
|
2024-07-30 13:38:25 +10:00
|
|
|
self.remove_screencopy_output(output);
|
|
|
|
|
|
2023-09-21 13:39:37 +04:00
|
|
|
// Disable the output global and remove some time later to give the clients some time to
|
|
|
|
|
// process it.
|
|
|
|
|
let global = state.global;
|
|
|
|
|
self.display_handle.disable_global::<State>(global.clone());
|
|
|
|
|
self.event_loop
|
|
|
|
|
.insert_source(
|
|
|
|
|
Timer::from_duration(Duration::from_secs(10)),
|
2023-09-24 17:30:06 +04:00
|
|
|
move |_, _, state| {
|
|
|
|
|
state
|
2023-09-21 13:39:37 +04:00
|
|
|
.niri
|
|
|
|
|
.display_handle
|
|
|
|
|
.remove_global::<State>(global.clone());
|
|
|
|
|
TimeoutAction::Drop
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
2023-10-13 13:30:11 +04:00
|
|
|
|
|
|
|
|
match mem::take(&mut self.lock_state) {
|
|
|
|
|
LockState::Locking(confirmation) => {
|
|
|
|
|
// We're locking and an output was removed, check if the requirements are now met.
|
|
|
|
|
let all_locked = self
|
|
|
|
|
.output_state
|
|
|
|
|
.values()
|
|
|
|
|
.all(|state| state.lock_render_state == LockRenderState::Locked);
|
|
|
|
|
|
|
|
|
|
if all_locked {
|
2024-02-17 07:47:06 +04:00
|
|
|
let lock = confirmation.ext_session_lock().clone();
|
2023-10-13 13:30:11 +04:00
|
|
|
confirmation.lock();
|
2024-02-17 07:47:06 +04:00
|
|
|
self.lock_state = LockState::Locked(lock);
|
2023-10-13 13:30:11 +04:00
|
|
|
} else {
|
|
|
|
|
// Still waiting.
|
|
|
|
|
self.lock_state = LockState::Locking(confirmation);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-13 18:34:47 +03:00
|
|
|
lock_state => {
|
|
|
|
|
self.lock_state = lock_state;
|
|
|
|
|
self.maybe_continue_to_locking();
|
|
|
|
|
}
|
2023-10-13 13:30:11 +04:00
|
|
|
}
|
2023-10-30 20:29:03 +04:00
|
|
|
|
|
|
|
|
if self.screenshot_ui.close() {
|
2023-10-31 07:16:36 +04:00
|
|
|
self.cursor_manager
|
|
|
|
|
.set_cursor_image(CursorImageStatus::default_named());
|
2023-10-30 20:29:03 +04:00
|
|
|
self.queue_redraw_all();
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2024-03-23 14:16:36 +04:00
|
|
|
pub fn output_resized(&mut self, output: &Output) {
|
2025-04-28 07:53:03 +03:00
|
|
|
let output_size = output_size(output);
|
2024-09-08 22:05:56 +03:00
|
|
|
let scale = output.current_scale();
|
|
|
|
|
let transform = output.current_transform();
|
2023-10-27 08:34:00 +04:00
|
|
|
|
2024-09-08 22:05:56 +03:00
|
|
|
{
|
|
|
|
|
let mut layer_map = layer_map_for_output(output);
|
|
|
|
|
for layer in layer_map.layers() {
|
|
|
|
|
layer.with_surfaces(|surface, data| {
|
|
|
|
|
send_scale_transform(surface, data, scale, transform);
|
|
|
|
|
});
|
2025-05-12 08:16:01 +03:00
|
|
|
|
|
|
|
|
if let Some(mapped) = self.mapped_layer_surfaces.get_mut(layer) {
|
|
|
|
|
mapped.update_sizes(output_size, scale.fractional_scale());
|
|
|
|
|
}
|
2024-09-08 22:05:56 +03:00
|
|
|
}
|
2024-09-08 22:33:09 +03:00
|
|
|
layer_map.arrange();
|
2024-09-08 22:05:56 +03:00
|
|
|
}
|
|
|
|
|
|
2024-03-23 14:16:36 +04:00
|
|
|
self.layout.update_output_size(output);
|
2023-10-13 13:30:11 +04:00
|
|
|
|
2024-03-23 14:16:36 +04:00
|
|
|
if let Some(state) = self.output_state.get_mut(output) {
|
2023-10-27 08:34:00 +04:00
|
|
|
state.background_buffer.resize(output_size);
|
2025-04-25 08:53:16 +03:00
|
|
|
state.backdrop_buffer.resize(output_size);
|
2023-10-27 08:34:00 +04:00
|
|
|
|
|
|
|
|
state.lock_color_buffer.resize(output_size);
|
2025-03-13 18:34:47 +03:00
|
|
|
if let Some(lock_surface) = &state.lock_surface {
|
|
|
|
|
configure_lock_surface(lock_surface, output);
|
2023-10-13 13:30:11 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-30 20:29:03 +04:00
|
|
|
// If the output size changed with an open screenshot UI, close the screenshot UI.
|
2024-03-23 14:16:36 +04:00
|
|
|
if let Some((old_size, old_scale, old_transform)) = self.screenshot_ui.output_size(output) {
|
2023-10-30 20:29:03 +04:00
|
|
|
let output_mode = output.current_mode().unwrap();
|
2024-01-28 14:25:40 +01:00
|
|
|
let size = transform.transform_size(output_mode.size);
|
2024-06-01 12:27:30 +03:00
|
|
|
let scale = output.current_scale().fractional_scale();
|
2024-01-28 14:25:40 +01:00
|
|
|
// FIXME: scale changes and transform flips shouldn't matter but they currently do since
|
|
|
|
|
// I haven't quite figured out how to draw the screenshot textures in
|
|
|
|
|
// physical coordinates.
|
|
|
|
|
if old_size != size || old_scale != scale || old_transform != transform {
|
2023-10-30 20:29:03 +04:00
|
|
|
self.screenshot_ui.close();
|
2023-10-31 07:16:36 +04:00
|
|
|
self.cursor_manager
|
|
|
|
|
.set_cursor_image(CursorImageStatus::default_named());
|
2023-10-30 20:29:03 +04:00
|
|
|
self.queue_redraw_all();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
self.queue_redraw(output);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-28 16:18:52 +01:00
|
|
|
pub fn deactivate_monitors(&mut self, backend: &mut Backend) {
|
2023-10-09 18:37:43 +04:00
|
|
|
if !self.monitors_active {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.monitors_active = false;
|
|
|
|
|
backend.set_monitors_active(false);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-28 16:18:52 +01:00
|
|
|
pub fn activate_monitors(&mut self, backend: &mut Backend) {
|
2023-10-09 18:37:43 +04:00
|
|
|
if self.monitors_active {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.monitors_active = true;
|
|
|
|
|
backend.set_monitors_active(true);
|
|
|
|
|
|
|
|
|
|
self.queue_redraw_all();
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
pub fn output_under(&self, pos: Point<f64, Logical>) -> Option<(&Output, Point<f64, Logical>)> {
|
|
|
|
|
let output = self.global_space.output_under(pos).next()?;
|
|
|
|
|
let pos_within_output = pos
|
|
|
|
|
- self
|
|
|
|
|
.global_space
|
|
|
|
|
.output_geometry(output)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.loc
|
|
|
|
|
.to_f64();
|
|
|
|
|
|
|
|
|
|
Some((output, pos_within_output))
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-24 22:11:04 +03:00
|
|
|
pub fn is_sticky_obscured_under(
|
2025-04-12 09:43:36 +03:00
|
|
|
&self,
|
|
|
|
|
output: &Output,
|
|
|
|
|
pos_within_output: Point<f64, Logical>,
|
|
|
|
|
) -> bool {
|
2025-01-03 12:22:43 +03:00
|
|
|
// The ordering here must be consistent with the ordering in render() so that input is
|
|
|
|
|
// consistent with the visuals.
|
|
|
|
|
|
2024-01-12 08:45:39 +04:00
|
|
|
// Check if some layer-shell surface is on top.
|
|
|
|
|
let layers = layer_map_for_output(output);
|
2025-01-03 12:22:43 +03:00
|
|
|
let layer_surface_under = |layer, popup| {
|
2024-12-21 15:34:14 +01:00
|
|
|
layers
|
2025-01-03 12:22:43 +03:00
|
|
|
.layers_on(layer)
|
|
|
|
|
.rev()
|
|
|
|
|
.find_map(|layer| {
|
2025-05-12 08:16:01 +03:00
|
|
|
let mapped = self.mapped_layer_surfaces.get(layer)?;
|
|
|
|
|
|
|
|
|
|
let mut layer_pos_within_output =
|
2024-12-21 15:34:14 +01:00
|
|
|
layers.layer_geometry(layer).unwrap().loc.to_f64();
|
2025-05-12 08:16:01 +03:00
|
|
|
layer_pos_within_output += mapped.bob_offset();
|
|
|
|
|
|
2025-01-03 12:22:43 +03:00
|
|
|
let surface_type = if popup {
|
|
|
|
|
WindowSurfaceType::POPUP
|
|
|
|
|
} else {
|
|
|
|
|
WindowSurfaceType::TOPLEVEL
|
|
|
|
|
} | WindowSurfaceType::SUBSURFACE;
|
|
|
|
|
layer.surface_under(pos_within_output - layer_pos_within_output, surface_type)
|
2024-12-21 15:34:14 +01:00
|
|
|
})
|
|
|
|
|
.is_some()
|
|
|
|
|
};
|
2025-01-03 12:22:43 +03:00
|
|
|
|
|
|
|
|
let layer_toplevel_under = |layer| layer_surface_under(layer, false);
|
|
|
|
|
let layer_popup_under = |layer| layer_surface_under(layer, true);
|
|
|
|
|
|
|
|
|
|
if layer_popup_under(Layer::Overlay) || layer_toplevel_under(Layer::Overlay) {
|
2025-04-12 09:43:36 +03:00
|
|
|
return true;
|
2024-01-12 08:45:39 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mon = self.layout.monitor_for_output(output).unwrap();
|
2025-04-12 09:44:49 +03:00
|
|
|
if mon.render_above_top_layer() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-29 08:24:24 +03:00
|
|
|
let hot_corners = self.config.borrow().gestures.hot_corners;
|
|
|
|
|
if !hot_corners.off {
|
|
|
|
|
let hot_corner = Rectangle::from_size(Size::from((1., 1.)));
|
|
|
|
|
if hot_corner.contains(pos_within_output) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2025-04-25 10:08:26 +03:00
|
|
|
}
|
|
|
|
|
|
2025-04-24 22:11:04 +03:00
|
|
|
if layer_popup_under(Layer::Top) || layer_toplevel_under(Layer::Top) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn is_layout_obscured_under(
|
|
|
|
|
&self,
|
|
|
|
|
output: &Output,
|
|
|
|
|
pos_within_output: Point<f64, Logical>,
|
|
|
|
|
) -> bool {
|
2025-04-25 09:36:50 +03:00
|
|
|
if self.layout.is_overview_open() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-24 22:11:04 +03:00
|
|
|
// Check if some layer-shell surface is on top.
|
|
|
|
|
let layers = layer_map_for_output(output);
|
|
|
|
|
let layer_popup_under = |layer| {
|
|
|
|
|
layers
|
|
|
|
|
.layers_on(layer)
|
|
|
|
|
.rev()
|
2025-04-25 08:53:16 +03:00
|
|
|
.find_map(|layer_surface| {
|
2025-05-06 16:51:18 +03:00
|
|
|
let mapped = self.mapped_layer_surfaces.get(layer_surface)?;
|
|
|
|
|
if mapped.place_within_backdrop() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-25 08:53:16 +03:00
|
|
|
let mut layer_pos_within_output =
|
|
|
|
|
layers.layer_geometry(layer_surface).unwrap().loc.to_f64();
|
2025-05-12 08:16:01 +03:00
|
|
|
layer_pos_within_output += mapped.bob_offset();
|
2025-04-25 08:53:16 +03:00
|
|
|
|
|
|
|
|
// Background and bottom layers move together with the workspaces.
|
|
|
|
|
let mon = self.layout.monitor_for_output(output)?;
|
|
|
|
|
let (_, geo) = mon.workspace_under(pos_within_output)?;
|
|
|
|
|
layer_pos_within_output += geo.loc;
|
|
|
|
|
|
2025-04-24 22:11:04 +03:00
|
|
|
let surface_type = WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE;
|
2025-04-25 08:53:16 +03:00
|
|
|
layer_surface
|
|
|
|
|
.surface_under(pos_within_output - layer_pos_within_output, surface_type)
|
2025-04-24 22:11:04 +03:00
|
|
|
})
|
|
|
|
|
.is_some()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if layer_popup_under(Layer::Bottom) || layer_popup_under(Layer::Background) {
|
2025-04-12 09:43:36 +03:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-25 09:36:50 +03:00
|
|
|
/// Returns the workspace under the position to be activated.
|
|
|
|
|
///
|
|
|
|
|
/// The return value is an output and a workspace index on it.
|
|
|
|
|
pub fn workspace_under(
|
|
|
|
|
&self,
|
|
|
|
|
extended_bounds: bool,
|
|
|
|
|
pos: Point<f64, Logical>,
|
|
|
|
|
) -> Option<(Output, &Workspace<Mapped>)> {
|
2025-07-16 14:36:58 +03:00
|
|
|
if self.exit_confirm_dialog.is_open() || self.is_locked() || self.screenshot_ui.is_open() {
|
2025-04-25 09:36:50 +03:00
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let (output, pos_within_output) = self.output_under(pos)?;
|
|
|
|
|
|
|
|
|
|
if self.is_sticky_obscured_under(output, pos_within_output) {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.is_layout_obscured_under(output, pos_within_output) {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let ws = self
|
|
|
|
|
.layout
|
|
|
|
|
.workspace_under(extended_bounds, output, pos_within_output)?;
|
|
|
|
|
Some((output.clone(), ws))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn workspace_under_cursor(
|
|
|
|
|
&self,
|
|
|
|
|
extended_bounds: bool,
|
|
|
|
|
) -> Option<(Output, &Workspace<Mapped>)> {
|
|
|
|
|
let pos = self.seat.get_pointer().unwrap().current_location();
|
|
|
|
|
self.workspace_under(extended_bounds, pos)
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-12 09:43:36 +03:00
|
|
|
/// Returns the window under the position to be activated.
|
|
|
|
|
///
|
|
|
|
|
/// The cursor may be inside the window's activation region, but not within the window's input
|
|
|
|
|
/// region.
|
|
|
|
|
pub fn window_under(&self, pos: Point<f64, Logical>) -> Option<&Mapped> {
|
2025-07-16 14:36:58 +03:00
|
|
|
if self.exit_confirm_dialog.is_open() || self.is_locked() || self.screenshot_ui.is_open() {
|
2025-04-12 09:43:36 +03:00
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let (output, pos_within_output) = self.output_under(pos)?;
|
|
|
|
|
|
2025-04-24 22:11:04 +03:00
|
|
|
if self.is_sticky_obscured_under(output, pos_within_output) {
|
2024-01-12 08:45:39 +04:00
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-24 22:04:49 +03:00
|
|
|
if let Some((window, _loc)) = self
|
|
|
|
|
.layout
|
|
|
|
|
.interactive_moved_window_under(output, pos_within_output)
|
|
|
|
|
{
|
|
|
|
|
return Some(window);
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-24 22:11:04 +03:00
|
|
|
if self.is_layout_obscured_under(output, pos_within_output) {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
let (window, _loc) = self.layout.window_under(output, pos_within_output)?;
|
2023-08-13 19:55:37 +04:00
|
|
|
Some(window)
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
|
|
|
|
|
2023-12-28 08:45:12 +04:00
|
|
|
/// Returns the window under the cursor to be activated.
|
|
|
|
|
///
|
|
|
|
|
/// The cursor may be inside the window's activation region, but not within the window's input
|
|
|
|
|
/// region.
|
2024-03-19 14:41:17 +04:00
|
|
|
pub fn window_under_cursor(&self) -> Option<&Mapped> {
|
2023-12-05 10:24:41 +04:00
|
|
|
let pos = self.seat.get_pointer().unwrap().current_location();
|
|
|
|
|
self.window_under(pos)
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-03 08:50:17 +03:00
|
|
|
/// Returns contents under the given point.
|
2023-08-13 12:46:53 +04:00
|
|
|
///
|
2024-11-03 08:50:17 +03:00
|
|
|
/// We don't have a proper global space for all windows, so this function converts window
|
|
|
|
|
/// locations to global space according to where they are rendered.
|
|
|
|
|
///
|
|
|
|
|
/// This function does not take pointer or touch grabs into account.
|
2025-02-10 08:03:39 +03:00
|
|
|
pub fn contents_under(&self, pos: Point<f64, Logical>) -> PointContents {
|
2024-11-03 08:50:17 +03:00
|
|
|
let mut rv = PointContents::default();
|
2024-03-18 17:50:31 +04:00
|
|
|
|
|
|
|
|
let Some((output, pos_within_output)) = self.output_under(pos) else {
|
|
|
|
|
return rv;
|
|
|
|
|
};
|
|
|
|
|
rv.output = Some(output.clone());
|
2024-03-18 17:32:51 +04:00
|
|
|
let output_pos_in_global_space = self.global_space.output_geometry(output).unwrap().loc;
|
2023-10-15 16:30:52 +04:00
|
|
|
|
2025-01-03 12:22:43 +03:00
|
|
|
// The ordering here must be consistent with the ordering in render() so that input is
|
|
|
|
|
// consistent with the visuals.
|
|
|
|
|
|
2025-07-16 14:36:58 +03:00
|
|
|
if self.exit_confirm_dialog.is_open() {
|
|
|
|
|
return rv;
|
|
|
|
|
} else if self.is_locked() {
|
2024-03-18 17:50:31 +04:00
|
|
|
let Some(state) = self.output_state.get(output) else {
|
|
|
|
|
return rv;
|
|
|
|
|
};
|
|
|
|
|
let Some(surface) = state.lock_surface.as_ref() else {
|
|
|
|
|
return rv;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
rv.surface = under_from_surface_tree(
|
2023-10-13 13:30:11 +04:00
|
|
|
surface.wl_surface(),
|
2024-03-18 17:50:31 +04:00
|
|
|
pos_within_output,
|
|
|
|
|
// We put lock surfaces at (0, 0).
|
2023-10-13 13:30:11 +04:00
|
|
|
(0, 0),
|
|
|
|
|
WindowSurfaceType::ALL,
|
2024-03-18 17:50:31 +04:00
|
|
|
)
|
|
|
|
|
.map(|(surface, pos_within_output)| {
|
2024-06-17 09:16:28 +03:00
|
|
|
(
|
|
|
|
|
surface,
|
|
|
|
|
(pos_within_output + output_pos_in_global_space).to_f64(),
|
|
|
|
|
)
|
2023-10-13 13:30:11 +04:00
|
|
|
});
|
2024-03-18 17:50:31 +04:00
|
|
|
|
|
|
|
|
return rv;
|
2023-10-13 13:30:11 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-30 20:29:03 +04:00
|
|
|
if self.screenshot_ui.is_open() {
|
2024-03-18 17:50:31 +04:00
|
|
|
return rv;
|
2023-10-30 20:29:03 +04:00
|
|
|
}
|
|
|
|
|
|
2023-11-01 10:13:16 +01:00
|
|
|
let layers = layer_map_for_output(output);
|
2025-01-03 12:22:43 +03:00
|
|
|
let layer_surface_under = |layer, popup| {
|
2023-11-01 10:13:16 +01:00
|
|
|
layers
|
2025-01-03 12:22:43 +03:00
|
|
|
.layers_on(layer)
|
|
|
|
|
.rev()
|
2025-04-25 08:53:16 +03:00
|
|
|
.find_map(|layer_surface| {
|
2025-05-06 16:51:18 +03:00
|
|
|
let mapped = self.mapped_layer_surfaces.get(layer_surface)?;
|
|
|
|
|
if mapped.place_within_backdrop() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-25 08:53:16 +03:00
|
|
|
let mut layer_pos_within_output =
|
|
|
|
|
layers.layer_geometry(layer_surface).unwrap().loc.to_f64();
|
2025-05-12 08:16:01 +03:00
|
|
|
layer_pos_within_output += mapped.bob_offset();
|
2025-04-25 08:53:16 +03:00
|
|
|
|
|
|
|
|
// Background and bottom layers move together with the workspaces.
|
|
|
|
|
if matches!(layer, Layer::Background | Layer::Bottom) {
|
|
|
|
|
let mon = self.layout.monitor_for_output(output)?;
|
|
|
|
|
let (_, geo) = mon.workspace_under(pos_within_output)?;
|
|
|
|
|
layer_pos_within_output += geo.loc;
|
2025-04-25 09:36:50 +03:00
|
|
|
// Don't need to deal with zoom here because in the overview background and
|
|
|
|
|
// bottom layers don't receive input.
|
2025-04-25 08:53:16 +03:00
|
|
|
}
|
|
|
|
|
|
2025-01-03 12:22:43 +03:00
|
|
|
let surface_type = if popup {
|
|
|
|
|
WindowSurfaceType::POPUP
|
|
|
|
|
} else {
|
|
|
|
|
WindowSurfaceType::TOPLEVEL
|
|
|
|
|
} | WindowSurfaceType::SUBSURFACE;
|
2025-04-25 08:53:16 +03:00
|
|
|
|
|
|
|
|
layer_surface
|
2025-01-03 12:22:43 +03:00
|
|
|
.surface_under(pos_within_output - layer_pos_within_output, surface_type)
|
2023-11-01 10:13:16 +01:00
|
|
|
.map(|(surface, pos_within_layer)| {
|
2024-07-06 18:17:48 +04:00
|
|
|
(
|
|
|
|
|
(surface, pos_within_layer.to_f64() + layer_pos_within_output),
|
2025-04-25 08:53:16 +03:00
|
|
|
layer_surface,
|
2024-07-06 18:17:48 +04:00
|
|
|
)
|
2023-11-01 10:13:16 +01:00
|
|
|
})
|
|
|
|
|
})
|
2025-02-10 08:03:39 +03:00
|
|
|
.map(|(s, l)| (Some(s), (None, Some(l.clone()))))
|
2023-11-01 10:13:16 +01:00
|
|
|
};
|
2023-08-13 19:55:37 +04:00
|
|
|
|
2025-01-03 12:22:43 +03:00
|
|
|
let layer_toplevel_under = |layer| layer_surface_under(layer, false);
|
|
|
|
|
let layer_popup_under = |layer| layer_surface_under(layer, true);
|
|
|
|
|
|
2025-04-24 21:36:47 +03:00
|
|
|
let mapped_hit_data = |(mapped, hit): (&Mapped, HitType)| {
|
|
|
|
|
let window = &mapped.window;
|
|
|
|
|
let surface_and_pos = if let HitType::Input { win_pos } = hit {
|
|
|
|
|
let win_pos_within_output = win_pos;
|
|
|
|
|
window
|
|
|
|
|
.surface_under(
|
|
|
|
|
pos_within_output - win_pos_within_output,
|
|
|
|
|
WindowSurfaceType::ALL,
|
|
|
|
|
)
|
|
|
|
|
.map(|(s, pos_within_window)| {
|
|
|
|
|
(s, pos_within_window.to_f64() + win_pos_within_output)
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
(surface_and_pos, (Some((window.clone(), hit)), None))
|
|
|
|
|
};
|
|
|
|
|
|
2025-04-24 22:04:49 +03:00
|
|
|
let interactive_moved_window_under = || {
|
|
|
|
|
self.layout
|
|
|
|
|
.interactive_moved_window_under(output, pos_within_output)
|
|
|
|
|
.map(mapped_hit_data)
|
|
|
|
|
};
|
2023-11-02 13:01:34 +04:00
|
|
|
let window_under = || {
|
|
|
|
|
self.layout
|
|
|
|
|
.window_under(output, pos_within_output)
|
2025-04-24 21:36:47 +03:00
|
|
|
.map(mapped_hit_data)
|
2023-11-02 13:01:34 +04:00
|
|
|
};
|
|
|
|
|
|
2023-11-02 13:07:01 +04:00
|
|
|
let mon = self.layout.monitor_for_output(output).unwrap();
|
|
|
|
|
|
2025-01-03 12:22:43 +03:00
|
|
|
let mut under =
|
|
|
|
|
layer_popup_under(Layer::Overlay).or_else(|| layer_toplevel_under(Layer::Overlay));
|
2023-11-02 13:07:01 +04:00
|
|
|
|
2025-04-25 09:36:50 +03:00
|
|
|
let is_overview_open = self.layout.is_overview_open();
|
|
|
|
|
|
2025-01-03 12:22:43 +03:00
|
|
|
// When rendering above the top layer, we put the regular monitor elements first.
|
|
|
|
|
// Otherwise, we will render all layer-shell pop-ups and the top layer on top.
|
2023-11-02 13:07:01 +04:00
|
|
|
if mon.render_above_top_layer() {
|
|
|
|
|
under = under
|
2025-04-24 22:04:49 +03:00
|
|
|
.or_else(interactive_moved_window_under)
|
2023-11-02 13:07:01 +04:00
|
|
|
.or_else(window_under)
|
2025-01-03 12:22:43 +03:00
|
|
|
.or_else(|| layer_popup_under(Layer::Top))
|
2025-04-12 09:27:19 +03:00
|
|
|
.or_else(|| layer_toplevel_under(Layer::Top))
|
2025-01-03 12:22:43 +03:00
|
|
|
.or_else(|| layer_popup_under(Layer::Bottom))
|
|
|
|
|
.or_else(|| layer_popup_under(Layer::Background))
|
|
|
|
|
.or_else(|| layer_toplevel_under(Layer::Bottom))
|
|
|
|
|
.or_else(|| layer_toplevel_under(Layer::Background));
|
2023-11-02 13:07:01 +04:00
|
|
|
} else {
|
2025-04-29 08:24:24 +03:00
|
|
|
let hot_corners = self.config.borrow().gestures.hot_corners;
|
|
|
|
|
if !hot_corners.off {
|
|
|
|
|
let hot_corner = Rectangle::from_size(Size::from((1., 1.)));
|
|
|
|
|
if hot_corner.contains(pos_within_output) {
|
2025-08-21 09:14:23 +03:00
|
|
|
rv.hot_corner = true;
|
2025-04-29 08:24:24 +03:00
|
|
|
return rv;
|
|
|
|
|
}
|
2025-04-25 10:08:26 +03:00
|
|
|
}
|
|
|
|
|
|
2023-11-02 13:07:01 +04:00
|
|
|
under = under
|
2025-01-03 12:22:43 +03:00
|
|
|
.or_else(|| layer_popup_under(Layer::Top))
|
2025-04-25 09:36:50 +03:00
|
|
|
.or_else(|| layer_toplevel_under(Layer::Top));
|
|
|
|
|
|
|
|
|
|
under = under.or_else(interactive_moved_window_under);
|
|
|
|
|
|
|
|
|
|
if !is_overview_open {
|
|
|
|
|
under = under
|
|
|
|
|
.or_else(|| layer_popup_under(Layer::Bottom))
|
|
|
|
|
.or_else(|| layer_popup_under(Layer::Background));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
under = under.or_else(window_under);
|
|
|
|
|
|
|
|
|
|
if !is_overview_open {
|
|
|
|
|
under = under
|
|
|
|
|
.or_else(|| layer_toplevel_under(Layer::Bottom))
|
|
|
|
|
.or_else(|| layer_toplevel_under(Layer::Background));
|
|
|
|
|
}
|
2023-11-02 13:07:01 +04:00
|
|
|
}
|
|
|
|
|
|
2025-02-10 08:03:39 +03:00
|
|
|
let Some((mut surface_and_pos, (window, layer))) = under else {
|
2024-03-18 17:50:31 +04:00
|
|
|
return rv;
|
|
|
|
|
};
|
2023-11-01 10:13:16 +01:00
|
|
|
|
2025-02-10 08:03:39 +03:00
|
|
|
if let Some((_, surface_pos)) = &mut surface_and_pos {
|
|
|
|
|
*surface_pos += output_pos_in_global_space.to_f64();
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2025-02-10 08:03:39 +03:00
|
|
|
rv.surface = surface_and_pos;
|
2024-03-18 17:50:31 +04:00
|
|
|
rv.window = window;
|
2024-07-06 18:17:48 +04:00
|
|
|
rv.layer = layer;
|
2024-03-18 17:50:31 +04:00
|
|
|
rv
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn output_under_cursor(&self) -> Option<Output> {
|
|
|
|
|
let pos = self.seat.get_pointer().unwrap().current_location();
|
|
|
|
|
self.global_space.output_under(pos).next().cloned()
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-11 21:04:49 +03:00
|
|
|
pub fn output_left_of(&self, current: &Output) -> Option<Output> {
|
|
|
|
|
let current_geo = self.global_space.output_geometry(current)?;
|
2025-01-04 10:14:51 +03:00
|
|
|
let extended_geo = Rectangle::new(
|
2025-06-11 21:04:49 +03:00
|
|
|
Point::from((i32::MIN / 2, current_geo.loc.y)),
|
|
|
|
|
Size::from((i32::MAX, current_geo.size.h)),
|
2023-08-16 08:03:20 +04:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
self.global_space
|
|
|
|
|
.outputs()
|
|
|
|
|
.map(|output| (output, self.global_space.output_geometry(output).unwrap()))
|
2025-06-11 21:04:49 +03:00
|
|
|
.filter(|(_, geo)| center(*geo).x < center(current_geo).x && geo.overlaps(extended_geo))
|
|
|
|
|
.min_by_key(|(_, geo)| center(current_geo).x - center(*geo).x)
|
2023-08-16 08:03:20 +04:00
|
|
|
.map(|(output, _)| output)
|
|
|
|
|
.cloned()
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-11 21:04:49 +03:00
|
|
|
pub fn output_right_of(&self, current: &Output) -> Option<Output> {
|
|
|
|
|
let current_geo = self.global_space.output_geometry(current)?;
|
2025-01-04 10:14:51 +03:00
|
|
|
let extended_geo = Rectangle::new(
|
2025-06-11 21:04:49 +03:00
|
|
|
Point::from((i32::MIN / 2, current_geo.loc.y)),
|
|
|
|
|
Size::from((i32::MAX, current_geo.size.h)),
|
2023-08-16 08:03:20 +04:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
self.global_space
|
|
|
|
|
.outputs()
|
|
|
|
|
.map(|output| (output, self.global_space.output_geometry(output).unwrap()))
|
2025-06-11 21:04:49 +03:00
|
|
|
.filter(|(_, geo)| center(*geo).x > center(current_geo).x && geo.overlaps(extended_geo))
|
|
|
|
|
.min_by_key(|(_, geo)| center(*geo).x - center(current_geo).x)
|
2023-08-16 08:03:20 +04:00
|
|
|
.map(|(output, _)| output)
|
|
|
|
|
.cloned()
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-11 21:04:49 +03:00
|
|
|
pub fn output_up_of(&self, current: &Output) -> Option<Output> {
|
|
|
|
|
let current_geo = self.global_space.output_geometry(current)?;
|
2025-01-04 10:14:51 +03:00
|
|
|
let extended_geo = Rectangle::new(
|
2025-06-11 21:04:49 +03:00
|
|
|
Point::from((current_geo.loc.x, i32::MIN / 2)),
|
|
|
|
|
Size::from((current_geo.size.w, i32::MAX)),
|
2023-08-16 08:03:20 +04:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
self.global_space
|
|
|
|
|
.outputs()
|
|
|
|
|
.map(|output| (output, self.global_space.output_geometry(output).unwrap()))
|
2025-06-11 21:04:49 +03:00
|
|
|
.filter(|(_, geo)| center(*geo).y < center(current_geo).y && geo.overlaps(extended_geo))
|
|
|
|
|
.min_by_key(|(_, geo)| center(current_geo).y - center(*geo).y)
|
2023-08-16 08:03:20 +04:00
|
|
|
.map(|(output, _)| output)
|
2024-12-15 16:04:42 +01:00
|
|
|
.cloned()
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-11 21:04:49 +03:00
|
|
|
pub fn output_down_of(&self, current: &Output) -> Option<Output> {
|
|
|
|
|
let current_geo = self.global_space.output_geometry(current)?;
|
|
|
|
|
let extended_geo = Rectangle::new(
|
|
|
|
|
Point::from((current_geo.loc.x, i32::MIN / 2)),
|
|
|
|
|
Size::from((current_geo.size.w, i32::MAX)),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
self.global_space
|
|
|
|
|
.outputs()
|
|
|
|
|
.map(|output| (output, self.global_space.output_geometry(output).unwrap()))
|
|
|
|
|
.filter(|(_, geo)| center(*geo).y > center(current_geo).y && geo.overlaps(extended_geo))
|
|
|
|
|
.min_by_key(|(_, geo)| center(*geo).y - center(current_geo).y)
|
|
|
|
|
.map(|(output, _)| output)
|
|
|
|
|
.cloned()
|
|
|
|
|
}
|
2024-12-15 16:04:42 +01:00
|
|
|
|
2025-06-11 21:04:49 +03:00
|
|
|
pub fn output_previous_of(&self, current: &Output) -> Option<Output> {
|
2024-12-15 16:04:42 +01:00
|
|
|
self.sorted_outputs
|
|
|
|
|
.iter()
|
|
|
|
|
.rev()
|
2025-06-11 21:04:49 +03:00
|
|
|
.skip_while(|&output| output != current)
|
2024-12-15 16:04:42 +01:00
|
|
|
.nth(1)
|
|
|
|
|
.or(self.sorted_outputs.last())
|
2025-06-11 21:04:49 +03:00
|
|
|
.filter(|&output| output != current)
|
2024-12-15 16:04:42 +01:00
|
|
|
.cloned()
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-11 21:04:49 +03:00
|
|
|
pub fn output_next_of(&self, current: &Output) -> Option<Output> {
|
2024-12-15 16:04:42 +01:00
|
|
|
self.sorted_outputs
|
|
|
|
|
.iter()
|
2025-06-11 21:04:49 +03:00
|
|
|
.skip_while(|&output| output != current)
|
2024-12-15 16:04:42 +01:00
|
|
|
.nth(1)
|
|
|
|
|
.or(self.sorted_outputs.first())
|
2025-06-11 21:04:49 +03:00
|
|
|
.filter(|&output| output != current)
|
2023-08-16 08:03:20 +04:00
|
|
|
.cloned()
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-11 21:04:49 +03:00
|
|
|
pub fn output_left(&self) -> Option<Output> {
|
|
|
|
|
let active = self.layout.active_output()?;
|
|
|
|
|
self.output_left_of(active)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn output_right(&self) -> Option<Output> {
|
|
|
|
|
let active = self.layout.active_output()?;
|
|
|
|
|
self.output_right_of(active)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn output_up(&self) -> Option<Output> {
|
|
|
|
|
let active = self.layout.active_output()?;
|
|
|
|
|
self.output_up_of(active)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn output_down(&self) -> Option<Output> {
|
|
|
|
|
let active = self.layout.active_output()?;
|
|
|
|
|
self.output_down_of(active)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn output_previous(&self) -> Option<Output> {
|
|
|
|
|
let active = self.layout.active_output()?;
|
|
|
|
|
self.output_previous_of(active)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn output_next(&self) -> Option<Output> {
|
|
|
|
|
let active = self.layout.active_output()?;
|
|
|
|
|
self.output_next_of(active)
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-11 22:40:30 +02:00
|
|
|
pub fn find_output_and_workspace_index(
|
|
|
|
|
&self,
|
|
|
|
|
workspace_reference: WorkspaceReference,
|
|
|
|
|
) -> Option<(Option<Output>, usize)> {
|
2024-09-02 09:20:23 +03:00
|
|
|
let (target_workspace_index, target_workspace) = match workspace_reference {
|
2024-05-11 22:40:30 +02:00
|
|
|
WorkspaceReference::Index(index) => {
|
|
|
|
|
return Some((None, index.saturating_sub(1) as usize));
|
|
|
|
|
}
|
2024-09-02 09:20:23 +03:00
|
|
|
WorkspaceReference::Name(name) => self.layout.find_workspace_by_name(&name)?,
|
|
|
|
|
WorkspaceReference::Id(id) => {
|
|
|
|
|
let id = WorkspaceId::specific(id);
|
|
|
|
|
self.layout.find_workspace_by_id(id)?
|
|
|
|
|
}
|
2024-05-11 22:40:30 +02:00
|
|
|
};
|
|
|
|
|
|
2024-09-06 15:10:01 +03:00
|
|
|
let target_output = target_workspace.current_output();
|
|
|
|
|
Some((target_output.cloned(), target_workspace_index))
|
2024-05-11 22:40:30 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-03 17:02:07 +04:00
|
|
|
pub fn output_for_tablet(&self) -> Option<&Output> {
|
|
|
|
|
let config = self.config.borrow();
|
|
|
|
|
let map_to_output = config.input.tablet.map_to_output.as_ref();
|
2024-09-03 12:13:04 +03:00
|
|
|
map_to_output.and_then(|name| self.output_by_name_match(name))
|
2023-10-03 17:02:07 +04:00
|
|
|
}
|
|
|
|
|
|
2024-02-24 18:32:13 +01:00
|
|
|
pub fn output_for_touch(&self) -> Option<&Output> {
|
|
|
|
|
let config = self.config.borrow();
|
|
|
|
|
let map_to_output = config.input.touch.map_to_output.as_ref();
|
|
|
|
|
map_to_output
|
2024-09-03 12:13:04 +03:00
|
|
|
.and_then(|name| self.output_by_name_match(name))
|
2024-02-24 18:32:13 +01:00
|
|
|
.or_else(|| self.global_space.outputs().next())
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-03 12:13:04 +03:00
|
|
|
pub fn output_by_name_match(&self, target: &str) -> Option<&Output> {
|
|
|
|
|
self.global_space
|
|
|
|
|
.outputs()
|
|
|
|
|
.find(|output| output_matches_name(output, target))
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-24 17:38:13 +04:00
|
|
|
pub fn output_for_root(&self, root: &WlSurface) -> Option<&Output> {
|
2023-12-19 13:29:22 +04:00
|
|
|
// Check the main layout.
|
|
|
|
|
let win_out = self.layout.find_window_and_output(root);
|
|
|
|
|
let layout_output = win_out.map(|(_, output)| output);
|
2025-02-05 09:32:47 +03:00
|
|
|
if let Some(output) = layout_output {
|
|
|
|
|
return output;
|
|
|
|
|
}
|
2023-12-19 13:29:22 +04:00
|
|
|
|
|
|
|
|
// Check layer-shell.
|
|
|
|
|
let has_layer_surface = |o: &&Output| {
|
|
|
|
|
layer_map_for_output(o)
|
|
|
|
|
.layer_for_surface(root, WindowSurfaceType::TOPLEVEL)
|
|
|
|
|
.is_some()
|
|
|
|
|
};
|
2025-02-05 09:32:47 +03:00
|
|
|
self.layout.outputs().find(has_layer_surface)
|
2023-12-19 13:29:22 +04:00
|
|
|
}
|
|
|
|
|
|
2024-01-12 17:53:35 +04:00
|
|
|
pub fn lock_surface_focus(&self) -> Option<WlSurface> {
|
2023-10-13 13:30:11 +04:00
|
|
|
let output_under_cursor = self.output_under_cursor();
|
|
|
|
|
let output = output_under_cursor
|
|
|
|
|
.as_ref()
|
|
|
|
|
.or_else(|| self.layout.active_output())
|
|
|
|
|
.or_else(|| self.global_space.outputs().next())?;
|
|
|
|
|
|
|
|
|
|
let state = self.output_state.get(output)?;
|
|
|
|
|
state.lock_surface.as_ref().map(|s| s.wl_surface()).cloned()
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
/// Schedules an immediate redraw on all outputs if one is not already scheduled.
|
|
|
|
|
pub fn queue_redraw_all(&mut self) {
|
2024-03-23 14:16:36 +04:00
|
|
|
for state in self.output_state.values_mut() {
|
|
|
|
|
state.redraw_state = mem::take(&mut state.redraw_state).queue_redraw();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-10 09:58:26 +04:00
|
|
|
/// Schedules an immediate redraw if one is not already scheduled.
|
2024-03-23 14:16:36 +04:00
|
|
|
pub fn queue_redraw(&mut self, output: &Output) {
|
|
|
|
|
let state = self.output_state.get_mut(output).unwrap();
|
|
|
|
|
state.redraw_state = mem::take(&mut state.redraw_state).queue_redraw();
|
2024-03-23 12:46:26 +04:00
|
|
|
}
|
2023-08-10 09:58:26 +04:00
|
|
|
|
2024-03-23 12:46:26 +04:00
|
|
|
pub fn redraw_queued_outputs(&mut self, backend: &mut Backend) {
|
|
|
|
|
let _span = tracy_client::span!("Niri::redraw_queued_outputs");
|
2023-09-30 17:13:56 +04:00
|
|
|
|
2024-03-23 12:46:26 +04:00
|
|
|
while let Some((output, _)) = self.output_state.iter().find(|(_, state)| {
|
|
|
|
|
matches!(
|
|
|
|
|
state.redraw_state,
|
|
|
|
|
RedrawState::Queued | RedrawState::WaitingForEstimatedVBlankAndQueued(_)
|
|
|
|
|
)
|
|
|
|
|
}) {
|
2024-08-22 14:44:11 +03:00
|
|
|
trace!("redrawing output");
|
2024-03-23 12:46:26 +04:00
|
|
|
let output = output.clone();
|
|
|
|
|
self.redraw(backend, &output);
|
|
|
|
|
}
|
2023-08-10 09:58:26 +04:00
|
|
|
}
|
|
|
|
|
|
2024-01-03 11:38:36 +04:00
|
|
|
pub fn pointer_element<R: NiriRenderer>(
|
2023-10-26 17:30:03 +04:00
|
|
|
&self,
|
2024-01-03 11:38:36 +04:00
|
|
|
renderer: &mut R,
|
2023-08-15 16:19:05 +04:00
|
|
|
output: &Output,
|
2024-01-03 11:38:36 +04:00
|
|
|
) -> Vec<OutputRenderElements<R>> {
|
2025-04-27 06:25:36 +00:00
|
|
|
if !self.pointer_visibility.is_visible() {
|
2024-04-02 08:44:29 +04:00
|
|
|
return vec![];
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-11 14:53:53 +04:00
|
|
|
let _span = tracy_client::span!("Niri::pointer_element");
|
2023-10-29 14:04:38 +04:00
|
|
|
let output_scale = output.current_scale();
|
2023-08-15 16:08:37 +04:00
|
|
|
let output_pos = self.global_space.output_geometry(output).unwrap().loc;
|
2023-12-05 10:24:41 +04:00
|
|
|
|
|
|
|
|
// Check whether we need to draw the tablet cursor or the regular cursor.
|
|
|
|
|
let pointer_pos = self
|
|
|
|
|
.tablet_cursor_location
|
|
|
|
|
.unwrap_or_else(|| self.seat.get_pointer().unwrap().current_location());
|
|
|
|
|
let pointer_pos = pointer_pos - output_pos.to_f64();
|
2023-08-15 16:08:37 +04:00
|
|
|
|
2023-10-29 14:04:38 +04:00
|
|
|
// Get the render cursor to draw.
|
|
|
|
|
let cursor_scale = output_scale.integer_scale();
|
|
|
|
|
let render_cursor = self.cursor_manager.get_render_cursor(cursor_scale);
|
2023-08-15 16:19:05 +04:00
|
|
|
|
2023-10-29 14:04:38 +04:00
|
|
|
let output_scale = Scale::from(output.current_scale().fractional_scale());
|
|
|
|
|
|
2024-02-24 18:17:51 +01:00
|
|
|
let mut pointer_elements = match render_cursor {
|
|
|
|
|
RenderCursor::Hidden => vec![],
|
2023-10-29 14:04:38 +04:00
|
|
|
RenderCursor::Surface { surface, hotspot } => {
|
|
|
|
|
let pointer_pos =
|
|
|
|
|
(pointer_pos - hotspot.to_f64()).to_physical_precise_round(output_scale);
|
|
|
|
|
|
2024-09-30 15:24:50 +03:00
|
|
|
render_elements_from_surface_tree(
|
2023-10-29 14:04:38 +04:00
|
|
|
renderer,
|
|
|
|
|
&surface,
|
|
|
|
|
pointer_pos,
|
|
|
|
|
output_scale,
|
|
|
|
|
1.,
|
2023-09-11 19:24:09 +04:00
|
|
|
Kind::Cursor,
|
2024-09-30 15:24:50 +03:00
|
|
|
)
|
2023-10-29 14:04:38 +04:00
|
|
|
}
|
|
|
|
|
RenderCursor::Named {
|
|
|
|
|
icon,
|
|
|
|
|
scale,
|
|
|
|
|
cursor,
|
|
|
|
|
} => {
|
|
|
|
|
let (idx, frame) = cursor.frame(self.start_time.elapsed().as_millis() as u32);
|
|
|
|
|
let hotspot = XCursor::hotspot(frame).to_logical(scale);
|
|
|
|
|
let pointer_pos =
|
|
|
|
|
(pointer_pos - hotspot.to_f64()).to_physical_precise_round(output_scale);
|
|
|
|
|
|
2024-01-23 20:44:47 +01:00
|
|
|
let texture = self.cursor_texture_cache.get(icon, scale, &cursor, idx);
|
2024-01-19 09:13:32 +04:00
|
|
|
let mut pointer_elements = vec![];
|
2024-01-23 20:44:47 +01:00
|
|
|
let pointer_element = match MemoryRenderBufferRenderElement::from_buffer(
|
|
|
|
|
renderer,
|
2024-02-24 18:17:51 +01:00
|
|
|
pointer_pos,
|
2024-01-23 20:44:47 +01:00
|
|
|
&texture,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
Kind::Cursor,
|
|
|
|
|
) {
|
|
|
|
|
Ok(element) => Some(element),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
warn!("error importing a cursor texture: {err:?}");
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
if let Some(element) = pointer_element {
|
|
|
|
|
pointer_elements.push(OutputRenderElements::NamedPointer(element));
|
2024-01-19 09:13:32 +04:00
|
|
|
}
|
2023-10-29 14:04:38 +04:00
|
|
|
|
2024-02-24 18:17:51 +01:00
|
|
|
pointer_elements
|
2023-10-29 14:04:38 +04:00
|
|
|
}
|
2023-08-15 16:40:54 +04:00
|
|
|
};
|
|
|
|
|
|
2024-02-24 18:17:51 +01:00
|
|
|
if let Some(dnd_icon) = self.dnd_icon.as_ref() {
|
|
|
|
|
let pointer_pos =
|
|
|
|
|
(pointer_pos + dnd_icon.offset.to_f64()).to_physical_precise_round(output_scale);
|
2023-08-15 16:40:54 +04:00
|
|
|
pointer_elements.extend(render_elements_from_surface_tree(
|
|
|
|
|
renderer,
|
2024-02-24 18:17:51 +01:00
|
|
|
&dnd_icon.surface,
|
2023-08-15 16:40:54 +04:00
|
|
|
pointer_pos,
|
2023-09-21 13:48:32 +04:00
|
|
|
output_scale,
|
2023-08-15 16:40:54 +04:00
|
|
|
1.,
|
2025-08-13 10:27:41 +02:00
|
|
|
Kind::ScanoutCandidate,
|
2023-08-15 16:40:54 +04:00
|
|
|
));
|
2023-08-15 16:19:05 +04:00
|
|
|
}
|
2023-08-15 16:40:54 +04:00
|
|
|
|
|
|
|
|
pointer_elements
|
2023-08-15 16:08:37 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-29 14:04:38 +04:00
|
|
|
pub fn refresh_pointer_outputs(&mut self) {
|
2025-04-27 06:25:36 +00:00
|
|
|
if !self.pointer_visibility.is_visible() {
|
2024-04-02 08:44:29 +04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-02 18:48:41 +04:00
|
|
|
let _span = tracy_client::span!("Niri::refresh_pointer_outputs");
|
|
|
|
|
|
2023-12-05 10:24:41 +04:00
|
|
|
// Check whether we need to draw the tablet cursor or the regular cursor.
|
|
|
|
|
let pointer_pos = self
|
|
|
|
|
.tablet_cursor_location
|
|
|
|
|
.unwrap_or_else(|| self.seat.get_pointer().unwrap().current_location());
|
|
|
|
|
|
2024-09-28 21:07:10 -04:00
|
|
|
match self.cursor_manager.cursor_image() {
|
2023-10-29 14:04:38 +04:00
|
|
|
CursorImageStatus::Surface(ref surface) => {
|
2023-10-02 18:48:41 +04:00
|
|
|
let hotspot = with_states(surface, |states| {
|
|
|
|
|
states
|
|
|
|
|
.data_map
|
2025-01-29 23:45:38 +02:00
|
|
|
.get::<CursorImageSurfaceData>()
|
2023-10-02 18:48:41 +04:00
|
|
|
.unwrap()
|
|
|
|
|
.lock()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.hotspot
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let surface_pos = pointer_pos.to_i32_round() - hotspot;
|
|
|
|
|
let bbox = bbox_from_surface_tree(surface, surface_pos);
|
|
|
|
|
|
|
|
|
|
let dnd = self
|
|
|
|
|
.dnd_icon
|
|
|
|
|
.as_ref()
|
2024-02-24 18:17:51 +01:00
|
|
|
.map(|icon| &icon.surface)
|
2023-10-02 18:48:41 +04:00
|
|
|
.map(|surface| (surface, bbox_from_surface_tree(surface, surface_pos)));
|
|
|
|
|
|
2023-10-26 00:15:46 +04:00
|
|
|
// FIXME we basically need to pick the largest scale factor across the overlapping
|
|
|
|
|
// outputs, this is how it's usually done in clients as well.
|
2024-05-29 13:32:11 +03:00
|
|
|
let mut cursor_scale = 1.;
|
2024-01-28 14:25:40 +01:00
|
|
|
let mut cursor_transform = Transform::Normal;
|
2024-05-29 13:32:11 +03:00
|
|
|
let mut dnd_scale = 1.;
|
2024-01-28 14:25:40 +01:00
|
|
|
let mut dnd_transform = Transform::Normal;
|
2023-10-02 18:48:41 +04:00
|
|
|
for output in self.global_space.outputs() {
|
|
|
|
|
let geo = self.global_space.output_geometry(output).unwrap();
|
|
|
|
|
|
|
|
|
|
// Compute pointer surface overlap.
|
|
|
|
|
if let Some(mut overlap) = geo.intersection(bbox) {
|
|
|
|
|
overlap.loc -= surface_pos;
|
2024-05-29 13:32:11 +03:00
|
|
|
cursor_scale =
|
|
|
|
|
f64::max(cursor_scale, output.current_scale().fractional_scale());
|
2024-01-28 14:25:40 +01:00
|
|
|
// FIXME: using the largest overlapping or "primary" output transform would
|
|
|
|
|
// make more sense here.
|
|
|
|
|
cursor_transform = output.current_transform();
|
2023-10-02 18:48:41 +04:00
|
|
|
output_update(output, Some(overlap), surface);
|
|
|
|
|
} else {
|
|
|
|
|
output_update(output, None, surface);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compute DnD icon surface overlap.
|
|
|
|
|
if let Some((surface, bbox)) = dnd {
|
|
|
|
|
if let Some(mut overlap) = geo.intersection(bbox) {
|
|
|
|
|
overlap.loc -= surface_pos;
|
2024-05-29 13:32:11 +03:00
|
|
|
dnd_scale =
|
|
|
|
|
f64::max(dnd_scale, output.current_scale().fractional_scale());
|
2024-01-28 14:25:40 +01:00
|
|
|
// FIXME: using the largest overlapping or "primary" output transform
|
|
|
|
|
// would make more sense here.
|
|
|
|
|
dnd_transform = output.current_transform();
|
2023-10-02 18:48:41 +04:00
|
|
|
output_update(output, Some(overlap), surface);
|
|
|
|
|
} else {
|
|
|
|
|
output_update(output, None, surface);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-26 00:15:46 +04:00
|
|
|
|
|
|
|
|
with_states(surface, |data| {
|
2024-05-29 13:32:11 +03:00
|
|
|
send_scale_transform(
|
|
|
|
|
surface,
|
|
|
|
|
data,
|
|
|
|
|
output::Scale::Fractional(cursor_scale),
|
|
|
|
|
cursor_transform,
|
|
|
|
|
)
|
2023-10-26 00:15:46 +04:00
|
|
|
});
|
|
|
|
|
if let Some((surface, _)) = dnd {
|
|
|
|
|
with_states(surface, |data| {
|
2024-05-29 13:32:11 +03:00
|
|
|
send_scale_transform(
|
|
|
|
|
surface,
|
|
|
|
|
data,
|
|
|
|
|
output::Scale::Fractional(dnd_scale),
|
|
|
|
|
dnd_transform,
|
|
|
|
|
);
|
2023-10-26 00:15:46 +04:00
|
|
|
});
|
|
|
|
|
}
|
2023-10-02 18:48:41 +04:00
|
|
|
}
|
2023-10-29 14:04:38 +04:00
|
|
|
cursor_image => {
|
|
|
|
|
// There's no cursor surface, but there might be a DnD icon.
|
2024-02-24 18:17:51 +01:00
|
|
|
let Some(surface) = self.dnd_icon.as_ref().map(|icon| &icon.surface) else {
|
2023-10-29 14:04:38 +04:00
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let icon = if let CursorImageStatus::Named(icon) = cursor_image {
|
2024-09-30 15:24:50 +03:00
|
|
|
*icon
|
2023-10-29 14:04:38 +04:00
|
|
|
} else {
|
|
|
|
|
Default::default()
|
|
|
|
|
};
|
|
|
|
|
|
2024-05-29 13:32:11 +03:00
|
|
|
let mut dnd_scale = 1.;
|
2024-01-28 14:25:40 +01:00
|
|
|
let mut dnd_transform = Transform::Normal;
|
2023-10-29 14:04:38 +04:00
|
|
|
for output in self.global_space.outputs() {
|
|
|
|
|
let geo = self.global_space.output_geometry(output).unwrap();
|
|
|
|
|
|
|
|
|
|
// The default cursor is rendered at the right scale for each output, which
|
|
|
|
|
// means that it may have a different hotspot for each output.
|
|
|
|
|
let output_scale = output.current_scale().integer_scale();
|
|
|
|
|
let cursor = self
|
|
|
|
|
.cursor_manager
|
|
|
|
|
.get_cursor_with_name(icon, output_scale)
|
|
|
|
|
.unwrap_or_else(|| self.cursor_manager.get_default_cursor(output_scale));
|
|
|
|
|
|
|
|
|
|
// For simplicity, we always use frame 0 for this computation. Let's hope the
|
|
|
|
|
// hotspot doesn't change between frames.
|
|
|
|
|
let hotspot = XCursor::hotspot(&cursor.frames()[0]).to_logical(output_scale);
|
|
|
|
|
|
|
|
|
|
let surface_pos = pointer_pos.to_i32_round() - hotspot;
|
|
|
|
|
let bbox = bbox_from_surface_tree(surface, surface_pos);
|
|
|
|
|
|
|
|
|
|
if let Some(mut overlap) = geo.intersection(bbox) {
|
|
|
|
|
overlap.loc -= surface_pos;
|
2024-05-29 13:32:11 +03:00
|
|
|
dnd_scale = f64::max(dnd_scale, output.current_scale().fractional_scale());
|
2024-01-28 14:25:40 +01:00
|
|
|
// FIXME: using the largest overlapping or "primary" output transform would
|
|
|
|
|
// make more sense here.
|
|
|
|
|
dnd_transform = output.current_transform();
|
2023-10-29 14:04:38 +04:00
|
|
|
output_update(output, Some(overlap), surface);
|
|
|
|
|
} else {
|
|
|
|
|
output_update(output, None, surface);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-01-28 14:25:40 +01:00
|
|
|
|
|
|
|
|
with_states(surface, |data| {
|
2024-05-29 13:32:11 +03:00
|
|
|
send_scale_transform(
|
|
|
|
|
surface,
|
|
|
|
|
data,
|
|
|
|
|
output::Scale::Fractional(dnd_scale),
|
|
|
|
|
dnd_transform,
|
|
|
|
|
);
|
2024-01-28 14:25:40 +01:00
|
|
|
});
|
2023-10-29 14:04:38 +04:00
|
|
|
}
|
2023-10-02 18:48:41 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-15 10:43:58 +03:00
|
|
|
pub fn refresh_layout(&mut self) {
|
|
|
|
|
let layout_is_active = match &self.keyboard_focus {
|
|
|
|
|
KeyboardFocus::Layout { .. } => true,
|
|
|
|
|
KeyboardFocus::LayerShell { .. } => false,
|
|
|
|
|
|
|
|
|
|
// Draw layout as active in these cases to reduce unnecessary window animations.
|
|
|
|
|
// There's no confusion because these are both fullscreen modes.
|
|
|
|
|
//
|
|
|
|
|
// FIXME: when going into the screenshot UI from a layer-shell focus, and then back to
|
|
|
|
|
// layer-shell, the layout will briefly draw as active, despite never having focus.
|
|
|
|
|
KeyboardFocus::LockScreen { .. } => true,
|
|
|
|
|
KeyboardFocus::ScreenshotUi => true,
|
2025-07-16 14:36:58 +03:00
|
|
|
KeyboardFocus::ExitConfirmDialog => true,
|
2025-04-25 09:36:50 +03:00
|
|
|
KeyboardFocus::Overview => true,
|
2024-10-15 10:43:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
self.layout.refresh(layout_is_active);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-08 13:51:54 +04:00
|
|
|
pub fn refresh_idle_inhibit(&mut self) {
|
|
|
|
|
let _span = tracy_client::span!("Niri::refresh_idle_inhibit");
|
|
|
|
|
|
|
|
|
|
self.idle_inhibiting_surfaces.retain(|s| s.is_alive());
|
|
|
|
|
|
2024-02-11 10:59:57 +04:00
|
|
|
let is_inhibited = self.is_fdo_idle_inhibited.load(Ordering::SeqCst)
|
|
|
|
|
|| self.idle_inhibiting_surfaces.iter().any(|surface| {
|
|
|
|
|
with_states(surface, |states| {
|
|
|
|
|
surface_primary_scanout_output(surface, states).is_some()
|
|
|
|
|
})
|
|
|
|
|
});
|
2024-02-08 13:51:54 +04:00
|
|
|
self.idle_notifier_state.set_is_inhibited(is_inhibited);
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-13 11:32:54 +03:00
|
|
|
pub fn refresh_window_states(&mut self) {
|
|
|
|
|
let _span = tracy_client::span!("Niri::refresh_window_states");
|
|
|
|
|
|
|
|
|
|
let config = self.config.borrow();
|
|
|
|
|
self.layout.with_windows_mut(|mapped, _output| {
|
|
|
|
|
mapped.update_tiled_state(config.prefer_no_csd);
|
|
|
|
|
});
|
|
|
|
|
drop(config);
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-23 14:38:07 +04:00
|
|
|
pub fn refresh_window_rules(&mut self) {
|
|
|
|
|
let _span = tracy_client::span!("Niri::refresh_window_rules");
|
|
|
|
|
|
|
|
|
|
let config = self.config.borrow();
|
|
|
|
|
let window_rules = &config.window_rules;
|
|
|
|
|
|
|
|
|
|
let mut windows = vec![];
|
|
|
|
|
let mut outputs = HashSet::new();
|
|
|
|
|
self.layout.with_windows_mut(|mapped, output| {
|
2024-05-16 11:43:13 +04:00
|
|
|
if mapped.recompute_window_rules_if_needed(window_rules, self.is_at_startup) {
|
2024-03-23 14:38:07 +04:00
|
|
|
windows.push(mapped.window.clone());
|
|
|
|
|
|
|
|
|
|
if let Some(output) = output {
|
|
|
|
|
outputs.insert(output.clone());
|
|
|
|
|
}
|
2025-03-13 11:32:54 +03:00
|
|
|
|
|
|
|
|
// Since refresh_window_rules() is called after refresh_layout(), we need to update
|
|
|
|
|
// the tiled state right here, so that it's picked up by the following
|
|
|
|
|
// send_pending_configure().
|
|
|
|
|
mapped.update_tiled_state(config.prefer_no_csd);
|
2024-03-23 14:38:07 +04:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
drop(config);
|
|
|
|
|
|
|
|
|
|
for win in windows {
|
2024-05-10 16:58:53 +04:00
|
|
|
self.layout.update_window(&win, None);
|
2024-03-23 18:57:06 +04:00
|
|
|
win.toplevel()
|
|
|
|
|
.expect("no X11 support")
|
|
|
|
|
.send_pending_configure();
|
2024-03-23 14:38:07 +04:00
|
|
|
}
|
|
|
|
|
for output in outputs {
|
|
|
|
|
self.queue_redraw(&output);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-11 09:52:05 +03:00
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
|
|
|
|
pub fn refresh_mapped_cast_window_rules(&mut self) {
|
|
|
|
|
// O(N^2) but should be fine since there aren't many casts usually.
|
|
|
|
|
self.layout.with_windows_mut(|mapped, _| {
|
|
|
|
|
let id = mapped.id().get();
|
|
|
|
|
// Find regardless of cast.is_active.
|
|
|
|
|
let value = self
|
|
|
|
|
.casts
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|cast| cast.target == (CastTarget::Window { id }));
|
|
|
|
|
mapped.set_is_window_cast_target(value);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-27 11:36:24 +04:00
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
|
|
|
|
pub fn refresh_mapped_cast_outputs(&mut self) {
|
|
|
|
|
use std::collections::hash_map::Entry;
|
|
|
|
|
|
|
|
|
|
let mut seen = HashSet::new();
|
|
|
|
|
let mut output_changed = vec![];
|
|
|
|
|
|
2025-08-16 01:42:08 -07:00
|
|
|
self.layout.with_windows(|mapped, output, _, _| {
|
2024-06-27 11:36:24 +04:00
|
|
|
seen.insert(mapped.window.clone());
|
|
|
|
|
|
|
|
|
|
let Some(output) = output else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match self.mapped_cast_output.entry(mapped.window.clone()) {
|
|
|
|
|
Entry::Occupied(mut entry) => {
|
|
|
|
|
if entry.get() != output {
|
|
|
|
|
entry.insert(output.clone());
|
|
|
|
|
output_changed.push((mapped.id(), output.clone()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Entry::Vacant(entry) => {
|
|
|
|
|
entry.insert(output.clone());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
self.mapped_cast_output.retain(|win, _| seen.contains(win));
|
|
|
|
|
|
|
|
|
|
let mut to_stop = vec![];
|
|
|
|
|
for (id, out) in output_changed {
|
|
|
|
|
let refresh = out.current_mode().unwrap().refresh as u32;
|
2024-08-31 10:22:57 +03:00
|
|
|
let target = CastTarget::Window { id: id.get() };
|
2024-06-27 11:36:24 +04:00
|
|
|
for cast in self.casts.iter_mut().filter(|cast| cast.target == target) {
|
|
|
|
|
if let Err(err) = cast.set_refresh(refresh) {
|
|
|
|
|
warn!("error changing cast FPS: {err:?}");
|
|
|
|
|
to_stop.push(cast.session_id);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for session_id in to_stop {
|
|
|
|
|
self.stop_cast(session_id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-24 09:41:43 +03:00
|
|
|
pub fn advance_animations(&mut self) {
|
|
|
|
|
let _span = tracy_client::span!("Niri::advance_animations");
|
|
|
|
|
|
|
|
|
|
self.layout.advance_animations();
|
|
|
|
|
self.config_error_notification.advance_animations();
|
2025-08-22 08:50:52 +03:00
|
|
|
self.exit_confirm_dialog.advance_animations();
|
2024-11-24 09:41:43 +03:00
|
|
|
self.screenshot_ui.advance_animations();
|
2024-11-23 11:27:27 +03:00
|
|
|
|
|
|
|
|
for state in self.output_state.values_mut() {
|
|
|
|
|
if let Some(transition) = &mut state.screen_transition {
|
|
|
|
|
if transition.is_done() {
|
|
|
|
|
state.screen_transition = None;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-23 12:48:05 +03:00
|
|
|
pub fn update_render_elements(&mut self, output: Option<&Output>) {
|
|
|
|
|
self.layout.update_render_elements(output);
|
2024-08-23 12:48:20 +03:00
|
|
|
|
|
|
|
|
for (out, state) in self.output_state.iter_mut() {
|
|
|
|
|
if output.map_or(true, |output| out == output) {
|
|
|
|
|
let scale = Scale::from(out.current_scale().fractional_scale());
|
|
|
|
|
let transform = out.current_transform();
|
|
|
|
|
|
|
|
|
|
if let Some(transition) = &mut state.screen_transition {
|
|
|
|
|
transition.update_render_elements(scale, transform);
|
|
|
|
|
}
|
2025-01-21 09:40:00 +03:00
|
|
|
|
|
|
|
|
let layer_map = layer_map_for_output(out);
|
|
|
|
|
for surface in layer_map.layers() {
|
|
|
|
|
let Some(mapped) = self.mapped_layer_surfaces.get_mut(surface) else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
let Some(geo) = layer_map.layer_geometry(surface) else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
|
2025-05-12 08:16:01 +03:00
|
|
|
mapped.update_render_elements(geo.size.to_f64());
|
2025-01-21 09:40:00 +03:00
|
|
|
}
|
2024-08-23 12:48:20 +03:00
|
|
|
}
|
|
|
|
|
}
|
2024-08-23 12:48:05 +03:00
|
|
|
}
|
|
|
|
|
|
2025-01-21 09:40:00 +03:00
|
|
|
pub fn update_shaders(&mut self) {
|
|
|
|
|
self.layout.update_shaders();
|
|
|
|
|
|
|
|
|
|
for mapped in self.mapped_layer_surfaces.values_mut() {
|
|
|
|
|
mapped.update_shaders();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-03 11:38:36 +04:00
|
|
|
pub fn render<R: NiriRenderer>(
|
2023-10-26 17:30:03 +04:00
|
|
|
&self,
|
2024-01-03 11:38:36 +04:00
|
|
|
renderer: &mut R,
|
2023-08-27 17:44:22 +04:00
|
|
|
output: &Output,
|
2023-09-20 09:22:39 +04:00
|
|
|
include_pointer: bool,
|
2024-03-24 09:03:59 +04:00
|
|
|
mut target: RenderTarget,
|
2024-01-03 11:38:36 +04:00
|
|
|
) -> Vec<OutputRenderElements<R>> {
|
2023-08-27 17:44:22 +04:00
|
|
|
let _span = tracy_client::span!("Niri::render");
|
2023-08-15 12:49:26 +04:00
|
|
|
|
2024-03-24 09:03:59 +04:00
|
|
|
if target == RenderTarget::Output {
|
|
|
|
|
if let Some(preview) = self.config.borrow().debug.preview_render {
|
|
|
|
|
target = match preview {
|
|
|
|
|
PreviewRender::Screencast => RenderTarget::Screencast,
|
|
|
|
|
PreviewRender::ScreenCapture => RenderTarget::ScreenCapture,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-21 13:48:32 +04:00
|
|
|
let output_scale = Scale::from(output.current_scale().fractional_scale());
|
|
|
|
|
|
2023-08-15 12:49:26 +04:00
|
|
|
// The pointer goes on the top.
|
2023-09-20 09:22:39 +04:00
|
|
|
let mut elements = vec![];
|
|
|
|
|
if include_pointer {
|
|
|
|
|
elements = self.pointer_element(renderer, output);
|
|
|
|
|
}
|
2023-08-15 12:49:26 +04:00
|
|
|
|
2024-05-07 22:06:43 +04:00
|
|
|
// Next, the screen transition texture.
|
|
|
|
|
{
|
|
|
|
|
let state = self.output_state.get(output).unwrap();
|
|
|
|
|
if let Some(transition) = &state.screen_transition {
|
|
|
|
|
elements.push(transition.render(target).into());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-19 08:33:54 +04:00
|
|
|
// Next, the exit confirm dialog.
|
2025-08-22 07:58:37 +03:00
|
|
|
elements.extend(
|
|
|
|
|
self.exit_confirm_dialog
|
|
|
|
|
.render(renderer, output)
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(OutputRenderElements::from),
|
|
|
|
|
);
|
2024-01-19 08:33:54 +04:00
|
|
|
|
|
|
|
|
// Next, the config error notification too.
|
2024-01-18 11:15:48 +04:00
|
|
|
if let Some(element) = self.config_error_notification.render(renderer, output) {
|
|
|
|
|
elements.push(element.into());
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-13 13:30:11 +04:00
|
|
|
// If the session is locked, draw the lock surface.
|
|
|
|
|
if self.is_locked() {
|
2023-10-26 17:30:03 +04:00
|
|
|
let state = self.output_state.get(output).unwrap();
|
2023-10-13 13:30:11 +04:00
|
|
|
if let Some(surface) = state.lock_surface.as_ref() {
|
|
|
|
|
elements.extend(render_elements_from_surface_tree(
|
|
|
|
|
renderer,
|
|
|
|
|
surface.wl_surface(),
|
|
|
|
|
(0, 0),
|
|
|
|
|
output_scale,
|
|
|
|
|
1.,
|
2025-08-13 10:27:41 +02:00
|
|
|
Kind::ScanoutCandidate,
|
2023-10-13 13:30:11 +04:00
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Draw the solid color background.
|
|
|
|
|
elements.push(
|
|
|
|
|
SolidColorRenderElement::from_buffer(
|
|
|
|
|
&state.lock_color_buffer,
|
2025-04-28 07:53:03 +03:00
|
|
|
(0., 0.),
|
2023-10-13 13:30:11 +04:00
|
|
|
1.,
|
|
|
|
|
Kind::Unspecified,
|
|
|
|
|
)
|
|
|
|
|
.into(),
|
|
|
|
|
);
|
|
|
|
|
|
2024-05-02 17:52:06 +04:00
|
|
|
if self.debug_draw_opaque_regions {
|
|
|
|
|
draw_opaque_regions(&mut elements, output_scale);
|
|
|
|
|
}
|
2023-10-13 13:30:11 +04:00
|
|
|
return elements;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-25 08:53:16 +03:00
|
|
|
// Prepare the background elements.
|
2023-10-30 20:29:03 +04:00
|
|
|
let state = self.output_state.get(output).unwrap();
|
2025-04-25 08:53:16 +03:00
|
|
|
let background_buffer = state.background_buffer.clone();
|
2023-10-30 20:29:03 +04:00
|
|
|
let background = SolidColorRenderElement::from_buffer(
|
2025-04-25 08:53:16 +03:00
|
|
|
&background_buffer,
|
2025-04-28 07:53:03 +03:00
|
|
|
(0., 0.),
|
2025-04-25 08:53:16 +03:00
|
|
|
1.,
|
|
|
|
|
Kind::Unspecified,
|
|
|
|
|
);
|
|
|
|
|
let backdrop = SolidColorRenderElement::from_buffer(
|
|
|
|
|
&state.backdrop_buffer,
|
2025-04-28 07:53:03 +03:00
|
|
|
(0., 0.),
|
2023-10-30 20:29:03 +04:00
|
|
|
1.,
|
|
|
|
|
Kind::Unspecified,
|
|
|
|
|
)
|
|
|
|
|
.into();
|
|
|
|
|
|
|
|
|
|
// If the screenshot UI is open, draw it.
|
|
|
|
|
if self.screenshot_ui.is_open() {
|
|
|
|
|
elements.extend(
|
|
|
|
|
self.screenshot_ui
|
2024-03-24 11:11:15 +04:00
|
|
|
.render_output(output, target)
|
2023-10-30 20:29:03 +04:00
|
|
|
.into_iter()
|
|
|
|
|
.map(OutputRenderElements::from),
|
|
|
|
|
);
|
|
|
|
|
|
2025-04-25 08:53:16 +03:00
|
|
|
// Add the backdrop for outputs that were connected while the screenshot UI was open.
|
|
|
|
|
elements.push(backdrop);
|
2023-10-30 20:29:03 +04:00
|
|
|
|
2024-05-02 17:52:06 +04:00
|
|
|
if self.debug_draw_opaque_regions {
|
|
|
|
|
draw_opaque_regions(&mut elements, output_scale);
|
|
|
|
|
}
|
2023-10-30 20:29:03 +04:00
|
|
|
return elements;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-18 19:20:46 +04:00
|
|
|
// Draw the hotkey overlay on top.
|
|
|
|
|
if let Some(element) = self.hotkey_overlay.render(renderer, output) {
|
|
|
|
|
elements.push(element.into());
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-29 11:04:08 +03:00
|
|
|
// Don't draw the focus ring on the workspaces while interactively moving above those
|
|
|
|
|
// workspaces, since the interactively-moved window already has a focus ring.
|
|
|
|
|
let focus_ring = !self.layout.interactive_move_is_moving_above_output(output);
|
|
|
|
|
|
2023-10-13 13:30:11 +04:00
|
|
|
// Get monitor elements.
|
|
|
|
|
let mon = self.layout.monitor_for_output(output).unwrap();
|
2025-04-25 09:36:50 +03:00
|
|
|
let zoom = mon.overview_zoom();
|
2025-04-25 08:53:16 +03:00
|
|
|
let monitor_elements = Vec::from_iter(
|
|
|
|
|
mon.render_elements(renderer, target, focus_ring)
|
|
|
|
|
.map(|(geo, iter)| (geo, Vec::from_iter(iter))),
|
|
|
|
|
);
|
2025-05-01 10:09:25 +03:00
|
|
|
let workspace_shadow_elements = Vec::from_iter(mon.render_workspace_shadows(renderer));
|
2025-04-25 10:11:27 +03:00
|
|
|
let insert_hint_elements = mon.render_insert_hint_between_workspaces(renderer);
|
2025-04-12 08:53:13 +03:00
|
|
|
let int_move_elements: Vec<_> = self
|
2024-07-15 15:51:48 +02:00
|
|
|
.layout
|
2025-04-12 08:53:13 +03:00
|
|
|
.render_interactive_move_for_output(renderer, output, target)
|
2024-07-15 15:51:48 +02:00
|
|
|
.collect();
|
2023-10-13 13:30:11 +04:00
|
|
|
|
2023-09-27 08:50:00 +04:00
|
|
|
// Get layer-shell elements.
|
|
|
|
|
let layer_map = layer_map_for_output(output);
|
2025-05-06 16:51:18 +03:00
|
|
|
let mut extend_from_layer =
|
|
|
|
|
|elements: &mut SplitElements<LayerSurfaceRenderElement<R>>, layer, for_backdrop| {
|
2025-05-12 08:16:01 +03:00
|
|
|
self.render_layer(renderer, target, &layer_map, layer, elements, for_backdrop);
|
2025-05-06 16:51:18 +03:00
|
|
|
};
|
2023-09-27 08:50:00 +04:00
|
|
|
|
2025-01-03 12:22:43 +03:00
|
|
|
// The overlay layer elements go next.
|
|
|
|
|
let mut layer_elems = SplitElements::default();
|
2025-05-06 16:51:18 +03:00
|
|
|
extend_from_layer(&mut layer_elems, Layer::Overlay, false);
|
2025-01-03 12:22:43 +03:00
|
|
|
elements.extend(layer_elems.into_iter().map(OutputRenderElements::from));
|
|
|
|
|
|
2025-04-12 09:27:19 +03:00
|
|
|
// Collect the top layer elements.
|
2025-01-03 12:22:43 +03:00
|
|
|
let mut layer_elems = SplitElements::default();
|
2025-05-06 16:51:18 +03:00
|
|
|
extend_from_layer(&mut layer_elems, Layer::Top, false);
|
2025-04-12 09:27:19 +03:00
|
|
|
let top_layer = layer_elems;
|
|
|
|
|
|
2025-01-03 12:22:43 +03:00
|
|
|
// When rendering above the top layer, we put the regular monitor elements first.
|
|
|
|
|
// Otherwise, we will render all layer-shell pop-ups and the top layer on top.
|
2023-11-02 13:07:01 +04:00
|
|
|
if mon.render_above_top_layer() {
|
2025-04-25 08:53:16 +03:00
|
|
|
// Collect all other layer-shell elements.
|
|
|
|
|
let mut layer_elems = SplitElements::default();
|
2025-05-06 16:51:18 +03:00
|
|
|
extend_from_layer(&mut layer_elems, Layer::Bottom, false);
|
|
|
|
|
extend_from_layer(&mut layer_elems, Layer::Background, false);
|
2025-04-25 08:53:16 +03:00
|
|
|
|
2025-04-12 08:53:13 +03:00
|
|
|
elements.extend(
|
|
|
|
|
int_move_elements
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(OutputRenderElements::from),
|
|
|
|
|
);
|
2025-04-25 10:11:27 +03:00
|
|
|
elements.extend(
|
|
|
|
|
insert_hint_elements
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(OutputRenderElements::from),
|
|
|
|
|
);
|
2025-04-25 08:53:16 +03:00
|
|
|
elements.extend(
|
|
|
|
|
monitor_elements
|
|
|
|
|
.into_iter()
|
|
|
|
|
.flat_map(|(_ws_geo, iter)| iter)
|
|
|
|
|
.map(OutputRenderElements::from),
|
|
|
|
|
);
|
2025-01-03 12:22:43 +03:00
|
|
|
|
2025-04-12 09:27:19 +03:00
|
|
|
elements.extend(top_layer.into_iter().map(OutputRenderElements::from));
|
2025-05-06 16:51:18 +03:00
|
|
|
elements.extend(layer_elems.into_iter().map(OutputRenderElements::from));
|
2025-04-25 08:53:16 +03:00
|
|
|
|
|
|
|
|
elements.push(OutputRenderElements::from(background));
|
2025-05-01 10:09:25 +03:00
|
|
|
|
|
|
|
|
elements.extend(
|
|
|
|
|
workspace_shadow_elements
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(OutputRenderElements::from),
|
|
|
|
|
);
|
2023-11-02 13:07:01 +04:00
|
|
|
} else {
|
2025-04-12 09:27:19 +03:00
|
|
|
elements.extend(top_layer.into_iter().map(OutputRenderElements::from));
|
2025-01-03 12:22:43 +03:00
|
|
|
|
2025-04-12 08:53:13 +03:00
|
|
|
elements.extend(
|
|
|
|
|
int_move_elements
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(OutputRenderElements::from),
|
|
|
|
|
);
|
2025-04-24 22:11:04 +03:00
|
|
|
|
2025-04-25 10:11:27 +03:00
|
|
|
elements.extend(
|
|
|
|
|
insert_hint_elements
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(OutputRenderElements::from),
|
|
|
|
|
);
|
|
|
|
|
|
2025-04-25 08:53:16 +03:00
|
|
|
for (ws_geo, ws_elements) in monitor_elements {
|
|
|
|
|
// Collect all other layer-shell elements.
|
|
|
|
|
let mut layer_elems = SplitElements::default();
|
2025-05-06 16:51:18 +03:00
|
|
|
extend_from_layer(&mut layer_elems, Layer::Bottom, false);
|
|
|
|
|
extend_from_layer(&mut layer_elems, Layer::Background, false);
|
2025-04-25 08:53:16 +03:00
|
|
|
|
|
|
|
|
elements.extend(
|
|
|
|
|
layer_elems
|
|
|
|
|
.popups
|
|
|
|
|
.into_iter()
|
|
|
|
|
.filter_map(|elem| scale_relocate_crop(elem, output_scale, zoom, ws_geo))
|
|
|
|
|
.map(OutputRenderElements::from),
|
|
|
|
|
);
|
2025-04-24 22:11:04 +03:00
|
|
|
|
2025-04-25 08:53:16 +03:00
|
|
|
elements.extend(ws_elements.into_iter().map(OutputRenderElements::from));
|
2023-08-15 12:49:26 +04:00
|
|
|
|
2025-04-25 08:53:16 +03:00
|
|
|
elements.extend(
|
|
|
|
|
layer_elems
|
|
|
|
|
.normal
|
|
|
|
|
.into_iter()
|
|
|
|
|
.filter_map(|elem| scale_relocate_crop(elem, output_scale, zoom, ws_geo))
|
|
|
|
|
.map(OutputRenderElements::from),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if let Some(elem) =
|
|
|
|
|
scale_relocate_crop(background.clone(), output_scale, zoom, ws_geo)
|
|
|
|
|
{
|
|
|
|
|
elements.push(OutputRenderElements::from(elem));
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-05-01 10:09:25 +03:00
|
|
|
|
|
|
|
|
elements.extend(
|
|
|
|
|
workspace_shadow_elements
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(OutputRenderElements::from),
|
|
|
|
|
);
|
2025-01-03 12:22:43 +03:00
|
|
|
}
|
2023-08-10 09:57:13 +04:00
|
|
|
|
2025-04-25 08:53:16 +03:00
|
|
|
// Then the backdrop.
|
2025-05-06 16:51:18 +03:00
|
|
|
let mut layer_elems = SplitElements::default();
|
|
|
|
|
extend_from_layer(&mut layer_elems, Layer::Background, true);
|
|
|
|
|
elements.extend(layer_elems.into_iter().map(OutputRenderElements::from));
|
|
|
|
|
|
2025-04-25 08:53:16 +03:00
|
|
|
elements.push(backdrop);
|
2023-10-27 08:34:00 +04:00
|
|
|
|
2024-05-02 17:52:06 +04:00
|
|
|
if self.debug_draw_opaque_regions {
|
|
|
|
|
draw_opaque_regions(&mut elements, output_scale);
|
|
|
|
|
}
|
2024-05-03 10:25:51 +04:00
|
|
|
|
2023-08-27 17:44:22 +04:00
|
|
|
elements
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-06 16:51:18 +03:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2024-11-14 10:24:04 +03:00
|
|
|
fn render_layer<R: NiriRenderer>(
|
|
|
|
|
&self,
|
|
|
|
|
renderer: &mut R,
|
2024-11-14 11:33:08 +03:00
|
|
|
target: RenderTarget,
|
2024-11-14 10:24:04 +03:00
|
|
|
layer_map: &LayerMap,
|
|
|
|
|
layer: Layer,
|
2025-01-03 12:22:43 +03:00
|
|
|
elements: &mut SplitElements<LayerSurfaceRenderElement<R>>,
|
2025-05-06 16:51:18 +03:00
|
|
|
for_backdrop: bool,
|
2024-11-14 10:24:04 +03:00
|
|
|
) {
|
2025-01-03 12:22:43 +03:00
|
|
|
// LayerMap returns layers in reverse stacking order.
|
|
|
|
|
let iter = layer_map.layers_on(layer).rev().filter_map(|surface| {
|
|
|
|
|
let mapped = self.mapped_layer_surfaces.get(surface)?;
|
2025-05-06 16:51:18 +03:00
|
|
|
|
|
|
|
|
if for_backdrop != mapped.place_within_backdrop() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-03 12:22:43 +03:00
|
|
|
let geo = layer_map.layer_geometry(surface)?;
|
|
|
|
|
Some((mapped, geo))
|
|
|
|
|
});
|
|
|
|
|
for (mapped, geo) in iter {
|
2025-05-12 08:16:01 +03:00
|
|
|
elements.extend(mapped.render(renderer, geo.loc.to_f64(), target));
|
2025-01-03 12:22:43 +03:00
|
|
|
}
|
2024-11-14 10:24:04 +03:00
|
|
|
}
|
|
|
|
|
|
2023-09-14 22:28:26 +04:00
|
|
|
fn redraw(&mut self, backend: &mut Backend, output: &Output) {
|
2023-08-27 17:44:22 +04:00
|
|
|
let _span = tracy_client::span!("Niri::redraw");
|
|
|
|
|
|
2024-01-03 11:28:42 +04:00
|
|
|
// Verify our invariant.
|
2023-10-09 17:37:15 +04:00
|
|
|
let state = self.output_state.get_mut(output).unwrap();
|
|
|
|
|
assert!(matches!(
|
|
|
|
|
state.redraw_state,
|
2024-03-23 12:46:26 +04:00
|
|
|
RedrawState::Queued | RedrawState::WaitingForEstimatedVBlankAndQueued(_)
|
2023-10-09 17:37:15 +04:00
|
|
|
));
|
|
|
|
|
|
2023-11-24 09:21:56 +04:00
|
|
|
let target_presentation_time = state.frame_clock.next_presentation_time();
|
2023-08-27 17:44:22 +04:00
|
|
|
|
2024-11-24 09:41:43 +03:00
|
|
|
// Freeze the clock at the target time.
|
|
|
|
|
self.clock.set_unadjusted(target_presentation_time);
|
|
|
|
|
|
|
|
|
|
self.update_render_elements(Some(output));
|
|
|
|
|
|
2024-01-03 11:31:04 +04:00
|
|
|
let mut res = RenderResult::Skipped;
|
2024-01-03 11:28:42 +04:00
|
|
|
if self.monitors_active {
|
2024-11-23 11:27:27 +03:00
|
|
|
let state = self.output_state.get_mut(output).unwrap();
|
2024-07-15 15:51:48 +02:00
|
|
|
state.unfinished_animations_remain = self.layout.are_animations_ongoing(Some(output));
|
2024-01-18 11:15:48 +04:00
|
|
|
state.unfinished_animations_remain |=
|
|
|
|
|
self.config_error_notification.are_animations_ongoing();
|
2025-08-22 08:50:52 +03:00
|
|
|
state.unfinished_animations_remain |= self.exit_confirm_dialog.are_animations_ongoing();
|
2024-07-08 11:24:08 +04:00
|
|
|
state.unfinished_animations_remain |= self.screenshot_ui.are_animations_ongoing();
|
2024-11-23 11:27:27 +03:00
|
|
|
state.unfinished_animations_remain |= state.screen_transition.is_some();
|
2024-01-18 11:15:48 +04:00
|
|
|
|
2024-01-03 11:28:42 +04:00
|
|
|
// Also keep redrawing if the current cursor is animated.
|
|
|
|
|
state.unfinished_animations_remain |= self
|
|
|
|
|
.cursor_manager
|
|
|
|
|
.is_current_cursor_animated(output.current_scale().integer_scale());
|
2023-08-16 10:59:34 +04:00
|
|
|
|
2025-05-12 08:16:01 +03:00
|
|
|
// Also check layer surfaces.
|
|
|
|
|
if !state.unfinished_animations_remain {
|
|
|
|
|
state.unfinished_animations_remain |= layer_map_for_output(output)
|
|
|
|
|
.layers()
|
|
|
|
|
.filter_map(|surface| self.mapped_layer_surfaces.get(surface))
|
|
|
|
|
.any(|mapped| mapped.are_animations_ongoing());
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-03 11:28:42 +04:00
|
|
|
// Render.
|
|
|
|
|
res = backend.render(self, output, target_presentation_time);
|
|
|
|
|
}
|
2023-10-13 13:30:11 +04:00
|
|
|
|
|
|
|
|
let is_locked = self.is_locked();
|
|
|
|
|
let state = self.output_state.get_mut(output).unwrap();
|
2024-01-03 11:28:42 +04:00
|
|
|
|
2024-01-03 11:31:04 +04:00
|
|
|
if res == RenderResult::Skipped {
|
2024-01-03 11:28:42 +04:00
|
|
|
// Update the redraw state on failed render.
|
2024-03-23 12:46:26 +04:00
|
|
|
state.redraw_state = if let RedrawState::WaitingForEstimatedVBlank(token)
|
|
|
|
|
| RedrawState::WaitingForEstimatedVBlankAndQueued(token) =
|
|
|
|
|
state.redraw_state
|
|
|
|
|
{
|
|
|
|
|
RedrawState::WaitingForEstimatedVBlank(token)
|
|
|
|
|
} else {
|
|
|
|
|
RedrawState::Idle
|
|
|
|
|
};
|
2024-02-21 07:40:50 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update the lock render state on successful render, or if monitors are inactive. When
|
|
|
|
|
// monitors are inactive on a TTY, they have no framebuffer attached, so no sensitive data
|
|
|
|
|
// from a last render will be visible.
|
|
|
|
|
if res != RenderResult::Skipped || !self.monitors_active {
|
2023-10-13 13:30:11 +04:00
|
|
|
state.lock_render_state = if is_locked {
|
|
|
|
|
LockRenderState::Locked
|
|
|
|
|
} else {
|
|
|
|
|
LockRenderState::Unlocked
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we're in process of locking the session, check if the requirements were met.
|
|
|
|
|
match mem::take(&mut self.lock_state) {
|
|
|
|
|
LockState::Locking(confirmation) => {
|
2024-02-21 07:40:50 +04:00
|
|
|
if state.lock_render_state == LockRenderState::Unlocked {
|
|
|
|
|
// We needed to render a locked frame on this output but failed.
|
|
|
|
|
self.unlock();
|
2023-10-13 13:30:11 +04:00
|
|
|
} else {
|
2024-02-21 07:40:50 +04:00
|
|
|
// Check if all outputs are now locked.
|
2023-10-13 13:30:11 +04:00
|
|
|
let all_locked = self
|
|
|
|
|
.output_state
|
|
|
|
|
.values()
|
|
|
|
|
.all(|state| state.lock_render_state == LockRenderState::Locked);
|
|
|
|
|
|
|
|
|
|
if all_locked {
|
2024-02-21 07:40:50 +04:00
|
|
|
// All outputs are locked, report success.
|
2024-02-17 07:47:06 +04:00
|
|
|
let lock = confirmation.ext_session_lock().clone();
|
2023-10-13 13:30:11 +04:00
|
|
|
confirmation.lock();
|
2024-02-17 07:47:06 +04:00
|
|
|
self.lock_state = LockState::Locked(lock);
|
2023-10-13 13:30:11 +04:00
|
|
|
} else {
|
2024-02-21 07:40:50 +04:00
|
|
|
// Still waiting for other outputs.
|
2023-10-13 13:30:11 +04:00
|
|
|
self.lock_state = LockState::Locking(confirmation);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
lock_state => self.lock_state = lock_state,
|
|
|
|
|
}
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2024-08-22 18:58:07 +10:00
|
|
|
self.refresh_on_demand_vrr(backend, output);
|
|
|
|
|
|
2023-08-27 17:44:22 +04:00
|
|
|
// Send the frame callbacks.
|
2023-10-01 19:41:42 +04:00
|
|
|
//
|
|
|
|
|
// FIXME: The logic here could be a bit smarter. Currently, during an animation, the
|
|
|
|
|
// surfaces that are visible for the very last frame (e.g. because the camera is moving
|
|
|
|
|
// away) will receive frame callbacks, and the surfaces that are invisible but will become
|
|
|
|
|
// visible next frame will not receive frame callbacks (so they will show stale contents for
|
|
|
|
|
// one frame). We could advance the animations for the next frame and send frame callbacks
|
|
|
|
|
// according to the expected new positions.
|
|
|
|
|
//
|
|
|
|
|
// However, this should probably be restricted to sending frame callbacks to more surfaces,
|
|
|
|
|
// to err on the safe side.
|
2023-08-27 17:44:22 +04:00
|
|
|
self.send_frame_callbacks(output);
|
2024-03-08 13:10:55 +01:00
|
|
|
backend.with_primary_renderer(|renderer| {
|
2024-07-30 13:38:25 +10:00
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
|
|
|
|
{
|
|
|
|
|
// Render and send to PipeWire screencast streams.
|
|
|
|
|
self.render_for_screen_cast(renderer, output, target_presentation_time);
|
|
|
|
|
|
|
|
|
|
// FIXME: when a window is hidden, it should probably still receive frame callbacks
|
|
|
|
|
// and get rendered for screen cast. This is currently
|
|
|
|
|
// unimplemented, but happens to work by chance, since output
|
|
|
|
|
// redrawing is more eager than it should be.
|
|
|
|
|
self.render_windows_for_screen_cast(renderer, output, target_presentation_time);
|
|
|
|
|
}
|
2024-06-27 11:36:24 +04:00
|
|
|
|
2024-07-30 13:38:25 +10:00
|
|
|
self.render_for_screencopy_with_damage(renderer, output);
|
2024-03-08 13:10:55 +01:00
|
|
|
});
|
2023-08-27 17:44:22 +04:00
|
|
|
}
|
|
|
|
|
|
2024-08-22 18:58:07 +10:00
|
|
|
pub fn refresh_on_demand_vrr(&mut self, backend: &mut Backend, output: &Output) {
|
|
|
|
|
let _span = tracy_client::span!("Niri::refresh_on_demand_vrr");
|
2024-09-03 12:13:04 +03:00
|
|
|
|
|
|
|
|
let name = output.user_data().get::<OutputName>().unwrap();
|
2024-09-03 13:25:06 +03:00
|
|
|
let on_demand = self
|
2024-08-22 18:58:07 +10:00
|
|
|
.config
|
|
|
|
|
.borrow()
|
|
|
|
|
.outputs
|
2024-09-03 12:13:04 +03:00
|
|
|
.find(name)
|
2025-01-10 09:01:23 +03:00
|
|
|
.is_some_and(|output| output.is_vrr_on_demand());
|
2024-08-22 18:58:07 +10:00
|
|
|
if !on_demand {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let current = self.layout.windows_for_output(output).any(|mapped| {
|
|
|
|
|
mapped.rules().variable_refresh_rate == Some(true) && {
|
|
|
|
|
let mut visible = false;
|
|
|
|
|
mapped.window.with_surfaces(|surface, states| {
|
|
|
|
|
if !visible
|
|
|
|
|
&& surface_primary_scanout_output(surface, states).as_ref() == Some(output)
|
|
|
|
|
{
|
|
|
|
|
visible = true;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
visible
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
backend.set_output_on_demand_vrr(self, output, current);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-01 19:41:42 +04:00
|
|
|
pub fn update_primary_scanout_output(
|
|
|
|
|
&self,
|
|
|
|
|
output: &Output,
|
|
|
|
|
render_element_states: &RenderElementStates,
|
|
|
|
|
) {
|
|
|
|
|
// FIXME: potentially tweak the compare function. The default one currently always prefers a
|
|
|
|
|
// higher refresh-rate output, which is not always desirable (i.e. with a very small
|
|
|
|
|
// overlap).
|
|
|
|
|
//
|
|
|
|
|
// While we only have cursors and DnD icons crossing output boundaries though, it doesn't
|
|
|
|
|
// matter all that much.
|
2023-10-29 14:04:38 +04:00
|
|
|
if let CursorImageStatus::Surface(surface) = &self.cursor_manager.cursor_image() {
|
2023-10-01 19:41:42 +04:00
|
|
|
with_surface_tree_downward(
|
|
|
|
|
surface,
|
|
|
|
|
(),
|
|
|
|
|
|_, _, _| TraversalAction::DoChildren(()),
|
|
|
|
|
|surface, states, _| {
|
|
|
|
|
update_surface_primary_scanout_output(
|
|
|
|
|
surface,
|
|
|
|
|
output,
|
|
|
|
|
states,
|
|
|
|
|
render_element_states,
|
|
|
|
|
default_primary_scanout_output_compare,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
|_, _, _| true,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-24 18:17:51 +01:00
|
|
|
if let Some(surface) = self.dnd_icon.as_ref().map(|icon| &icon.surface) {
|
2023-10-01 19:41:42 +04:00
|
|
|
with_surface_tree_downward(
|
|
|
|
|
surface,
|
|
|
|
|
(),
|
|
|
|
|
|_, _, _| TraversalAction::DoChildren(()),
|
|
|
|
|
|surface, states, _| {
|
|
|
|
|
update_surface_primary_scanout_output(
|
|
|
|
|
surface,
|
|
|
|
|
output,
|
|
|
|
|
states,
|
|
|
|
|
render_element_states,
|
|
|
|
|
default_primary_scanout_output_compare,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
|_, _, _| true,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We're only updating the current output's windows and layer surfaces. This should be fine
|
|
|
|
|
// as in niri they can only be rendered on a single output at a time.
|
|
|
|
|
//
|
|
|
|
|
// The reason to do this at all is that it keeps track of whether the surface is visible or
|
|
|
|
|
// not in a unified way with the pointer surfaces, which makes the logic elsewhere simpler.
|
|
|
|
|
|
2024-03-19 14:41:17 +04:00
|
|
|
for mapped in self.layout.windows_for_output(output) {
|
|
|
|
|
let win = &mapped.window;
|
2025-03-01 09:45:57 +03:00
|
|
|
let offscreen_data = mapped.offscreen_data();
|
|
|
|
|
let offscreen_data = offscreen_data.as_ref();
|
2024-02-07 11:30:52 +04:00
|
|
|
|
2023-10-01 19:41:42 +04:00
|
|
|
win.with_surfaces(|surface, states| {
|
2025-03-01 09:45:57 +03:00
|
|
|
let primary_scanout_output = states
|
2024-02-07 11:30:52 +04:00
|
|
|
.data_map
|
2024-08-22 18:58:07 +10:00
|
|
|
.get_or_insert_threadsafe(Mutex::<PrimaryScanoutOutput>::default);
|
2025-03-01 09:45:57 +03:00
|
|
|
let mut primary_scanout_output = primary_scanout_output.lock().unwrap();
|
|
|
|
|
|
|
|
|
|
let mut id = Id::from_wayland_resource(surface);
|
|
|
|
|
|
|
|
|
|
if let Some(data) = offscreen_data {
|
|
|
|
|
// We have offscreen data; it's likely that all surfaces are on it.
|
|
|
|
|
if data.states.element_was_presented(id.clone()) {
|
|
|
|
|
// If the surface was presented to the offscreen, use the offscreen's id.
|
|
|
|
|
id = data.id.clone();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we the surface wasn't presented to the offscreen it can mean:
|
|
|
|
|
//
|
|
|
|
|
// - The surface was invisible. For example, it's obscured by another surface on
|
|
|
|
|
// the offscreen, or simply isn't mapped.
|
|
|
|
|
// - The surface is rendered separately from the offscreen, for example: popups
|
|
|
|
|
// during the window resize animation.
|
|
|
|
|
//
|
|
|
|
|
// In both of these cases, using the original surface element id and the
|
|
|
|
|
// original states is the correct thing to do. We may find the surface in the
|
|
|
|
|
// original states (in the second case). Either way, we definitely know it is
|
|
|
|
|
// *not* in the offscreen, and we won't miss it.
|
|
|
|
|
//
|
|
|
|
|
// There's one edge case: if the surface is both in the offscreen and separate,
|
|
|
|
|
// and the offscreen itself is invisible, while the separate surface is
|
|
|
|
|
// visible. In this case we'll currently mark the surface as invisible. We
|
|
|
|
|
// don't really use offscreens like that however, and if we start, it's easy
|
|
|
|
|
// enough to fix (need an extra check).
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
primary_scanout_output.update_from_render_element_states(
|
|
|
|
|
id,
|
|
|
|
|
output,
|
|
|
|
|
render_element_states,
|
|
|
|
|
|_, _, output, _| output,
|
|
|
|
|
);
|
2023-10-01 19:41:42 +04:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for surface in layer_map_for_output(output).layers() {
|
|
|
|
|
surface.with_surfaces(|surface, states| {
|
|
|
|
|
update_surface_primary_scanout_output(
|
|
|
|
|
surface,
|
|
|
|
|
output,
|
|
|
|
|
states,
|
|
|
|
|
render_element_states,
|
|
|
|
|
// Layer surfaces are shown only on one output at a time.
|
|
|
|
|
|_, _, output, _| output,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
2023-10-13 13:30:11 +04:00
|
|
|
|
|
|
|
|
if let Some(surface) = &self.output_state[output].lock_surface {
|
|
|
|
|
with_surface_tree_downward(
|
|
|
|
|
surface.wl_surface(),
|
|
|
|
|
(),
|
|
|
|
|
|_, _, _| TraversalAction::DoChildren(()),
|
|
|
|
|
|surface, states, _| {
|
|
|
|
|
update_surface_primary_scanout_output(
|
|
|
|
|
surface,
|
|
|
|
|
output,
|
|
|
|
|
states,
|
|
|
|
|
render_element_states,
|
|
|
|
|
default_primary_scanout_output_compare,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
|_, _, _| true,
|
|
|
|
|
);
|
|
|
|
|
}
|
2023-10-01 19:41:42 +04:00
|
|
|
}
|
|
|
|
|
|
2024-01-03 18:16:20 +04:00
|
|
|
pub fn send_dmabuf_feedbacks(
|
|
|
|
|
&self,
|
|
|
|
|
output: &Output,
|
|
|
|
|
feedback: &SurfaceDmabufFeedback,
|
|
|
|
|
render_element_states: &RenderElementStates,
|
|
|
|
|
) {
|
2023-09-03 15:15:55 +04:00
|
|
|
let _span = tracy_client::span!("Niri::send_dmabuf_feedbacks");
|
|
|
|
|
|
2023-10-01 19:41:42 +04:00
|
|
|
// We can unconditionally send the current output's feedback to regular and layer-shell
|
|
|
|
|
// surfaces, as they can only be displayed on a single output at a time. Even if a surface
|
|
|
|
|
// is currently invisible, this is the DMABUF feedback that it should know about.
|
2024-03-19 14:41:17 +04:00
|
|
|
for mapped in self.layout.windows_for_output(output) {
|
|
|
|
|
mapped.window.send_dmabuf_feedback(
|
2024-01-03 18:16:20 +04:00
|
|
|
output,
|
|
|
|
|
|_, _| Some(output.clone()),
|
|
|
|
|
|surface, _| {
|
|
|
|
|
select_dmabuf_feedback(
|
|
|
|
|
surface,
|
|
|
|
|
render_element_states,
|
|
|
|
|
&feedback.render,
|
|
|
|
|
&feedback.scanout,
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
);
|
2023-10-01 19:41:42 +04:00
|
|
|
}
|
2023-09-03 15:15:55 +04:00
|
|
|
|
|
|
|
|
for surface in layer_map_for_output(output).layers() {
|
2024-01-03 18:16:20 +04:00
|
|
|
surface.send_dmabuf_feedback(
|
|
|
|
|
output,
|
|
|
|
|
|_, _| Some(output.clone()),
|
|
|
|
|
|surface, _| {
|
|
|
|
|
select_dmabuf_feedback(
|
|
|
|
|
surface,
|
|
|
|
|
render_element_states,
|
|
|
|
|
&feedback.render,
|
|
|
|
|
&feedback.scanout,
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
);
|
2023-09-03 15:15:55 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-13 13:30:11 +04:00
|
|
|
if let Some(surface) = &self.output_state[output].lock_surface {
|
|
|
|
|
send_dmabuf_feedback_surface_tree(
|
|
|
|
|
surface.wl_surface(),
|
|
|
|
|
output,
|
|
|
|
|
|_, _| Some(output.clone()),
|
2024-01-03 18:16:20 +04:00
|
|
|
|surface, _| {
|
|
|
|
|
select_dmabuf_feedback(
|
|
|
|
|
surface,
|
|
|
|
|
render_element_states,
|
|
|
|
|
&feedback.render,
|
|
|
|
|
&feedback.scanout,
|
|
|
|
|
)
|
|
|
|
|
},
|
2023-10-13 13:30:11 +04:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-24 18:17:51 +01:00
|
|
|
if let Some(surface) = self.dnd_icon.as_ref().map(|icon| &icon.surface) {
|
2023-09-03 15:15:55 +04:00
|
|
|
send_dmabuf_feedback_surface_tree(
|
|
|
|
|
surface,
|
|
|
|
|
output,
|
2023-10-01 19:41:42 +04:00
|
|
|
surface_primary_scanout_output,
|
2024-01-03 18:16:20 +04:00
|
|
|
|surface, _| {
|
|
|
|
|
select_dmabuf_feedback(
|
|
|
|
|
surface,
|
|
|
|
|
render_element_states,
|
|
|
|
|
&feedback.render,
|
|
|
|
|
&feedback.scanout,
|
|
|
|
|
)
|
|
|
|
|
},
|
2023-09-03 15:15:55 +04:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-29 14:04:38 +04:00
|
|
|
if let CursorImageStatus::Surface(surface) = &self.cursor_manager.cursor_image() {
|
2023-09-03 15:15:55 +04:00
|
|
|
send_dmabuf_feedback_surface_tree(
|
|
|
|
|
surface,
|
|
|
|
|
output,
|
2023-10-01 19:41:42 +04:00
|
|
|
surface_primary_scanout_output,
|
2024-01-03 18:16:20 +04:00
|
|
|
|surface, _| {
|
|
|
|
|
select_dmabuf_feedback(
|
|
|
|
|
surface,
|
|
|
|
|
render_element_states,
|
|
|
|
|
&feedback.render,
|
|
|
|
|
&feedback.scanout,
|
|
|
|
|
)
|
|
|
|
|
},
|
2023-09-03 15:15:55 +04:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-05 10:08:56 +03:00
|
|
|
pub fn send_frame_callbacks(&mut self, output: &Output) {
|
2023-08-27 17:44:22 +04:00
|
|
|
let _span = tracy_client::span!("Niri::send_frame_callbacks");
|
|
|
|
|
|
2023-09-29 13:12:50 +04:00
|
|
|
let state = self.output_state.get(output).unwrap();
|
2024-03-02 08:10:05 +04:00
|
|
|
let sequence = state.frame_callback_sequence;
|
2023-09-29 13:12:50 +04:00
|
|
|
|
2023-10-01 19:41:42 +04:00
|
|
|
let should_send = |surface: &WlSurface, states: &SurfaceData| {
|
|
|
|
|
// Do the standard primary scanout output check. For pointer surfaces it deduplicates
|
|
|
|
|
// the frame callbacks across potentially multiple outputs, and for regular windows and
|
|
|
|
|
// layer-shell surfaces it avoids sending frame callbacks to invisible surfaces.
|
|
|
|
|
let current_primary_output = surface_primary_scanout_output(surface, states);
|
|
|
|
|
if current_primary_output.as_ref() != Some(output) {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Next, check the throttling status.
|
2023-09-29 13:12:50 +04:00
|
|
|
let frame_throttling_state = states
|
|
|
|
|
.data_map
|
|
|
|
|
.get_or_insert(SurfaceFrameThrottlingState::default);
|
|
|
|
|
let mut last_sent_at = frame_throttling_state.last_sent_at.borrow_mut();
|
|
|
|
|
|
|
|
|
|
let mut send = true;
|
|
|
|
|
|
|
|
|
|
// If we already sent a frame callback to this surface this output refresh
|
|
|
|
|
// cycle, don't send one again to prevent empty-damage commit busy loops.
|
|
|
|
|
if let Some((last_output, last_sequence)) = &*last_sent_at {
|
2024-03-02 08:10:05 +04:00
|
|
|
if last_output == output && *last_sequence == sequence {
|
2023-09-29 13:12:50 +04:00
|
|
|
send = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if send {
|
2024-03-02 08:10:05 +04:00
|
|
|
*last_sent_at = Some((output.clone(), sequence));
|
2023-10-01 19:41:42 +04:00
|
|
|
Some(output.clone())
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
2023-09-29 13:12:50 +04:00
|
|
|
};
|
|
|
|
|
|
2023-09-04 14:27:16 +04:00
|
|
|
let frame_callback_time = get_monotonic_time();
|
2023-10-01 19:41:42 +04:00
|
|
|
|
2025-02-05 10:08:56 +03:00
|
|
|
for mapped in self.layout.windows_for_output_mut(output) {
|
|
|
|
|
mapped.send_frame(
|
2024-03-12 10:17:45 +04:00
|
|
|
output,
|
|
|
|
|
frame_callback_time,
|
|
|
|
|
FRAME_CALLBACK_THROTTLE,
|
|
|
|
|
should_send,
|
|
|
|
|
);
|
2023-10-01 19:41:42 +04:00
|
|
|
}
|
2023-08-15 12:49:26 +04:00
|
|
|
|
2023-08-16 10:59:34 +04:00
|
|
|
for surface in layer_map_for_output(output).layers() {
|
2024-03-12 10:17:45 +04:00
|
|
|
surface.send_frame(
|
|
|
|
|
output,
|
|
|
|
|
frame_callback_time,
|
|
|
|
|
FRAME_CALLBACK_THROTTLE,
|
|
|
|
|
should_send,
|
|
|
|
|
);
|
2023-08-15 12:49:26 +04:00
|
|
|
}
|
2023-08-16 07:08:57 +04:00
|
|
|
|
2023-10-13 13:30:11 +04:00
|
|
|
if let Some(surface) = &self.output_state[output].lock_surface {
|
|
|
|
|
send_frames_surface_tree(
|
|
|
|
|
surface.wl_surface(),
|
|
|
|
|
output,
|
|
|
|
|
frame_callback_time,
|
2024-03-12 10:17:45 +04:00
|
|
|
FRAME_CALLBACK_THROTTLE,
|
|
|
|
|
should_send,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-24 18:17:51 +01:00
|
|
|
if let Some(surface) = self.dnd_icon.as_ref().map(|icon| &icon.surface) {
|
2024-03-12 10:17:45 +04:00
|
|
|
send_frames_surface_tree(
|
|
|
|
|
surface,
|
|
|
|
|
output,
|
|
|
|
|
frame_callback_time,
|
|
|
|
|
FRAME_CALLBACK_THROTTLE,
|
|
|
|
|
should_send,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let CursorImageStatus::Surface(surface) = self.cursor_manager.cursor_image() {
|
|
|
|
|
send_frames_surface_tree(
|
|
|
|
|
surface,
|
|
|
|
|
output,
|
|
|
|
|
frame_callback_time,
|
|
|
|
|
FRAME_CALLBACK_THROTTLE,
|
2023-10-13 13:30:11 +04:00
|
|
|
should_send,
|
|
|
|
|
);
|
|
|
|
|
}
|
2024-03-12 10:17:45 +04:00
|
|
|
}
|
|
|
|
|
|
2025-02-05 10:08:56 +03:00
|
|
|
pub fn send_frame_callbacks_on_fallback_timer(&mut self) {
|
2024-03-12 10:17:45 +04:00
|
|
|
let _span = tracy_client::span!("Niri::send_frame_callbacks_on_fallback_timer");
|
|
|
|
|
|
|
|
|
|
// Make up a bogus output; we don't care about it here anyway, just the throttling timer.
|
|
|
|
|
let output = Output::new(
|
|
|
|
|
String::new(),
|
|
|
|
|
PhysicalProperties {
|
|
|
|
|
size: Size::from((0, 0)),
|
|
|
|
|
subpixel: Subpixel::Unknown,
|
|
|
|
|
make: String::new(),
|
|
|
|
|
model: String::new(),
|
2025-08-11 09:02:28 +03:00
|
|
|
serial_number: String::new(),
|
2024-03-12 10:17:45 +04:00
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
let output = &output;
|
|
|
|
|
|
|
|
|
|
let frame_callback_time = get_monotonic_time();
|
|
|
|
|
|
2025-02-05 10:08:56 +03:00
|
|
|
self.layout.with_windows_mut(|mapped, _| {
|
|
|
|
|
mapped.send_frame(
|
2024-03-12 10:17:45 +04:00
|
|
|
output,
|
|
|
|
|
frame_callback_time,
|
|
|
|
|
FRAME_CALLBACK_THROTTLE,
|
|
|
|
|
|_, _| None,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
for (output, state) in self.output_state.iter() {
|
|
|
|
|
for surface in layer_map_for_output(output).layers() {
|
|
|
|
|
surface.send_frame(
|
|
|
|
|
output,
|
|
|
|
|
frame_callback_time,
|
|
|
|
|
FRAME_CALLBACK_THROTTLE,
|
|
|
|
|
|_, _| None,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(surface) = &state.lock_surface {
|
|
|
|
|
send_frames_surface_tree(
|
|
|
|
|
surface.wl_surface(),
|
|
|
|
|
output,
|
|
|
|
|
frame_callback_time,
|
|
|
|
|
FRAME_CALLBACK_THROTTLE,
|
|
|
|
|
|_, _| None,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-13 13:30:11 +04:00
|
|
|
|
2024-02-24 18:17:51 +01:00
|
|
|
if let Some(surface) = &self.dnd_icon.as_ref().map(|icon| &icon.surface) {
|
2024-03-12 10:17:45 +04:00
|
|
|
send_frames_surface_tree(
|
|
|
|
|
surface,
|
|
|
|
|
output,
|
|
|
|
|
frame_callback_time,
|
|
|
|
|
FRAME_CALLBACK_THROTTLE,
|
|
|
|
|
|_, _| None,
|
|
|
|
|
);
|
2023-08-16 07:08:57 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-29 14:04:38 +04:00
|
|
|
if let CursorImageStatus::Surface(surface) = self.cursor_manager.cursor_image() {
|
2024-03-12 10:17:45 +04:00
|
|
|
send_frames_surface_tree(
|
|
|
|
|
surface,
|
|
|
|
|
output,
|
|
|
|
|
frame_callback_time,
|
|
|
|
|
FRAME_CALLBACK_THROTTLE,
|
|
|
|
|
|_, _| None,
|
|
|
|
|
);
|
2023-08-16 07:08:57 +04:00
|
|
|
}
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
2023-08-16 10:59:34 +04:00
|
|
|
|
|
|
|
|
pub fn take_presentation_feedbacks(
|
|
|
|
|
&mut self,
|
|
|
|
|
output: &Output,
|
|
|
|
|
render_element_states: &RenderElementStates,
|
|
|
|
|
) -> OutputPresentationFeedback {
|
|
|
|
|
let mut feedback = OutputPresentationFeedback::new(output);
|
|
|
|
|
|
2023-10-29 14:04:38 +04:00
|
|
|
if let CursorImageStatus::Surface(surface) = &self.cursor_manager.cursor_image() {
|
2023-08-16 10:59:34 +04:00
|
|
|
take_presentation_feedback_surface_tree(
|
|
|
|
|
surface,
|
|
|
|
|
&mut feedback,
|
2023-10-01 19:41:42 +04:00
|
|
|
surface_primary_scanout_output,
|
2023-08-16 10:59:34 +04:00
|
|
|
|surface, _| {
|
|
|
|
|
surface_presentation_feedback_flags_from_states(surface, render_element_states)
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-24 18:17:51 +01:00
|
|
|
if let Some(surface) = self.dnd_icon.as_ref().map(|icon| &icon.surface) {
|
2023-08-16 10:59:34 +04:00
|
|
|
take_presentation_feedback_surface_tree(
|
|
|
|
|
surface,
|
|
|
|
|
&mut feedback,
|
2023-10-01 19:41:42 +04:00
|
|
|
surface_primary_scanout_output,
|
2023-08-16 10:59:34 +04:00
|
|
|
|surface, _| {
|
|
|
|
|
surface_presentation_feedback_flags_from_states(surface, render_element_states)
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-19 14:41:17 +04:00
|
|
|
for mapped in self.layout.windows_for_output(output) {
|
|
|
|
|
mapped.window.take_presentation_feedback(
|
2023-08-16 10:59:34 +04:00
|
|
|
&mut feedback,
|
2023-10-01 19:41:42 +04:00
|
|
|
surface_primary_scanout_output,
|
2023-08-16 10:59:34 +04:00
|
|
|
|surface, _| {
|
|
|
|
|
surface_presentation_feedback_flags_from_states(surface, render_element_states)
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for surface in layer_map_for_output(output).layers() {
|
|
|
|
|
surface.take_presentation_feedback(
|
|
|
|
|
&mut feedback,
|
2023-10-01 19:41:42 +04:00
|
|
|
surface_primary_scanout_output,
|
2023-08-16 10:59:34 +04:00
|
|
|
|surface, _| {
|
|
|
|
|
surface_presentation_feedback_flags_from_states(surface, render_element_states)
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-13 13:30:11 +04:00
|
|
|
if let Some(surface) = &self.output_state[output].lock_surface {
|
|
|
|
|
take_presentation_feedback_surface_tree(
|
|
|
|
|
surface.wl_surface(),
|
|
|
|
|
&mut feedback,
|
|
|
|
|
surface_primary_scanout_output,
|
|
|
|
|
|surface, _| {
|
|
|
|
|
surface_presentation_feedback_flags_from_states(surface, render_element_states)
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-16 10:59:34 +04:00
|
|
|
feedback
|
|
|
|
|
}
|
2023-08-27 19:34:37 +04:00
|
|
|
|
2023-09-30 09:58:34 +04:00
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
2024-01-03 11:22:11 +04:00
|
|
|
fn render_for_screen_cast(
|
2023-08-27 19:34:37 +04:00
|
|
|
&mut self,
|
2023-09-26 10:06:07 +04:00
|
|
|
renderer: &mut GlesRenderer,
|
2023-08-27 19:34:37 +04:00
|
|
|
output: &Output,
|
2023-11-24 09:21:56 +04:00
|
|
|
target_presentation_time: Duration,
|
2023-09-08 17:54:02 +04:00
|
|
|
) {
|
2024-01-03 11:22:11 +04:00
|
|
|
let _span = tracy_client::span!("Niri::render_for_screen_cast");
|
2023-08-27 19:34:37 +04:00
|
|
|
|
2024-06-27 11:36:24 +04:00
|
|
|
let target = CastTarget::Output(output.downgrade());
|
|
|
|
|
|
2023-08-27 19:34:37 +04:00
|
|
|
let size = output.current_mode().unwrap().size;
|
2024-01-28 14:25:40 +01:00
|
|
|
let transform = output.current_transform();
|
|
|
|
|
let size = transform.transform_size(size);
|
|
|
|
|
|
2023-09-21 13:48:32 +04:00
|
|
|
let scale = Scale::from(output.current_scale().fractional_scale());
|
2023-08-27 19:34:37 +04:00
|
|
|
|
2024-01-03 11:22:11 +04:00
|
|
|
let mut elements = None;
|
2024-01-29 10:13:59 +04:00
|
|
|
let mut casts_to_stop = vec![];
|
2024-01-03 11:22:11 +04:00
|
|
|
|
|
|
|
|
let mut casts = mem::take(&mut self.casts);
|
|
|
|
|
for cast in &mut casts {
|
2025-08-02 13:29:08 +03:00
|
|
|
if !cast.is_active() {
|
2023-09-08 17:54:02 +04:00
|
|
|
continue;
|
|
|
|
|
}
|
2023-08-27 19:34:37 +04:00
|
|
|
|
2024-06-27 11:36:24 +04:00
|
|
|
if cast.target != target {
|
2023-09-08 17:54:02 +04:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-27 11:36:24 +04:00
|
|
|
match cast.ensure_size(size) {
|
|
|
|
|
Ok(CastSizeChange::Ready) => (),
|
|
|
|
|
Ok(CastSizeChange::Pending) => continue,
|
|
|
|
|
Err(err) => {
|
|
|
|
|
warn!("error updating stream size, stopping screencast: {err:?}");
|
|
|
|
|
casts_to_stop.push(cast.session_id);
|
2024-06-21 11:05:28 +03:00
|
|
|
}
|
2024-06-27 11:36:24 +04:00
|
|
|
}
|
2024-06-21 11:05:28 +03:00
|
|
|
|
2025-08-03 13:29:48 +03:00
|
|
|
if cast.check_time_and_schedule(output, target_presentation_time) {
|
2024-01-29 10:13:59 +04:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-27 11:36:24 +04:00
|
|
|
// FIXME: Hidden / embedded / metadata cursor
|
|
|
|
|
let elements = elements.get_or_insert_with(|| {
|
|
|
|
|
self.render(renderer, output, true, RenderTarget::Screencast)
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-03 13:29:48 +03:00
|
|
|
if cast.dequeue_buffer_and_render(renderer, elements, size, scale) {
|
2024-06-27 11:36:24 +04:00
|
|
|
cast.last_frame_time = target_presentation_time;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
self.casts = casts;
|
|
|
|
|
|
|
|
|
|
for id in casts_to_stop {
|
|
|
|
|
self.stop_cast(id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
|
|
|
|
fn render_windows_for_screen_cast(
|
|
|
|
|
&mut self,
|
|
|
|
|
renderer: &mut GlesRenderer,
|
|
|
|
|
output: &Output,
|
|
|
|
|
target_presentation_time: Duration,
|
|
|
|
|
) {
|
|
|
|
|
let _span = tracy_client::span!("Niri::render_windows_for_screen_cast");
|
|
|
|
|
|
|
|
|
|
let scale = Scale::from(output.current_scale().fractional_scale());
|
|
|
|
|
|
|
|
|
|
let mut casts_to_stop = vec![];
|
|
|
|
|
|
|
|
|
|
let mut casts = mem::take(&mut self.casts);
|
|
|
|
|
for cast in &mut casts {
|
2025-08-02 13:29:08 +03:00
|
|
|
if !cast.is_active() {
|
2024-06-27 11:36:24 +04:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let CastTarget::Window { id } = cast.target else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut windows = self.layout.windows_for_output(output);
|
2024-08-31 10:22:57 +03:00
|
|
|
let Some(mapped) = windows.find(|win| win.id().get() == id) else {
|
2024-06-27 11:36:24 +04:00
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
|
2024-06-28 12:35:12 +04:00
|
|
|
let bbox = mapped
|
|
|
|
|
.window
|
|
|
|
|
.bbox_with_popups()
|
|
|
|
|
.to_physical_precise_up(scale);
|
2024-06-27 11:36:24 +04:00
|
|
|
|
2024-06-28 12:35:12 +04:00
|
|
|
match cast.ensure_size(bbox.size) {
|
2024-06-27 11:36:24 +04:00
|
|
|
Ok(CastSizeChange::Ready) => (),
|
|
|
|
|
Ok(CastSizeChange::Pending) => continue,
|
|
|
|
|
Err(err) => {
|
|
|
|
|
warn!("error updating stream size, stopping screencast: {err:?}");
|
|
|
|
|
casts_to_stop.push(cast.session_id);
|
2023-11-24 09:34:53 +04:00
|
|
|
}
|
2023-09-08 17:54:02 +04:00
|
|
|
}
|
|
|
|
|
|
2025-08-03 13:29:48 +03:00
|
|
|
if cast.check_time_and_schedule(output, target_presentation_time) {
|
2024-06-27 11:36:24 +04:00
|
|
|
continue;
|
|
|
|
|
}
|
2023-08-27 19:34:37 +04:00
|
|
|
|
2024-06-27 11:36:24 +04:00
|
|
|
// FIXME: pointer.
|
2024-08-23 11:02:34 +03:00
|
|
|
let elements: Vec<_> = mapped.render_for_screen_cast(renderer, scale).collect();
|
2023-09-08 17:54:02 +04:00
|
|
|
|
2025-08-03 13:29:48 +03:00
|
|
|
if cast.dequeue_buffer_and_render(renderer, &elements, bbox.size, scale) {
|
2024-06-27 11:36:24 +04:00
|
|
|
cast.last_frame_time = target_presentation_time;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
self.casts = casts;
|
|
|
|
|
|
|
|
|
|
for id in casts_to_stop {
|
|
|
|
|
self.stop_cast(id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-30 13:38:25 +10:00
|
|
|
pub fn render_for_screencopy_with_damage(
|
2024-03-08 13:10:55 +01:00
|
|
|
&mut self,
|
2024-07-30 13:38:25 +10:00
|
|
|
renderer: &mut GlesRenderer,
|
|
|
|
|
output: &Output,
|
|
|
|
|
) {
|
|
|
|
|
let _span = tracy_client::span!("Niri::render_for_screencopy_with_damage");
|
|
|
|
|
|
|
|
|
|
let mut screencopy_state = mem::take(&mut self.screencopy_state);
|
|
|
|
|
let elements = OnceCell::new();
|
|
|
|
|
|
|
|
|
|
for queue in screencopy_state.queues_mut() {
|
|
|
|
|
let (damage_tracker, screencopy) = queue.split();
|
|
|
|
|
if let Some(screencopy) = screencopy {
|
|
|
|
|
if screencopy.output() == output {
|
|
|
|
|
let elements = elements.get_or_init(|| {
|
|
|
|
|
self.render(renderer, output, true, RenderTarget::ScreenCapture)
|
|
|
|
|
});
|
|
|
|
|
// FIXME: skip elements if not including pointers
|
|
|
|
|
let render_result = Self::render_for_screencopy_internal(
|
|
|
|
|
renderer,
|
|
|
|
|
output,
|
|
|
|
|
elements,
|
|
|
|
|
true,
|
|
|
|
|
damage_tracker,
|
|
|
|
|
screencopy,
|
|
|
|
|
);
|
|
|
|
|
match render_result {
|
2024-08-08 12:52:24 +03:00
|
|
|
Ok((sync, damages)) => {
|
2024-07-30 13:38:25 +10:00
|
|
|
if let Some(damages) = damages {
|
2024-08-08 11:53:42 +03:00
|
|
|
// Convert from Physical coordinates back to Buffer coordinates.
|
|
|
|
|
let transform = output.current_transform();
|
|
|
|
|
let physical_size =
|
|
|
|
|
transform.transform_size(screencopy.buffer_size());
|
|
|
|
|
let damages = damages.iter().map(|dmg| {
|
|
|
|
|
dmg.to_logical(1).to_buffer(
|
|
|
|
|
1,
|
|
|
|
|
transform.invert(),
|
|
|
|
|
&physical_size.to_logical(1),
|
|
|
|
|
)
|
|
|
|
|
});
|
|
|
|
|
|
2024-07-30 13:38:25 +10:00
|
|
|
screencopy.damage(damages);
|
2024-08-08 12:52:24 +03:00
|
|
|
queue.pop().submit_after_sync(false, sync, &self.event_loop);
|
2024-07-30 13:38:25 +10:00
|
|
|
} else {
|
|
|
|
|
trace!("no damage found, waiting till next redraw");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
// Recreate damage tracker to report full damage next check.
|
|
|
|
|
*damage_tracker =
|
|
|
|
|
OutputDamageTracker::new((0, 0), 1.0, Transform::Normal);
|
|
|
|
|
queue.pop();
|
|
|
|
|
warn!("error rendering for screencopy: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.screencopy_state = screencopy_state;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn render_for_screencopy_without_damage(
|
|
|
|
|
&mut self,
|
|
|
|
|
renderer: &mut GlesRenderer,
|
|
|
|
|
manager: &ZwlrScreencopyManagerV1,
|
2024-03-08 13:10:55 +01:00
|
|
|
screencopy: Screencopy,
|
|
|
|
|
) -> anyhow::Result<()> {
|
2024-07-30 13:38:25 +10:00
|
|
|
let _span = tracy_client::span!("Niri::render_for_screencopy");
|
2024-03-08 13:10:55 +01:00
|
|
|
|
2024-07-30 13:38:25 +10:00
|
|
|
let output = screencopy.output();
|
|
|
|
|
ensure!(
|
|
|
|
|
self.output_state.contains_key(output),
|
|
|
|
|
"screencopy output missing"
|
|
|
|
|
);
|
2024-05-15 20:08:37 +04:00
|
|
|
|
2024-08-23 12:48:05 +03:00
|
|
|
self.update_render_elements(Some(output));
|
2024-07-30 13:38:25 +10:00
|
|
|
|
|
|
|
|
let elements = self.render(
|
|
|
|
|
renderer,
|
|
|
|
|
output,
|
|
|
|
|
screencopy.overlay_cursor(),
|
|
|
|
|
RenderTarget::ScreenCapture,
|
|
|
|
|
);
|
|
|
|
|
let Some(queue) = self.screencopy_state.get_queue_mut(manager) else {
|
|
|
|
|
bail!("screencopy manager destroyed already");
|
|
|
|
|
};
|
|
|
|
|
let damage_tracker = queue.split().0;
|
|
|
|
|
|
|
|
|
|
let render_result = Self::render_for_screencopy_internal(
|
|
|
|
|
renderer,
|
|
|
|
|
output,
|
|
|
|
|
&elements,
|
|
|
|
|
false,
|
|
|
|
|
damage_tracker,
|
|
|
|
|
&screencopy,
|
2024-08-08 12:52:24 +03:00
|
|
|
);
|
2024-07-30 13:38:25 +10:00
|
|
|
|
2024-08-08 12:52:24 +03:00
|
|
|
let res = render_result
|
|
|
|
|
.map(|(sync, _damage)| screencopy.submit_after_sync(false, sync, &self.event_loop));
|
|
|
|
|
|
|
|
|
|
if res.is_err() {
|
|
|
|
|
// Recreate damage tracker to report full damage next check.
|
|
|
|
|
*damage_tracker = OutputDamageTracker::new((0, 0), 1.0, Transform::Normal);
|
2024-07-30 13:38:25 +10:00
|
|
|
}
|
2024-08-08 12:52:24 +03:00
|
|
|
|
|
|
|
|
res
|
2024-07-30 13:38:25 +10:00
|
|
|
}
|
2024-03-08 13:10:55 +01:00
|
|
|
|
2024-08-08 12:52:24 +03:00
|
|
|
#[allow(clippy::type_complexity)]
|
2024-07-30 13:38:25 +10:00
|
|
|
fn render_for_screencopy_internal<'a>(
|
|
|
|
|
renderer: &mut GlesRenderer,
|
|
|
|
|
output: &Output,
|
|
|
|
|
elements: &[OutputRenderElements<GlesRenderer>],
|
|
|
|
|
with_damage: bool,
|
|
|
|
|
damage_tracker: &'a mut OutputDamageTracker,
|
|
|
|
|
screencopy: &Screencopy,
|
2024-08-08 12:52:24 +03:00
|
|
|
) -> anyhow::Result<(Option<SyncPoint>, Option<&'a Vec<Rectangle<i32, Physical>>>)> {
|
2024-07-30 13:38:25 +10:00
|
|
|
let OutputModeSource::Static {
|
|
|
|
|
size: last_size,
|
|
|
|
|
scale: last_scale,
|
|
|
|
|
transform: last_transform,
|
|
|
|
|
} = damage_tracker.mode().clone()
|
|
|
|
|
else {
|
|
|
|
|
unreachable!("damage tracker must have static mode");
|
|
|
|
|
};
|
2024-03-08 13:10:55 +01:00
|
|
|
|
2024-07-30 13:38:25 +10:00
|
|
|
let size = screencopy.buffer_size();
|
|
|
|
|
let scale: Scale<f64> = output.current_scale().fractional_scale().into();
|
|
|
|
|
let transform = output.current_transform();
|
2024-03-08 13:10:55 +01:00
|
|
|
|
2024-07-30 13:38:25 +10:00
|
|
|
if size != last_size || scale != last_scale || transform != last_transform {
|
|
|
|
|
*damage_tracker = OutputDamageTracker::new(size, scale, transform);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let region_loc = screencopy.region_loc();
|
|
|
|
|
let elements = elements
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|element| {
|
|
|
|
|
RelocateRenderElement::from_element(
|
|
|
|
|
element,
|
|
|
|
|
region_loc.upscale(-1),
|
|
|
|
|
Relocate::Relative,
|
|
|
|
|
)
|
2024-03-08 13:10:55 +01:00
|
|
|
})
|
2024-07-30 13:38:25 +10:00
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
|
|
// Just checked damage tracker has static mode
|
|
|
|
|
let damages = damage_tracker.damage_output(1, &elements).unwrap().0;
|
|
|
|
|
if with_damage && damages.is_none() {
|
2024-08-08 12:52:24 +03:00
|
|
|
return Ok((None, None));
|
2024-07-30 13:38:25 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let elements = elements.iter().rev();
|
|
|
|
|
|
2024-08-08 12:52:24 +03:00
|
|
|
let sync = match screencopy.buffer() {
|
2024-07-30 13:38:25 +10:00
|
|
|
ScreencopyBuffer::Dmabuf(dmabuf) => {
|
2024-08-08 12:52:24 +03:00
|
|
|
let sync =
|
2024-07-30 13:38:25 +10:00
|
|
|
render_to_dmabuf(renderer, dmabuf.clone(), size, scale, transform, elements)
|
|
|
|
|
.context("error rendering to screencopy dmabuf")?;
|
2024-08-08 12:52:24 +03:00
|
|
|
Some(sync)
|
2024-07-30 13:38:25 +10:00
|
|
|
}
|
|
|
|
|
ScreencopyBuffer::Shm(wl_buffer) => {
|
|
|
|
|
render_to_shm(renderer, wl_buffer, size, scale, transform, elements)
|
|
|
|
|
.context("error rendering to screencopy shm buffer")?;
|
2024-08-08 12:52:24 +03:00
|
|
|
None
|
2024-07-30 13:38:25 +10:00
|
|
|
}
|
2024-08-08 12:52:24 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Ok((sync, damages))
|
2024-03-08 13:10:55 +01:00
|
|
|
}
|
|
|
|
|
|
2023-10-10 08:49:47 +04:00
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
|
|
|
|
fn stop_cast(&mut self, session_id: usize) {
|
|
|
|
|
let _span = tracy_client::span!("Niri::stop_cast");
|
|
|
|
|
|
|
|
|
|
debug!(session_id, "StopCast");
|
|
|
|
|
|
|
|
|
|
for i in (0..self.casts.len()).rev() {
|
|
|
|
|
let cast = &self.casts[i];
|
|
|
|
|
if cast.session_id != session_id {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let cast = self.casts.swap_remove(i);
|
|
|
|
|
if let Err(err) = cast.stream.disconnect() {
|
|
|
|
|
warn!("error disconnecting stream: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-10 09:55:44 +04:00
|
|
|
let dbus = &self.dbus.as_ref().unwrap();
|
|
|
|
|
let server = dbus.conn_screen_cast.as_ref().unwrap().object_server();
|
2025-07-13 12:49:00 +03:00
|
|
|
let path = format!("/org/gnome/Mutter/ScreenCast/Session/u{session_id}");
|
2023-10-10 08:49:47 +04:00
|
|
|
if let Ok(iface) = server.interface::<_, mutter_screen_cast::Session>(path) {
|
|
|
|
|
let _span = tracy_client::span!("invoking Session::stop");
|
|
|
|
|
|
|
|
|
|
async_io::block_on(async move {
|
|
|
|
|
iface
|
|
|
|
|
.get()
|
2024-12-10 01:58:26 +02:00
|
|
|
.stop(server.inner(), iface.signal_emitter().clone())
|
2023-10-10 08:49:47 +04:00
|
|
|
.await
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-15 11:22:30 +03:00
|
|
|
#[cfg(not(feature = "xdp-gnome-screencast"))]
|
|
|
|
|
pub fn stop_casts_for_target(&mut self, _target: CastTarget) {}
|
|
|
|
|
|
2024-06-27 11:36:24 +04:00
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
|
|
|
|
pub fn stop_casts_for_target(&mut self, target: CastTarget) {
|
|
|
|
|
let _span = tracy_client::span!("Niri::stop_casts_for_target");
|
|
|
|
|
|
|
|
|
|
// This is O(N^2) but it shouldn't be a problem I think.
|
2025-03-15 11:23:01 +03:00
|
|
|
let mut saw_dynamic = false;
|
|
|
|
|
let mut ids = Vec::new();
|
|
|
|
|
for cast in &self.casts {
|
|
|
|
|
if cast.target != target {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if cast.dynamic_target {
|
|
|
|
|
saw_dynamic = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ids.push(cast.session_id);
|
|
|
|
|
}
|
2024-06-27 11:36:24 +04:00
|
|
|
|
|
|
|
|
for id in ids {
|
|
|
|
|
self.stop_cast(id);
|
|
|
|
|
}
|
2025-03-15 11:23:01 +03:00
|
|
|
|
|
|
|
|
// We don't stop dynamic casts, instead we switch them to Nothing.
|
|
|
|
|
if saw_dynamic {
|
|
|
|
|
self.event_loop
|
|
|
|
|
.insert_idle(|state| state.set_dynamic_cast_target(CastTarget::Nothing));
|
|
|
|
|
}
|
2024-06-27 11:36:24 +04:00
|
|
|
}
|
|
|
|
|
|
2024-07-30 13:38:25 +10:00
|
|
|
pub fn remove_screencopy_output(&mut self, output: &Output) {
|
|
|
|
|
let _span = tracy_client::span!("Niri::remove_screencopy_output");
|
|
|
|
|
for queue in self.screencopy_state.queues_mut() {
|
|
|
|
|
queue.remove_output(output);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-03 10:25:51 +04:00
|
|
|
pub fn debug_toggle_damage(&mut self) {
|
|
|
|
|
self.debug_draw_damage = !self.debug_draw_damage;
|
|
|
|
|
|
|
|
|
|
if self.debug_draw_damage {
|
|
|
|
|
for (output, state) in &mut self.output_state {
|
|
|
|
|
state.debug_damage_tracker = OutputDamageTracker::from_output(output);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.queue_redraw_all();
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-07 09:48:19 +04:00
|
|
|
pub fn capture_screenshots<'a>(
|
|
|
|
|
&'a self,
|
|
|
|
|
renderer: &'a mut GlesRenderer,
|
|
|
|
|
) -> impl Iterator<Item = (Output, [OutputScreenshot; 3])> + 'a {
|
|
|
|
|
self.global_space.outputs().cloned().filter_map(|output| {
|
|
|
|
|
let size = output.current_mode().unwrap().size;
|
|
|
|
|
let transform = output.current_transform();
|
|
|
|
|
let size = transform.transform_size(size);
|
|
|
|
|
|
|
|
|
|
let scale = Scale::from(output.current_scale().fractional_scale());
|
|
|
|
|
let targets = [
|
|
|
|
|
RenderTarget::Output,
|
|
|
|
|
RenderTarget::Screencast,
|
|
|
|
|
RenderTarget::ScreenCapture,
|
|
|
|
|
];
|
|
|
|
|
let screenshot = targets.map(|target| {
|
|
|
|
|
let elements = self.render::<GlesRenderer>(renderer, &output, false, target);
|
|
|
|
|
let elements = elements.iter().rev();
|
|
|
|
|
|
|
|
|
|
let res = render_to_texture(
|
|
|
|
|
renderer,
|
|
|
|
|
size,
|
|
|
|
|
scale,
|
|
|
|
|
Transform::Normal,
|
|
|
|
|
Fourcc::Abgr8888,
|
|
|
|
|
elements,
|
|
|
|
|
);
|
|
|
|
|
if let Err(err) = &res {
|
|
|
|
|
warn!("error rendering output {}: {err:?}", output.name());
|
|
|
|
|
}
|
|
|
|
|
let res_output = res.ok();
|
|
|
|
|
|
|
|
|
|
let pointer = self.pointer_element(renderer, &output);
|
|
|
|
|
let res_pointer = if pointer.is_empty() {
|
|
|
|
|
None
|
|
|
|
|
} else {
|
|
|
|
|
let res = render_to_encompassing_texture(
|
|
|
|
|
renderer,
|
|
|
|
|
scale,
|
|
|
|
|
Transform::Normal,
|
|
|
|
|
Fourcc::Abgr8888,
|
|
|
|
|
&pointer,
|
|
|
|
|
);
|
|
|
|
|
if let Err(err) = &res {
|
|
|
|
|
warn!("error rendering pointer for {}: {err:?}", output.name());
|
|
|
|
|
}
|
|
|
|
|
res.ok()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
res_output.map(|(texture, _)| {
|
|
|
|
|
OutputScreenshot::from_textures(
|
|
|
|
|
renderer,
|
|
|
|
|
scale,
|
|
|
|
|
texture,
|
|
|
|
|
res_pointer.map(|(texture, _, geo)| (texture, geo)),
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if screenshot.iter().any(|res| res.is_none()) {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let screenshot = screenshot.map(|res| res.unwrap());
|
|
|
|
|
Some((output, screenshot))
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-15 20:08:37 +04:00
|
|
|
pub fn screenshot(
|
|
|
|
|
&mut self,
|
|
|
|
|
renderer: &mut GlesRenderer,
|
|
|
|
|
output: &Output,
|
2025-01-14 09:10:45 +01:00
|
|
|
write_to_disk: bool,
|
2025-03-09 20:55:30 +00:00
|
|
|
include_pointer: bool,
|
2024-05-15 20:08:37 +04:00
|
|
|
) -> anyhow::Result<()> {
|
2023-09-08 17:54:02 +04:00
|
|
|
let _span = tracy_client::span!("Niri::screenshot");
|
|
|
|
|
|
2024-08-23 12:48:05 +03:00
|
|
|
self.update_render_elements(Some(output));
|
2024-05-15 20:08:37 +04:00
|
|
|
|
2023-09-08 17:54:02 +04:00
|
|
|
let size = output.current_mode().unwrap().size;
|
2024-01-28 14:25:40 +01:00
|
|
|
let transform = output.current_transform();
|
|
|
|
|
let size = transform.transform_size(size);
|
|
|
|
|
|
2023-09-21 13:48:32 +04:00
|
|
|
let scale = Scale::from(output.current_scale().fractional_scale());
|
2025-03-09 20:55:30 +00:00
|
|
|
let elements = self.render::<GlesRenderer>(
|
|
|
|
|
renderer,
|
|
|
|
|
output,
|
|
|
|
|
include_pointer,
|
|
|
|
|
RenderTarget::ScreenCapture,
|
|
|
|
|
);
|
2024-02-06 17:42:32 +04:00
|
|
|
let elements = elements.iter().rev();
|
2024-03-08 13:10:55 +01:00
|
|
|
let pixels = render_to_vec(
|
|
|
|
|
renderer,
|
|
|
|
|
size,
|
|
|
|
|
scale,
|
|
|
|
|
Transform::Normal,
|
|
|
|
|
Fourcc::Abgr8888,
|
|
|
|
|
elements,
|
|
|
|
|
)?;
|
2023-08-27 19:34:37 +04:00
|
|
|
|
2025-01-14 09:10:45 +01:00
|
|
|
self.save_screenshot(size, pixels, write_to_disk)
|
2023-10-10 12:42:24 +04:00
|
|
|
.context("error saving screenshot")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn screenshot_window(
|
2023-10-26 17:30:03 +04:00
|
|
|
&self,
|
2023-10-10 12:42:24 +04:00
|
|
|
renderer: &mut GlesRenderer,
|
|
|
|
|
output: &Output,
|
2024-03-24 09:03:59 +04:00
|
|
|
mapped: &Mapped,
|
2025-01-14 09:10:45 +01:00
|
|
|
write_to_disk: bool,
|
2023-10-10 12:42:24 +04:00
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
|
let _span = tracy_client::span!("Niri::screenshot_window");
|
|
|
|
|
|
|
|
|
|
let scale = Scale::from(output.current_scale().fractional_scale());
|
2025-01-23 10:40:52 +03:00
|
|
|
let alpha = if mapped.is_fullscreen() || mapped.is_ignoring_opacity_window_rule() {
|
2024-03-24 09:03:59 +04:00
|
|
|
1.
|
|
|
|
|
} else {
|
|
|
|
|
mapped.rules().opacity.unwrap_or(1.).clamp(0., 1.)
|
|
|
|
|
};
|
2023-10-10 12:42:24 +04:00
|
|
|
// FIXME: pointer.
|
2024-03-24 09:03:59 +04:00
|
|
|
let elements = mapped.render(
|
2023-10-10 12:42:24 +04:00
|
|
|
renderer,
|
2024-06-17 09:16:28 +03:00
|
|
|
mapped.window.geometry().loc.to_f64(),
|
2023-10-10 12:42:24 +04:00
|
|
|
scale,
|
2024-03-24 09:03:59 +04:00
|
|
|
alpha,
|
|
|
|
|
RenderTarget::ScreenCapture,
|
2023-10-10 12:42:24 +04:00
|
|
|
);
|
2025-02-27 08:09:44 +03:00
|
|
|
let geo = encompassing_geo(scale, elements.iter());
|
2024-03-24 09:03:59 +04:00
|
|
|
let elements = elements.iter().rev().map(|elem| {
|
|
|
|
|
RelocateRenderElement::from_element(elem, geo.loc.upscale(-1), Relocate::Relative)
|
|
|
|
|
});
|
2024-03-08 13:10:55 +01:00
|
|
|
let pixels = render_to_vec(
|
|
|
|
|
renderer,
|
2024-03-24 09:03:59 +04:00
|
|
|
geo.size,
|
2024-03-08 13:10:55 +01:00
|
|
|
scale,
|
|
|
|
|
Transform::Normal,
|
|
|
|
|
Fourcc::Abgr8888,
|
|
|
|
|
elements,
|
|
|
|
|
)?;
|
2023-10-10 12:42:24 +04:00
|
|
|
|
2025-01-14 09:10:45 +01:00
|
|
|
self.save_screenshot(geo.size, pixels, write_to_disk)
|
2023-10-10 12:42:24 +04:00
|
|
|
.context("error saving screenshot")
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-30 20:29:03 +04:00
|
|
|
pub fn save_screenshot(
|
|
|
|
|
&self,
|
|
|
|
|
size: Size<i32, Physical>,
|
|
|
|
|
pixels: Vec<u8>,
|
2025-01-14 09:10:45 +01:00
|
|
|
write_to_disk: bool,
|
2023-10-30 20:29:03 +04:00
|
|
|
) -> anyhow::Result<()> {
|
2025-01-14 09:10:45 +01:00
|
|
|
let path = write_to_disk
|
|
|
|
|
.then(|| match make_screenshot_path(&self.config.borrow()) {
|
|
|
|
|
Ok(path) => path,
|
|
|
|
|
Err(err) => {
|
|
|
|
|
warn!("error making screenshot path: {err:?}");
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.flatten();
|
2023-08-27 19:34:37 +04:00
|
|
|
|
2023-10-10 12:11:05 +04:00
|
|
|
// Prepare to set the encoded image as our clipboard selection. This must be done from the
|
|
|
|
|
// main thread.
|
|
|
|
|
let (tx, rx) = calloop::channel::sync_channel::<Arc<[u8]>>(1);
|
|
|
|
|
self.event_loop
|
|
|
|
|
.insert_source(rx, move |event, _, state| match event {
|
|
|
|
|
calloop::channel::Event::Msg(buf) => {
|
|
|
|
|
set_data_device_selection(
|
|
|
|
|
&state.niri.display_handle,
|
|
|
|
|
&state.niri.seat,
|
|
|
|
|
vec![String::from("image/png")],
|
|
|
|
|
buf.clone(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
calloop::channel::Event::Closed => (),
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
// Encode and save the image in a thread as it's slow.
|
2023-08-27 19:34:37 +04:00
|
|
|
thread::spawn(move || {
|
2023-10-10 12:11:05 +04:00
|
|
|
let mut buf = vec![];
|
|
|
|
|
|
2023-10-24 17:49:08 +04:00
|
|
|
let w = std::io::Cursor::new(&mut buf);
|
|
|
|
|
if let Err(err) = write_png_rgba8(w, size.w as u32, size.h as u32, &pixels) {
|
2023-10-10 12:11:05 +04:00
|
|
|
warn!("error encoding screenshot image: {err:?}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let buf: Arc<[u8]> = Arc::from(buf.into_boxed_slice());
|
|
|
|
|
let _ = tx.send(buf.clone());
|
|
|
|
|
|
2023-10-31 17:06:14 +04:00
|
|
|
let mut image_path = None;
|
|
|
|
|
|
2023-10-31 08:57:44 +04:00
|
|
|
if let Some(path) = path {
|
|
|
|
|
debug!("saving screenshot to {path:?}");
|
2023-10-31 17:06:14 +04:00
|
|
|
|
2024-03-28 20:59:42 +04:00
|
|
|
if let Some(parent) = path.parent() {
|
|
|
|
|
if let Err(err) = std::fs::create_dir(parent) {
|
|
|
|
|
if err.kind() != std::io::ErrorKind::AlreadyExists {
|
|
|
|
|
warn!("error creating screenshot directory: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-31 17:06:14 +04:00
|
|
|
match std::fs::write(&path, buf) {
|
|
|
|
|
Ok(()) => image_path = Some(path),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
warn!("error saving screenshot image: {err:?}");
|
|
|
|
|
}
|
2023-10-31 08:57:44 +04:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
debug!("not saving screenshot to disk");
|
2023-08-27 19:34:37 +04:00
|
|
|
}
|
2023-10-31 17:06:14 +04:00
|
|
|
|
|
|
|
|
#[cfg(feature = "dbus")]
|
2025-01-02 09:30:56 +03:00
|
|
|
if let Err(err) = crate::utils::show_screenshot_notification(image_path) {
|
|
|
|
|
warn!("error showing screenshot notification: {err:?}");
|
|
|
|
|
}
|
2023-10-31 17:06:14 +04:00
|
|
|
#[cfg(not(feature = "dbus"))]
|
|
|
|
|
drop(image_path);
|
2023-08-27 19:34:37 +04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2023-09-20 09:28:23 +04:00
|
|
|
|
2023-10-10 10:25:26 +04:00
|
|
|
#[cfg(feature = "dbus")]
|
2023-09-20 09:28:23 +04:00
|
|
|
pub fn screenshot_all_outputs(
|
2024-05-15 20:08:37 +04:00
|
|
|
&mut self,
|
2023-09-20 09:28:23 +04:00
|
|
|
renderer: &mut GlesRenderer,
|
|
|
|
|
include_pointer: bool,
|
|
|
|
|
on_done: impl FnOnce(PathBuf) + Send + 'static,
|
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
|
let _span = tracy_client::span!("Niri::screenshot_all_outputs");
|
|
|
|
|
|
2024-08-23 12:48:05 +03:00
|
|
|
self.update_render_elements(None);
|
2024-05-15 20:08:37 +04:00
|
|
|
|
2023-09-20 09:28:23 +04:00
|
|
|
let outputs: Vec<_> = self.global_space.outputs().cloned().collect();
|
|
|
|
|
|
2024-01-21 10:03:13 +04:00
|
|
|
// FIXME: support multiple outputs, needs fixing multi-scale handling and cropping.
|
|
|
|
|
anyhow::ensure!(outputs.len() == 1);
|
2023-09-20 09:28:23 +04:00
|
|
|
|
2024-01-21 10:03:13 +04:00
|
|
|
let output = outputs.into_iter().next().unwrap();
|
|
|
|
|
let geom = self.global_space.output_geometry(&output).unwrap();
|
|
|
|
|
|
|
|
|
|
let output_scale = output.current_scale().integer_scale();
|
|
|
|
|
let geom = geom.to_physical(output_scale);
|
2023-09-20 09:28:23 +04:00
|
|
|
|
2024-01-21 10:03:13 +04:00
|
|
|
let size = geom.size;
|
2024-01-28 14:25:40 +01:00
|
|
|
let transform = output.current_transform();
|
|
|
|
|
let size = transform.transform_size(size);
|
|
|
|
|
|
2024-03-24 09:03:59 +04:00
|
|
|
let elements = self.render::<GlesRenderer>(
|
|
|
|
|
renderer,
|
|
|
|
|
&output,
|
|
|
|
|
include_pointer,
|
|
|
|
|
RenderTarget::ScreenCapture,
|
|
|
|
|
);
|
2024-02-06 17:42:32 +04:00
|
|
|
let elements = elements.iter().rev();
|
2024-01-21 10:03:13 +04:00
|
|
|
let pixels = render_to_vec(
|
|
|
|
|
renderer,
|
|
|
|
|
size,
|
|
|
|
|
Scale::from(f64::from(output_scale)),
|
2024-03-08 13:10:55 +01:00
|
|
|
Transform::Normal,
|
2024-01-21 10:03:13 +04:00
|
|
|
Fourcc::Abgr8888,
|
2024-02-06 17:42:32 +04:00
|
|
|
elements,
|
2024-01-21 10:03:13 +04:00
|
|
|
)?;
|
2023-09-20 09:28:23 +04:00
|
|
|
|
2023-10-31 08:57:44 +04:00
|
|
|
let path = make_screenshot_path(&self.config.borrow())
|
2023-10-31 14:23:54 +04:00
|
|
|
.ok()
|
|
|
|
|
.flatten()
|
|
|
|
|
.unwrap_or_else(|| {
|
|
|
|
|
let mut path = env::temp_dir();
|
|
|
|
|
path.push("screenshot.png");
|
|
|
|
|
path
|
|
|
|
|
});
|
2023-09-20 09:28:23 +04:00
|
|
|
debug!("saving screenshot to {path:?}");
|
|
|
|
|
|
|
|
|
|
thread::spawn(move || {
|
2023-10-24 17:49:08 +04:00
|
|
|
let file = match std::fs::File::create(&path) {
|
|
|
|
|
Ok(file) => file,
|
|
|
|
|
Err(err) => {
|
|
|
|
|
warn!("error creating file: {err:?}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
2023-09-20 09:28:23 +04:00
|
|
|
|
2023-10-24 17:49:08 +04:00
|
|
|
let w = std::io::BufWriter::new(file);
|
|
|
|
|
if let Err(err) = write_png_rgba8(w, size.w as u32, size.h as u32, &pixels) {
|
|
|
|
|
warn!("error encoding screenshot image: {err:?}");
|
2023-09-20 09:28:23 +04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
on_done(path);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2023-10-13 13:30:11 +04:00
|
|
|
|
|
|
|
|
pub fn is_locked(&self) -> bool {
|
2025-03-13 18:34:47 +03:00
|
|
|
match self.lock_state {
|
|
|
|
|
LockState::Unlocked | LockState::WaitingForSurfaces { .. } => false,
|
|
|
|
|
LockState::Locking(_) | LockState::Locked(_) => true,
|
|
|
|
|
}
|
2023-10-13 13:30:11 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn lock(&mut self, confirmation: SessionLocker) {
|
2024-02-17 07:47:06 +04:00
|
|
|
// Check if another client is in the process of locking.
|
2025-03-13 18:34:47 +03:00
|
|
|
if matches!(
|
|
|
|
|
self.lock_state,
|
|
|
|
|
LockState::WaitingForSurfaces { .. } | LockState::Locking(_)
|
|
|
|
|
) {
|
2024-02-17 07:47:06 +04:00
|
|
|
info!("refusing lock as another client is currently locking");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if we're already locked with an active client.
|
|
|
|
|
if let LockState::Locked(lock) = &self.lock_state {
|
|
|
|
|
if lock.is_alive() {
|
|
|
|
|
info!("refusing lock as already locked with an active client");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the client had died, continue with the new lock.
|
2025-03-13 18:34:47 +03:00
|
|
|
info!("locking session (replacing existing dead lock)");
|
|
|
|
|
|
|
|
|
|
// Since the session was already locked, we know that the outputs are blanked, and
|
|
|
|
|
// can lock right away.
|
|
|
|
|
let lock = confirmation.ext_session_lock().clone();
|
|
|
|
|
confirmation.lock();
|
|
|
|
|
self.lock_state = LockState::Locked(lock);
|
|
|
|
|
|
|
|
|
|
return;
|
2024-02-17 07:47:06 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-13 13:30:11 +04:00
|
|
|
info!("locking session");
|
|
|
|
|
|
2024-11-08 12:28:31 +03:00
|
|
|
if self.output_state.is_empty() {
|
|
|
|
|
// There are no outputs, lock the session right away.
|
2025-03-13 18:34:47 +03:00
|
|
|
self.screenshot_ui.close();
|
|
|
|
|
self.cursor_manager
|
|
|
|
|
.set_cursor_image(CursorImageStatus::default_named());
|
|
|
|
|
|
2024-11-08 12:28:31 +03:00
|
|
|
let lock = confirmation.ext_session_lock().clone();
|
|
|
|
|
confirmation.lock();
|
|
|
|
|
self.lock_state = LockState::Locked(lock);
|
|
|
|
|
} else {
|
2025-03-13 18:34:47 +03:00
|
|
|
// There are outputs which we need to redraw before locking. But before we do that,
|
|
|
|
|
// let's wait for the lock surfaces.
|
|
|
|
|
//
|
|
|
|
|
// Give them a second; swaylock can take its time to paint a big enough image.
|
|
|
|
|
let timer = Timer::from_duration(Duration::from_millis(1000));
|
|
|
|
|
let deadline_token = self
|
|
|
|
|
.event_loop
|
|
|
|
|
.insert_source(timer, |_, _, state| {
|
|
|
|
|
trace!("lock deadline expired, continuing");
|
|
|
|
|
state.niri.continue_to_locking();
|
|
|
|
|
TimeoutAction::Drop
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
self.lock_state = LockState::WaitingForSurfaces {
|
|
|
|
|
confirmation,
|
|
|
|
|
deadline_token,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn maybe_continue_to_locking(&mut self) {
|
|
|
|
|
if !matches!(self.lock_state, LockState::WaitingForSurfaces { .. }) {
|
|
|
|
|
// Not waiting.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if there are any outputs whose lock surfaces had not had a commit yet.
|
|
|
|
|
for state in self.output_state.values() {
|
|
|
|
|
let Some(surface) = &state.lock_surface else {
|
|
|
|
|
// Surface not created yet.
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if !is_mapped(surface.wl_surface()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// All good.
|
|
|
|
|
trace!("lock surfaces are ready, continuing");
|
|
|
|
|
self.continue_to_locking();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn continue_to_locking(&mut self) {
|
|
|
|
|
match mem::take(&mut self.lock_state) {
|
|
|
|
|
LockState::WaitingForSurfaces {
|
|
|
|
|
confirmation,
|
|
|
|
|
deadline_token,
|
|
|
|
|
} => {
|
|
|
|
|
self.event_loop.remove(deadline_token);
|
|
|
|
|
|
|
|
|
|
self.screenshot_ui.close();
|
|
|
|
|
self.cursor_manager
|
|
|
|
|
.set_cursor_image(CursorImageStatus::default_named());
|
|
|
|
|
|
|
|
|
|
if self.output_state.is_empty() {
|
|
|
|
|
// There are no outputs, lock the session right away.
|
|
|
|
|
let lock = confirmation.ext_session_lock().clone();
|
|
|
|
|
confirmation.lock();
|
|
|
|
|
self.lock_state = LockState::Locked(lock);
|
|
|
|
|
} else {
|
|
|
|
|
// There are outputs which we need to redraw before locking.
|
|
|
|
|
self.lock_state = LockState::Locking(confirmation);
|
|
|
|
|
self.queue_redraw_all();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
other => {
|
|
|
|
|
error!("continue_to_locking() called with wrong lock state: {other:?}",);
|
|
|
|
|
self.lock_state = other;
|
|
|
|
|
}
|
2024-11-08 12:28:31 +03:00
|
|
|
}
|
2023-10-13 13:30:11 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn unlock(&mut self) {
|
|
|
|
|
info!("unlocking session");
|
|
|
|
|
|
2025-03-13 18:34:47 +03:00
|
|
|
let prev = mem::take(&mut self.lock_state);
|
|
|
|
|
if let LockState::WaitingForSurfaces { deadline_token, .. } = prev {
|
|
|
|
|
self.event_loop.remove(deadline_token);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-13 13:30:11 +04:00
|
|
|
for output_state in self.output_state.values_mut() {
|
|
|
|
|
output_state.lock_surface = None;
|
|
|
|
|
}
|
|
|
|
|
self.queue_redraw_all();
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-14 07:34:10 -04:00
|
|
|
#[cfg(feature = "dbus")]
|
|
|
|
|
fn update_locked_hint(&mut self) {
|
|
|
|
|
use std::sync::LazyLock;
|
|
|
|
|
|
|
|
|
|
if !self.is_session_instance {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static XDG_SESSION_ID: LazyLock<Option<String>> = LazyLock::new(|| {
|
|
|
|
|
let id = std::env::var("XDG_SESSION_ID").ok();
|
|
|
|
|
if id.is_none() {
|
|
|
|
|
warn!(
|
|
|
|
|
"env var 'XDG_SESSION_ID' is unset or invalid; logind LockedHint won't be set"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
id
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let Some(session_id) = &*XDG_SESSION_ID else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fn call(session_id: &str, locked: bool) -> anyhow::Result<()> {
|
|
|
|
|
let conn = zbus::blocking::Connection::system()
|
|
|
|
|
.context("error connecting to the system bus")?;
|
|
|
|
|
|
|
|
|
|
let message = conn
|
|
|
|
|
.call_method(
|
|
|
|
|
Some("org.freedesktop.login1"),
|
|
|
|
|
"/org/freedesktop/login1",
|
|
|
|
|
Some("org.freedesktop.login1.Manager"),
|
|
|
|
|
"GetSession",
|
|
|
|
|
&(session_id),
|
|
|
|
|
)
|
|
|
|
|
.context("failed to call GetSession")?;
|
|
|
|
|
|
|
|
|
|
let message_body = message.body();
|
|
|
|
|
let session_path: zbus::zvariant::ObjectPath = message_body
|
|
|
|
|
.deserialize()
|
|
|
|
|
.context("failed to deserialize GetSession reply")?;
|
|
|
|
|
|
|
|
|
|
conn.call_method(
|
|
|
|
|
Some("org.freedesktop.login1"),
|
|
|
|
|
session_path,
|
|
|
|
|
Some("org.freedesktop.login1.Session"),
|
|
|
|
|
"SetLockedHint",
|
|
|
|
|
&(locked),
|
|
|
|
|
)
|
|
|
|
|
.context("failed to call SetLockedHint")?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-14 14:39:42 +03:00
|
|
|
// Consider only the fully locked state here. When using the locked hint with sleep
|
|
|
|
|
// inhibitor tools, we want to allow sleep only after the screens are fully cleared with
|
|
|
|
|
// the lock screen, which corresponds to the Locked state.
|
|
|
|
|
let locked = matches!(self.lock_state, LockState::Locked(_));
|
|
|
|
|
|
2025-07-14 07:34:10 -04:00
|
|
|
if self.locked_hint.is_some_and(|h| h == locked) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.locked_hint = Some(locked);
|
|
|
|
|
|
|
|
|
|
let res = thread::Builder::new()
|
|
|
|
|
.name("Logind LockedHint Updater".to_owned())
|
|
|
|
|
.spawn(move || {
|
|
|
|
|
let _span = tracy_client::span!("LockedHint");
|
|
|
|
|
|
|
|
|
|
if let Err(err) = call(session_id, locked) {
|
|
|
|
|
warn!("failed to set logind LockedHint: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if let Err(err) = res {
|
|
|
|
|
warn!("error spawning a thread to set logind LockedHint: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-13 13:30:11 +04:00
|
|
|
pub fn new_lock_surface(&mut self, surface: LockSurface, output: &Output) {
|
2025-06-10 17:02:51 +03:00
|
|
|
let lock = match &self.lock_state {
|
|
|
|
|
LockState::Unlocked => {
|
|
|
|
|
error!("tried to add a lock surface on an unlocked session");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
LockState::WaitingForSurfaces { confirmation, .. } => confirmation.ext_session_lock(),
|
|
|
|
|
LockState::Locking(confirmation) => confirmation.ext_session_lock(),
|
|
|
|
|
LockState::Locked(lock) => lock,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if lock.client() != surface.wl_surface().client() {
|
|
|
|
|
debug!("ignoring lock surface from an unrelated client");
|
2023-10-13 13:30:11 +04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let Some(output_state) = self.output_state.get_mut(output) else {
|
|
|
|
|
error!("missing output state");
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
output_state.lock_surface = Some(surface);
|
|
|
|
|
}
|
2023-12-21 16:19:16 +04:00
|
|
|
|
2024-11-03 09:32:58 +03:00
|
|
|
/// Activates the pointer constraint if necessary according to the current pointer contents.
|
|
|
|
|
///
|
|
|
|
|
/// Make sure the pointer location and contents are up to date before calling this.
|
|
|
|
|
pub fn maybe_activate_pointer_constraint(&self) {
|
|
|
|
|
let Some((surface, surface_loc)) = &self.pointer_contents.surface else {
|
2024-03-18 17:50:31 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2025-07-13 15:49:08 +01:00
|
|
|
|
|
|
|
|
let pointer = self.seat.get_pointer().unwrap();
|
2024-11-03 10:14:24 +03:00
|
|
|
if Some(surface) != pointer.current_focus().as_ref() {
|
2024-05-11 09:26:49 +04:00
|
|
|
return;
|
|
|
|
|
}
|
2024-11-03 09:32:58 +03:00
|
|
|
|
2025-07-13 15:49:08 +01:00
|
|
|
with_pointer_constraint(surface, &pointer, |constraint| {
|
2023-12-21 16:19:16 +04:00
|
|
|
let Some(constraint) = constraint else { return };
|
2024-05-11 09:26:49 +04:00
|
|
|
|
2023-12-21 16:19:16 +04:00
|
|
|
if constraint.is_active() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Constraint does not apply if not within region.
|
|
|
|
|
if let Some(region) = constraint.region() {
|
2025-07-13 15:49:08 +01:00
|
|
|
let pointer_pos = pointer.current_location();
|
2024-11-03 09:32:58 +03:00
|
|
|
let pos_within_surface = pointer_pos - *surface_loc;
|
|
|
|
|
if !region.contains(pos_within_surface.to_i32_round()) {
|
2023-12-21 16:19:16 +04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constraint.activate();
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-01-23 12:02:52 +04:00
|
|
|
|
2024-07-06 18:20:19 +04:00
|
|
|
pub fn focus_layer_surface_if_on_demand(&mut self, surface: Option<LayerSurface>) {
|
|
|
|
|
if let Some(surface) = surface {
|
|
|
|
|
if surface.cached_state().keyboard_interactivity
|
|
|
|
|
== wlr_layer::KeyboardInteractivity::OnDemand
|
|
|
|
|
{
|
|
|
|
|
if self.layer_shell_on_demand_focus.as_ref() != Some(&surface) {
|
|
|
|
|
self.layer_shell_on_demand_focus = Some(surface);
|
|
|
|
|
|
|
|
|
|
// FIXME: granular.
|
|
|
|
|
self.queue_redraw_all();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Something else got clicked, clear on-demand layer-shell focus.
|
|
|
|
|
if self.layer_shell_on_demand_focus.is_some() {
|
|
|
|
|
self.layer_shell_on_demand_focus = None;
|
|
|
|
|
|
|
|
|
|
// FIXME: granular.
|
|
|
|
|
self.queue_redraw_all();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-23 11:50:43 +03:00
|
|
|
/// Tries to find and return the root shell surface for a given surface.
|
|
|
|
|
///
|
|
|
|
|
/// I.e. for popups, this function will try to find the parent toplevel or layer surface. For
|
|
|
|
|
/// regular subsurfaces, it will find the root surface.
|
|
|
|
|
pub fn find_root_shell_surface(&self, surface: &WlSurface) -> WlSurface {
|
|
|
|
|
let Some(root) = self.root_surface.get(surface) else {
|
|
|
|
|
return surface.clone();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if let Some(popup) = self.popups.find_popup(root) {
|
|
|
|
|
return find_popup_root_surface(&popup).unwrap_or_else(|_| root.clone());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
root.clone()
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-23 12:02:52 +04:00
|
|
|
#[cfg(feature = "dbus")]
|
2024-03-27 09:46:18 +04:00
|
|
|
pub fn on_ipc_outputs_changed(&self) {
|
|
|
|
|
let _span = tracy_client::span!("Niri::on_ipc_outputs_changed");
|
2024-01-23 12:02:52 +04:00
|
|
|
|
|
|
|
|
let Some(dbus) = &self.dbus else { return };
|
|
|
|
|
let Some(conn_display_config) = dbus.conn_display_config.clone() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let res = thread::Builder::new()
|
|
|
|
|
.name("DisplayConfig MonitorsChanged Emitter".to_owned())
|
|
|
|
|
.spawn(move || {
|
|
|
|
|
use crate::dbus::mutter_display_config::DisplayConfig;
|
|
|
|
|
let _span = tracy_client::span!("MonitorsChanged");
|
|
|
|
|
let iface = match conn_display_config
|
|
|
|
|
.object_server()
|
|
|
|
|
.interface::<_, DisplayConfig>("/org/gnome/Mutter/DisplayConfig")
|
|
|
|
|
{
|
|
|
|
|
Ok(iface) => iface,
|
|
|
|
|
Err(err) => {
|
|
|
|
|
warn!("error getting DisplayConfig interface: {err:?}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
async_io::block_on(async move {
|
2024-12-10 01:58:26 +02:00
|
|
|
if let Err(err) = DisplayConfig::monitors_changed(iface.signal_emitter()).await
|
2024-01-23 12:02:52 +04:00
|
|
|
{
|
|
|
|
|
warn!("error emitting MonitorsChanged: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if let Err(err) = res {
|
|
|
|
|
warn!("error spawning a thread to send MonitorsChanged: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-03-18 18:17:04 +04:00
|
|
|
|
2024-11-03 08:50:17 +03:00
|
|
|
pub fn handle_focus_follows_mouse(&mut self, new_focus: &PointContents) {
|
2024-07-05 20:12:56 +04:00
|
|
|
let Some(ffm) = self.config.borrow().input.focus_follows_mouse else {
|
2024-03-18 18:17:04 +04:00
|
|
|
return;
|
2024-07-05 20:12:56 +04:00
|
|
|
};
|
2024-03-18 18:17:04 +04:00
|
|
|
|
2024-07-05 10:13:50 +04:00
|
|
|
let pointer = &self.seat.get_pointer().unwrap();
|
|
|
|
|
if pointer.is_grabbed() {
|
2024-03-18 18:17:04 +04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 10:13:50 +04:00
|
|
|
// Recompute the current pointer focus because we don't update it during animations.
|
2024-11-03 08:50:17 +03:00
|
|
|
let current_focus = self.contents_under(pointer.current_location());
|
2024-07-05 10:13:50 +04:00
|
|
|
|
2024-03-18 18:17:04 +04:00
|
|
|
if let Some(output) = &new_focus.output {
|
2024-07-05 10:13:50 +04:00
|
|
|
if current_focus.output.as_ref() != Some(output) {
|
2024-03-18 18:17:04 +04:00
|
|
|
self.layout.focus_output(output);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(window) = &new_focus.window {
|
2025-04-25 09:36:50 +03:00
|
|
|
if !self.layout.is_overview_open() && current_focus.window.as_ref() != Some(window) {
|
2025-02-10 13:12:53 +03:00
|
|
|
let (window, hit) = window;
|
|
|
|
|
|
|
|
|
|
// Don't trigger focus-follows-mouse over the tab indicator.
|
|
|
|
|
if matches!(
|
|
|
|
|
hit,
|
|
|
|
|
HitType::Activate {
|
|
|
|
|
is_tab_indicator: true
|
|
|
|
|
}
|
|
|
|
|
) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-02-10 13:11:50 +03:00
|
|
|
|
2024-09-12 16:48:29 +03:00
|
|
|
if !self.layout.should_trigger_focus_follows_mouse_on(window) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 20:12:56 +04:00
|
|
|
if let Some(threshold) = ffm.max_scroll_amount {
|
|
|
|
|
if self.layout.scroll_amount_to_activate(window) > threshold.0 {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-29 21:11:02 +03:00
|
|
|
self.layout.activate_window_without_raising(window);
|
2024-07-06 18:20:19 +04:00
|
|
|
self.layer_shell_on_demand_focus = None;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(layer) = &new_focus.layer {
|
|
|
|
|
if current_focus.layer.as_ref() != Some(layer) {
|
|
|
|
|
self.layer_shell_on_demand_focus = Some(layer.clone());
|
2024-03-18 18:17:04 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-05-07 22:06:43 +04:00
|
|
|
|
|
|
|
|
pub fn do_screen_transition(&mut self, renderer: &mut GlesRenderer, delay_ms: Option<u16>) {
|
2024-05-08 08:21:04 +04:00
|
|
|
let _span = tracy_client::span!("Niri::do_screen_transition");
|
|
|
|
|
|
2024-08-23 12:48:05 +03:00
|
|
|
self.update_render_elements(None);
|
2024-05-15 20:08:37 +04:00
|
|
|
|
2024-05-07 22:06:43 +04:00
|
|
|
let textures: Vec<_> = self
|
|
|
|
|
.output_state
|
|
|
|
|
.keys()
|
|
|
|
|
.cloned()
|
|
|
|
|
.filter_map(|output| {
|
|
|
|
|
let size = output.current_mode().unwrap().size;
|
|
|
|
|
let transform = output.current_transform();
|
|
|
|
|
|
|
|
|
|
let scale = Scale::from(output.current_scale().fractional_scale());
|
|
|
|
|
let targets = [
|
|
|
|
|
RenderTarget::Output,
|
|
|
|
|
RenderTarget::Screencast,
|
|
|
|
|
RenderTarget::ScreenCapture,
|
|
|
|
|
];
|
|
|
|
|
let textures = targets.map(|target| {
|
|
|
|
|
let elements = self.render::<GlesRenderer>(renderer, &output, false, target);
|
|
|
|
|
let elements = elements.iter().rev();
|
|
|
|
|
|
|
|
|
|
let res = render_to_texture(
|
|
|
|
|
renderer,
|
|
|
|
|
size,
|
|
|
|
|
scale,
|
2024-08-23 12:48:20 +03:00
|
|
|
transform,
|
2024-05-07 22:06:43 +04:00
|
|
|
Fourcc::Abgr8888,
|
|
|
|
|
elements,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if let Err(err) = &res {
|
|
|
|
|
warn!("error rendering output {}: {err:?}", output.name());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if textures.iter().any(|res| res.is_err()) {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let textures = textures.map(|res| {
|
|
|
|
|
let texture = res.unwrap().0;
|
|
|
|
|
TextureBuffer::from_texture(
|
|
|
|
|
renderer,
|
|
|
|
|
texture,
|
2024-08-23 12:48:20 +03:00
|
|
|
scale,
|
|
|
|
|
transform,
|
2024-06-01 12:27:30 +03:00
|
|
|
Vec::new(), // We want windows below to get frame callbacks.
|
2024-05-07 22:06:43 +04:00
|
|
|
)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Some((output, textures))
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
let delay = delay_ms.map_or(screen_transition::DELAY, |d| {
|
|
|
|
|
Duration::from_millis(u64::from(d))
|
|
|
|
|
});
|
2024-11-23 11:27:27 +03:00
|
|
|
|
2024-05-07 22:06:43 +04:00
|
|
|
for (output, from_texture) in textures {
|
|
|
|
|
let state = self.output_state.get_mut(&output).unwrap();
|
2024-11-24 09:41:43 +03:00
|
|
|
state.screen_transition = Some(ScreenTransition::new(
|
|
|
|
|
from_texture,
|
|
|
|
|
delay,
|
|
|
|
|
self.clock.clone(),
|
|
|
|
|
));
|
2024-05-07 22:06:43 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We don't actually need to queue a redraw because the point is to freeze the screen for a
|
|
|
|
|
// bit, and even if the delay was zero, we're drawing the same contents anyway.
|
|
|
|
|
}
|
2024-05-16 11:43:13 +04:00
|
|
|
|
|
|
|
|
pub fn recompute_window_rules(&mut self) {
|
|
|
|
|
let _span = tracy_client::span!("Niri::recompute_window_rules");
|
|
|
|
|
|
|
|
|
|
let changed = {
|
|
|
|
|
let window_rules = &self.config.borrow().window_rules;
|
|
|
|
|
|
|
|
|
|
for unmapped in self.unmapped_windows.values_mut() {
|
|
|
|
|
let new_rules = ResolvedWindowRules::compute(
|
|
|
|
|
window_rules,
|
|
|
|
|
WindowRef::Unmapped(unmapped),
|
|
|
|
|
self.is_at_startup,
|
|
|
|
|
);
|
|
|
|
|
if let InitialConfigureState::Configured { rules, .. } = &mut unmapped.state {
|
|
|
|
|
*rules = new_rules;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut windows = vec![];
|
|
|
|
|
self.layout.with_windows_mut(|mapped, _| {
|
|
|
|
|
if mapped.recompute_window_rules(window_rules, self.is_at_startup) {
|
|
|
|
|
windows.push(mapped.window.clone());
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
let changed = !windows.is_empty();
|
|
|
|
|
for win in windows {
|
|
|
|
|
self.layout.update_window(&win, None);
|
|
|
|
|
}
|
|
|
|
|
changed
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if changed {
|
|
|
|
|
// FIXME: granular.
|
|
|
|
|
self.queue_redraw_all();
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-10-06 20:27:04 +03:00
|
|
|
|
2024-11-14 11:33:08 +03:00
|
|
|
pub fn recompute_layer_rules(&mut self) {
|
|
|
|
|
let _span = tracy_client::span!("Niri::recompute_layer_rules");
|
|
|
|
|
|
|
|
|
|
let mut changed = false;
|
|
|
|
|
{
|
2025-01-21 09:40:00 +03:00
|
|
|
let config = self.config.borrow();
|
|
|
|
|
let rules = &config.layer_rules;
|
2024-11-14 11:33:08 +03:00
|
|
|
|
|
|
|
|
for mapped in self.mapped_layer_surfaces.values_mut() {
|
|
|
|
|
if mapped.recompute_layer_rules(rules, self.is_at_startup) {
|
|
|
|
|
changed = true;
|
2025-01-21 09:40:00 +03:00
|
|
|
mapped.update_config(&config);
|
2024-11-14 11:33:08 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if changed {
|
|
|
|
|
// FIXME: granular.
|
|
|
|
|
self.queue_redraw_all();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-06 20:27:04 +03:00
|
|
|
pub fn reset_pointer_inactivity_timer(&mut self) {
|
2025-02-17 09:03:42 +03:00
|
|
|
if self.pointer_inactivity_timer_got_reset {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-06 20:27:04 +03:00
|
|
|
let _span = tracy_client::span!("Niri::reset_pointer_inactivity_timer");
|
|
|
|
|
|
|
|
|
|
if let Some(token) = self.pointer_inactivity_timer.take() {
|
|
|
|
|
self.event_loop.remove(token);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let Some(timeout_ms) = self.config.borrow().cursor.hide_after_inactive_ms else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let duration = Duration::from_millis(timeout_ms as u64);
|
|
|
|
|
let timer = Timer::from_duration(duration);
|
|
|
|
|
let token = self
|
|
|
|
|
.event_loop
|
|
|
|
|
.insert_source(timer, move |_, _, state| {
|
|
|
|
|
state.niri.pointer_inactivity_timer = None;
|
2025-05-23 07:24:24 +02:00
|
|
|
|
|
|
|
|
// If the pointer is already invisible, don't reset it back to Hidden causing one
|
|
|
|
|
// frame of hover.
|
|
|
|
|
if state.niri.pointer_visibility.is_visible() {
|
|
|
|
|
state.niri.pointer_visibility = PointerVisibility::Hidden;
|
|
|
|
|
state.niri.queue_redraw_all();
|
|
|
|
|
}
|
2024-10-06 20:27:04 +03:00
|
|
|
|
|
|
|
|
TimeoutAction::Drop
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
self.pointer_inactivity_timer = Some(token);
|
2025-02-17 09:03:42 +03:00
|
|
|
|
|
|
|
|
self.pointer_inactivity_timer_got_reset = true;
|
2024-10-06 20:27:04 +03:00
|
|
|
}
|
2025-02-17 09:09:48 +03:00
|
|
|
|
|
|
|
|
pub fn notify_activity(&mut self) {
|
|
|
|
|
if self.notified_activity_this_iteration {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let _span = tracy_client::span!("Niri::notify_activity");
|
|
|
|
|
|
|
|
|
|
self.idle_notifier_state.notify_activity(&self.seat);
|
|
|
|
|
|
|
|
|
|
self.notified_activity_this_iteration = true;
|
|
|
|
|
}
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
|
|
|
|
|
2025-01-27 07:55:32 +03:00
|
|
|
pub struct NewClient {
|
|
|
|
|
pub client: UnixStream,
|
|
|
|
|
pub restricted: bool,
|
|
|
|
|
pub credentials_unknown: bool,
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-09 11:03:38 +04:00
|
|
|
pub struct ClientState {
|
|
|
|
|
pub compositor_state: CompositorClientState,
|
2024-01-15 16:01:01 +04:00
|
|
|
pub can_view_decoration_globals: bool,
|
2025-01-22 00:00:35 -05:00
|
|
|
pub primary_selection_disabled: bool,
|
2024-01-15 16:02:07 +04:00
|
|
|
/// Whether this client is denied from the restricted protocols such as security-context.
|
|
|
|
|
pub restricted: bool,
|
2024-11-12 09:09:33 +03:00
|
|
|
/// We cannot retrieve this client's socket credentials.
|
|
|
|
|
pub credentials_unknown: bool,
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ClientData for ClientState {
|
|
|
|
|
fn initialized(&self, _client_id: ClientId) {}
|
|
|
|
|
fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {}
|
|
|
|
|
}
|
2023-09-08 17:54:02 +04:00
|
|
|
|
2025-04-25 08:53:16 +03:00
|
|
|
fn scale_relocate_crop<E: Element>(
|
|
|
|
|
elem: E,
|
|
|
|
|
output_scale: Scale<f64>,
|
|
|
|
|
zoom: f64,
|
|
|
|
|
ws_geo: Rectangle<f64, Logical>,
|
|
|
|
|
) -> Option<CropRenderElement<RelocateRenderElement<RescaleRenderElement<E>>>> {
|
|
|
|
|
let ws_geo = ws_geo.to_physical_precise_round(output_scale);
|
|
|
|
|
let elem = RescaleRenderElement::from_element(elem, Point::from((0, 0)), zoom);
|
|
|
|
|
let elem = RelocateRenderElement::from_element(elem, ws_geo.loc, Relocate::Relative);
|
|
|
|
|
CropRenderElement::from_element(elem, output_scale, ws_geo)
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-05 13:07:14 +04:00
|
|
|
niri_render_elements! {
|
2024-02-21 09:05:41 +04:00
|
|
|
OutputRenderElements<R> => {
|
2024-02-05 13:07:14 +04:00
|
|
|
Monitor = MonitorRenderElement<R>,
|
2025-04-25 09:36:50 +03:00
|
|
|
RescaledTile = RescaleRenderElement<TileRenderElement<R>>,
|
2024-11-14 11:33:08 +03:00
|
|
|
LayerSurface = LayerSurfaceRenderElement<R>,
|
2025-04-25 08:53:16 +03:00
|
|
|
RelocatedLayerSurface = CropRenderElement<RelocateRenderElement<RescaleRenderElement<
|
|
|
|
|
LayerSurfaceRenderElement<R>
|
|
|
|
|
>>>,
|
2024-02-05 13:07:14 +04:00
|
|
|
Wayland = WaylandSurfaceRenderElement<R>,
|
|
|
|
|
NamedPointer = MemoryRenderBufferRenderElement<R>,
|
|
|
|
|
SolidColor = SolidColorRenderElement,
|
2025-04-25 08:53:16 +03:00
|
|
|
RelocatedSolidColor = CropRenderElement<RelocateRenderElement<RescaleRenderElement<
|
|
|
|
|
SolidColorRenderElement
|
|
|
|
|
>>>,
|
2024-02-05 13:07:14 +04:00
|
|
|
ScreenshotUi = ScreenshotUiRenderElement,
|
2025-08-22 07:58:37 +03:00
|
|
|
ExitConfirmDialog = ExitConfirmDialogRenderElement,
|
2024-05-07 22:06:43 +04:00
|
|
|
Texture = PrimaryGpuTextureRenderElement,
|
2024-02-05 13:07:14 +04:00
|
|
|
// Used for the CPU-rendered panels.
|
|
|
|
|
RelocatedMemoryBuffer = RelocateRenderElement<MemoryRenderBufferRenderElement<R>>,
|
2024-01-18 11:15:48 +04:00
|
|
|
}
|
|
|
|
|
}
|