Files
niri/src/niri.rs
T

2987 lines
109 KiB
Rust
Raw Normal View History

use std::cell::{Cell, RefCell};
2023-10-27 16:50:02 +04:00
use std::collections::{HashMap, HashSet};
use std::ffi::OsString;
use std::path::PathBuf;
2023-09-14 22:28:26 +04:00
use std::rc::Rc;
2023-11-25 10:25:12 +04:00
use std::sync::atomic::Ordering;
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
use _server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as KdeDecorationsMode;
2023-08-27 19:34:37 +04:00
use anyhow::Context;
2024-01-07 09:07:22 +04:00
use niri_config::{Config, TrackLayout};
2023-08-27 19:34:37 +04:00
use smithay::backend::allocator::Fourcc;
2023-10-13 13:30:11 +04:00
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
2023-08-15 16:19:05 +04:00
use smithay::backend::renderer::element::surface::{
render_elements_from_surface_tree, WaylandSurfaceRenderElement,
};
2023-10-01 07:59:28 +04:00
use smithay::backend::renderer::element::texture::TextureRenderElement;
2024-01-03 18:16:20 +04:00
use smithay::backend::renderer::element::utils::select_dmabuf_feedback;
2023-08-27 19:34:37 +04:00
use smithay::backend::renderer::element::{
2024-01-03 11:38:36 +04:00
default_primary_scanout_output_compare, AsRenderElements, Element, Id, Kind, RenderElement,
RenderElementStates, UnderlyingStorage,
};
use smithay::backend::renderer::gles::{
GlesError, GlesFrame, GlesMapping, GlesRenderer, GlesTexture,
2023-08-27 19:34:37 +04:00
};
2023-10-30 20:29:03 +04:00
use smithay::backend::renderer::sync::SyncPoint;
2024-01-03 11:38:36 +04:00
use smithay::backend::renderer::utils::CommitCounter;
use smithay::backend::renderer::{Bind, ExportMem, Frame, Offscreen, Renderer};
2023-08-16 10:59:34 +04:00
use smithay::desktop::utils::{
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
};
use smithay::desktop::{
2024-01-12 17:53:35 +04:00
layer_map_for_output, LayerSurface, PopupGrab, PopupManager, PopupUngrabStrategy, Space,
Window, WindowSurfaceType,
};
2023-12-05 08:04:46 +04:00
use smithay::input::keyboard::{Layout as KeyboardLayout, XkbContextHandler};
use smithay::input::pointer::{CursorIcon, CursorImageAttributes, CursorImageStatus, MotionEvent};
2023-08-09 11:03:38 +04:00
use smithay::input::{Seat, SeatState};
2024-01-16 09:45:47 +04:00
use smithay::output::{self, Output};
2023-08-09 11:03:38 +04:00
use smithay::reexports::calloop::generic::Generic;
use smithay::reexports::calloop::timer::{TimeoutAction, Timer};
use smithay::reexports::calloop::{
2023-10-10 12:11:05 +04:00
self, Idle, Interest, LoopHandle, LoopSignal, Mode, PostAction, RegistrationToken,
};
use smithay::reexports::input;
2023-08-10 17:17:17 +04:00
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::WmCapabilities;
use smithay::reexports::wayland_protocols_misc::server_decoration as _server_decoration;
use smithay::reexports::wayland_server::backend::{
ClientData, ClientId, DisconnectReason, GlobalId,
};
2023-08-09 11:03:38 +04:00
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::reexports::wayland_server::{Display, DisplayHandle};
2023-08-27 19:34:37 +04:00
use smithay::utils::{
2024-01-03 11:38:36 +04:00
Buffer, ClockSource, Logical, Monotonic, Physical, Point, Rectangle, Scale, Size, Transform,
2023-10-24 15:05:14 +04:00
SERIAL_COUNTER,
2023-08-27 19:34:37 +04:00
};
use smithay::wayland::compositor::{
2023-10-26 00:15:46 +04:00
send_surface_state, with_states, with_surface_tree_downward, CompositorClientState,
CompositorState, SurfaceData, TraversalAction,
};
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;
2023-09-30 23:16:20 +04:00
use smithay::wayland::input_method::InputMethodManagerState;
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;
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};
2023-11-08 22:16:16 +04:00
use smithay::wayland::selection::primary_selection::PrimarySelectionState;
2023-09-30 21:05:13 +04:00
use smithay::wayland::selection::wlr_data_control::DataControlState;
2023-10-13 13:30:11 +04:00
use smithay::wayland::session_lock::{LockSurface, SessionLockManagerState, SessionLocker};
use smithay::wayland::shell::kde::decoration::KdeDecorationState;
2023-08-15 12:49:26 +04:00
use smithay::wayland::shell::wlr_layer::{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;
use smithay::wayland::socket::ListeningSocketSource;
use smithay::wayland::tablet_manager::{TabletManagerState, TabletSeatTrait};
2023-09-30 23:16:20 +04:00
use smithay::wayland::text_input::TextInputManagerState;
use smithay::wayland::virtual_keyboard::VirtualKeyboardManagerState;
2023-08-09 11:03:38 +04:00
2023-11-25 10:25:12 +04:00
use crate::animation;
2024-01-03 18:16:20 +04:00
use crate::backend::tty::{SurfaceDmabufFeedback, TtyFrame, TtyRenderer, TtyRendererError};
2023-10-13 13:30:11 +04:00
use crate::backend::{Backend, 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")]
2023-10-10 09:55:44 +04:00
use crate::dbus::gnome_shell_screenshot::{NiriToScreenshot, ScreenshotToNiri};
#[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;
2023-10-13 13:30:11 +04:00
use crate::handlers::configure_lock_surface;
use crate::input::TabletData;
2023-12-24 14:30:19 +04:00
use crate::layout::{Layout, MonitorRenderElement};
2023-09-08 17:54:02 +04:00
use crate::pw_utils::{Cast, PipeWire};
2024-01-03 11:38:36 +04:00
use crate::render_helpers::{NiriRenderer, PrimaryGpuTextureRenderElement};
2023-10-30 20:29:03 +04:00
use crate::screenshot_ui::{ScreenshotUi, ScreenshotUiRenderElement};
2023-12-24 14:30:19 +04:00
use crate::utils::{
center, get_monotonic_time, make_screenshot_path, output_size, write_png_rgba8,
};
2023-08-09 11:03:38 +04:00
2023-10-27 08:34:00 +04:00
const CLEAR_COLOR: [f32; 4] = [0.2, 0.2, 0.2, 1.];
const CLEAR_COLOR_LOCKED: [f32; 4] = [0.3, 0.1, 0.1, 1.];
2023-10-13 13:18:50 +04:00
2023-08-09 11:03:38 +04:00
pub struct Niri {
2023-09-14 22:28:26 +04:00
pub config: Rc<RefCell<Config>>,
2023-09-24 17:30:06 +04:00
pub event_loop: LoopHandle<'static, State>,
2023-08-09 11:03:38 +04:00
pub stop_signal: LoopSignal,
pub display_handle: DisplayHandle,
pub socket_name: OsString,
2023-08-09 11:03:38 +04:00
2023-10-29 14:04:38 +04:00
pub start_time: Instant,
// Each workspace corresponds to a Space. Each workspace generally has one Output mapped to it,
// however it may have none (when there are no outputs connected) or mutiple (when mirroring).
pub layout: Layout<Window>,
// 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>,
// Windows which don't have a buffer attached yet.
pub unmapped_windows: HashMap<WlSurface, Window>,
pub output_state: HashMap<Output, OutputState>,
2023-10-03 17:02:07 +04:00
pub output_by_name: HashMap<String, Output>,
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,
pub tablets: HashMap<input::Device, TabletData>,
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,
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,
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,
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,
pub virtual_keyboard_state: VirtualKeyboardManagerState,
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,
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,
2023-09-30 21:05:13 +04:00
pub data_control_state: DataControlState,
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,
pub security_context_state: SecurityContextState,
2023-08-09 11:03:38 +04:00
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.
pub suppressed_keys: HashSet<u32>,
2024-01-12 17:14:18 +04:00
// This is always a toplevel surface 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
// this toplevel surface).
pub keyboard_focus: Option<WlSurface>,
2023-08-10 09:58:26 +04:00
2023-10-29 14:04:38 +04:00
pub cursor_manager: CursorManager,
pub cursor_texture_cache: CursorTextureCache,
pub cursor_shape_manager_state: CursorShapeManagerState,
2023-08-15 16:40:54 +04:00
pub dnd_icon: Option<WlSurface>,
2023-10-15 16:30:52 +04:00
pub pointer_focus: Option<PointerFocus>,
pub tablet_cursor_location: Option<Point<f64, Logical>>,
2023-08-27 10:29:06 +04:00
2023-10-13 13:30:11 +04:00
pub lock_state: LockState,
2023-10-30 20:29:03 +04:00
pub screenshot_ui: ScreenshotUi,
2023-10-10 10:25:26 +04:00
#[cfg(feature = "dbus")]
pub dbus: Option<crate::dbus::DBusServers>,
#[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
// Casts are dropped before PipeWire to prevent a double-free (yay).
pub casts: Vec<Cast>,
pub pipewire: Option<PipeWire>,
}
2023-08-10 14:27:09 +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,
// After the last redraw, some ongoing animations still remain.
pub unfinished_animations_remain: bool,
/// Estimated sequence currently displayed on this output.
///
/// When a frame is presented on this output, this becomes the real sequence from the VBlank
/// callback. Then, as long as there are no KMS submissions, but we keep getting commits, this
/// sequence increases by 1 at estimated VBlank times.
///
/// If there are no commits, then we won't have a timer running, so the estimated sequence will
/// not increase.
pub current_estimated_sequence: Option<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,
2023-10-13 13:30:11 +04:00
pub lock_render_state: LockRenderState,
pub lock_surface: Option<LockSurface>,
pub lock_color_buffer: SolidColorBuffer,
}
2023-09-30 17:13:56 +04:00
#[derive(Default)]
pub enum RedrawState {
/// The compositor is idle.
#[default]
Idle,
/// A redraw is queued.
Queued(Idle<'static>),
/// 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.
WaitingForEstimatedVBlankAndQueued((RegistrationToken, Idle<'static>)),
}
2024-01-12 17:53:35 +04:00
pub struct PopupGrabState {
pub root: WlSurface,
pub grab: PopupGrab<State>,
}
2023-10-15 16:30:52 +04:00
#[derive(Clone, PartialEq, Eq)]
pub struct PointerFocus {
pub output: Output,
pub surface: (WlSurface, Point<i32, Logical>),
}
2023-10-13 13:30:11 +04:00
#[derive(Default)]
pub enum LockState {
#[default]
Unlocked,
Locking(SessionLocker),
Locked,
}
#[derive(PartialEq, Eq)]
pub enum LockRenderState {
/// The output displays a normal session frame.
Unlocked,
/// The output displays a locked frame.
Locked,
}
// 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)>>,
}
impl Default for SurfaceFrameThrottlingState {
fn default() -> Self {
Self {
last_sent_at: RefCell::new(None),
}
}
2023-08-09 11:03:38 +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>,
2023-09-03 14:10:02 +04:00
) -> Self {
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));
2023-09-03 13:25:43 +04:00
let has_display =
env::var_os("WAYLAND_DISPLAY").is_some() || env::var_os("DISPLAY").is_some();
let mut backend = if has_display {
2023-09-14 22:28:26 +04:00
Backend::Winit(Winit::new(config.clone(), event_loop.clone()))
2023-09-03 13:25:43 +04:00
} else {
2023-09-14 22:28:26 +04:00
Backend::Tty(Tty::new(config.clone(), event_loop.clone()))
2023-09-03 13:25:43 +04:00
};
2023-09-14 22:28:26 +04:00
let mut niri = Niri::new(config.clone(), event_loop, stop_signal, display, &backend);
2023-09-03 13:25:43 +04:00
backend.init(&mut niri);
2023-09-14 22:28:26 +04:00
Self { backend, niri }
2023-09-03 14:10:02 +04:00
}
2023-10-10 07:55:31 +04:00
pub fn refresh_and_flush_clients(&mut self) {
let _span = tracy_client::span!("refresh_and_flush_clients");
// These should be called periodically, before flushing the clients.
self.niri.layout.refresh();
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();
self.niri.popups.cleanup();
2024-01-12 17:53:35 +04:00
self.niri.refresh_popup_grab();
self.update_keyboard_focus();
2023-10-21 20:47:41 +04:00
self.refresh_pointer_focus();
2023-10-10 07:55:31 +04:00
{
let _span = tracy_client::span!("flush_clients");
self.niri.display_handle.flush_clients().unwrap();
}
}
2023-09-03 14:10:02 +04:00
pub fn move_cursor(&mut self, location: Point<f64, Logical>) {
let under = self.niri.surface_under_and_global_space(location);
2023-12-21 16:19:16 +04:00
self.niri
.maybe_activate_pointer_constraint(location, &under);
2023-10-15 16:30:52 +04:00
self.niri.pointer_focus = under.clone();
let under = under.map(|u| u.surface);
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,
under,
&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);
2023-09-03 14:10:02 +04:00
// FIXME: granular
self.niri.queue_redraw_all();
}
2023-10-21 20:47:41 +04:00
pub fn refresh_pointer_focus(&mut self) {
let _span = tracy_client::span!("Niri::refresh_pointer_focus");
2023-10-15 16:30:52 +04:00
let pointer = &self.niri.seat.get_pointer().unwrap();
let location = pointer.current_location();
2023-10-30 20:29:03 +04:00
if !self.niri.is_locked() && !self.niri.screenshot_ui.is_open() {
// 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
}
}
2023-10-21 20:48:00 +04:00
if !self.update_pointer_focus() {
return;
}
pointer.frame(self);
// FIXME: granular
self.niri.queue_redraw_all();
}
pub fn update_pointer_focus(&mut self) -> bool {
let _span = tracy_client::span!("Niri::update_pointer_focus");
let pointer = &self.niri.seat.get_pointer().unwrap();
let location = pointer.current_location();
2023-10-15 16:30:52 +04:00
let under = self.niri.surface_under_and_global_space(location);
// We're not changing the global cursor location here, so if the focus did not change, then
// nothing changed.
if self.niri.pointer_focus == under {
2023-10-21 20:48:00 +04:00
return false;
2023-10-15 16:30:52 +04:00
}
2023-12-21 16:19:16 +04:00
self.niri
.maybe_activate_pointer_constraint(location, &under);
2023-10-15 16:30:52 +04:00
self.niri.pointer_focus = under.clone();
let under = under.map(|u| u.surface);
pointer.motion(
self,
under,
&MotionEvent {
location,
serial: SERIAL_COUNTER.next_serial(),
time: get_monotonic_time().as_millis() as u32,
},
);
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());
}
pub fn update_keyboard_focus(&mut self) {
2023-10-13 13:30:11 +04:00
let focus = if self.niri.is_locked() {
self.niri.lock_surface_focus()
2023-10-30 20:29:03 +04:00
} else if self.niri.screenshot_ui.is_open() {
None
} 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)
.map(|l| (&g.root, l.layer()))
});
let grab_on_layer = |layer: Layer| {
layer_grab.and_then(move |(s, l)| if l == layer { Some(s.clone()) } else { None })
};
let layout_focus = || {
2023-10-13 13:30:11 +04:00
self.niri
.layout
.focus()
.map(|win| win.toplevel().wl_surface().clone())
};
let layer_focus = |surface: &LayerSurface| {
surface
.can_receive_keyboard_focus()
.then(|| surface.wl_surface().clone())
};
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));
surface = surface.or_else(|| layers.layers_on(Layer::Overlay).find_map(layer_focus));
if mon.render_above_top_layer() {
surface = surface.or_else(layout_focus);
surface = surface.or_else(|| layers.layers_on(Layer::Top).find_map(layer_focus));
} else {
surface = surface.or_else(|| layers.layers_on(Layer::Top).find_map(layer_focus));
surface = surface.or_else(layout_focus);
}
surface
} else {
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
);
if let Some(grab) = self.niri.popup_grab.as_mut() {
if Some(&grab.root) != focus.as_ref() {
trace!(
"grab root {:?} is not the new focus {:?}, ungrabbing",
grab.root,
focus
);
grab.grab.ungrab(PopupUngrabStrategy::All);
keyboard.unset_grab();
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;
}
}
if self.niri.config.borrow().input.keyboard.track_layout == TrackLayout::Window {
let current_layout =
2023-11-24 11:49:07 +04:00
keyboard.with_xkb_state(self, |context| context.active_layout());
let mut new_layout = current_layout;
// Store the currently active layout for the surface.
2024-01-12 17:14:18 +04:00
if let Some(current_focus) = self.niri.keyboard_focus.as_ref() {
with_states(current_focus, |data| {
let cell = data
.data_map
.get_or_insert::<Cell<KeyboardLayout>, _>(Cell::default);
cell.set(current_layout);
});
}
if let Some(focus) = focus.as_ref() {
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()
});
}
if new_layout != current_layout && focus.is_some() {
keyboard.set_focus(self, None, SERIAL_COUNTER.next_serial());
2023-11-24 11:49:07 +04:00
keyboard.with_xkb_state(self, |mut context| {
context.set_layout(new_layout);
});
}
}
2024-01-12 17:14:18 +04:00
self.niri.keyboard_focus = focus.clone();
2023-09-03 14:10:02 +04:00
keyboard.set_focus(self, focus, SERIAL_COUNTER.next_serial());
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
pub fn reload_config(&mut self, path: PathBuf) {
let _span = tracy_client::span!("State::reload_config");
let config = match Config::load(Some(path)) {
Ok((config, _)) => config,
Err(err) => {
warn!("{:?}", err.context("error loading config"));
return;
}
};
self.niri.layout.update_config(&config);
2023-11-25 10:25:12 +04:00
animation::ANIMATION_SLOWDOWN.store(config.debug.animation_slowdown, Ordering::Relaxed);
2023-12-08 07:58:03 +04:00
let mut reload_xkb = None;
2024-01-16 08:44:04 +04:00
let mut output_config_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 08:44:04 +04:00
if config.outputs != old_config.outputs {
output_config_changed = true;
}
2023-10-01 17:42:56 +04:00
*old_config = config;
// 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.
if let Some(xkb) = reload_xkb {
let keyboard = self.niri.seat.get_keyboard().unwrap();
if let Err(err) = keyboard.set_xkb_config(self, xkb.to_xkb_config()) {
warn!("error updating xkb config: {err:?}");
}
}
2024-01-16 08:44:04 +04:00
if output_config_changed {
2024-01-16 09:45:47 +04:00
let mut resized_outputs = vec![];
for output in self.niri.global_space.outputs() {
let name = output.name();
let scale = self
.niri
.config
.borrow()
.outputs
.iter()
.find(|o| o.name == name)
.map(|c| c.scale)
.unwrap_or(1.);
let scale = scale.clamp(1., 10.).ceil() as i32;
if output.current_scale().integer_scale() != scale {
output.change_current_state(
None,
None,
Some(output::Scale::Integer(scale)),
None,
);
resized_outputs.push(output.clone());
}
}
for output in resized_outputs {
self.niri.output_resized(output);
}
2024-01-16 08:44:04 +04:00
self.niri.reposition_outputs(None);
}
2023-09-26 19:24:50 +04:00
self.niri.queue_redraw_all();
// FIXME: apply libinput device settings.
// FIXME: apply xdg decoration settings.
}
#[cfg(feature = "xdp-gnome-screencast")]
2023-10-10 09:55:44 +04:00
pub fn on_screen_cast_msg(
&mut self,
2023-10-10 12:11:05 +04:00
to_niri: &calloop::channel::Sender<ScreenCastToNiri>,
2023-10-10 08:55:54 +04:00
msg: ScreenCastToNiri,
) {
match msg {
2023-10-10 08:55:54 +04:00
ScreenCastToNiri::StartCast {
session_id,
output,
cursor_mode,
signal_ctx,
} => {
let _span = tracy_client::span!("StartCast");
debug!(session_id, "StartCast");
let gbm = match self.backend.gbm_device() {
Some(gbm) => gbm,
None => {
debug!("no GBM device available");
return;
}
};
let pw = self.niri.pipewire.as_ref().unwrap();
match pw.start_cast(
to_niri.clone(),
gbm,
session_id,
output,
cursor_mode,
signal_ctx,
) {
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: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,
) {
let ScreenshotToNiri::TakeScreenshot { include_cursor } = msg;
let _span = tracy_client::span!("TakeScreenshot");
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
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) {
warn!("error sending None to screenshot: {err:?}");
2023-10-10 08:54:15 +04:00
}
}
});
2023-10-10 08:54:15 +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:?}");
}
}
}
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,
2023-08-09 11:03:38 +04:00
) -> Self {
2023-10-09 17:36:58 +04:00
let _span = tracy_client::span!("Niri::new");
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();
2023-08-09 11:03:38 +04:00
let layout = Layout::new(&config_);
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,
[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, _>(
&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
},
);
let layer_shell_state =
WlrLayerShellState::new_with_filter::<State, _>(&display_handle, |client| {
!client.get_data::<ClientState>().unwrap().restricted
});
2023-10-13 13:30:11 +04:00
let session_lock_state =
SessionLockManagerState::new::<State, _>(&display_handle, |client| {
!client.get_data::<ClientState>().unwrap().restricted
});
2023-09-03 14:10:02 +04:00
let shm_state = ShmState::new::<State>(&display_handle, vec![]);
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();
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);
2023-09-03 14:10:02 +04:00
let data_device_state = DataDeviceState::new::<State>(&display_handle);
2023-09-26 22:22:11 +04:00
let primary_selection_state = PrimarySelectionState::new::<State>(&display_handle);
2023-09-30 21:05:13 +04:00
let data_control_state = DataControlState::new::<State, _>(
&display_handle,
Some(&primary_selection_state),
|client| !client.get_data::<ClientState>().unwrap().restricted,
2023-09-30 21:05:13 +04:00
);
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);
let security_context_state =
SecurityContextState::new::<State, _>(&display_handle, |client| {
!client.get_data::<ClientState>().unwrap().restricted
});
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 =
InputMethodManagerState::new::<State, _>(&display_handle, |client| {
!client.get_data::<ClientState>().unwrap().restricted
});
2023-09-30 23:16:20 +04:00
let virtual_keyboard_state =
VirtualKeyboardManagerState::new::<State, _>(&display_handle, |client| {
!client.get_data::<ClientState>().unwrap().restricted
});
2023-09-30 23:16:20 +04:00
2023-09-08 17:54:02 +04:00
let mut seat: Seat<State> = seat_state.new_wl_seat(&display_handle, backend.seat_name());
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(),
)
.unwrap();
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
let (tx, rx) = calloop::channel::channel();
event_loop
.insert_source(rx, move |event, _, state| {
if let calloop::channel::Event::Msg(image) = event {
state.niri.cursor_manager.set_cursor_image(image);
// FIXME: granular.
state.niri.queue_redraw_all();
}
})
.unwrap();
seat.tablet_seat()
.on_cursor_surface(move |_tool, new_image| {
if let Err(err) = tx.send(new_image) {
warn!("error sending new tablet cursor image: {err:?}");
};
});
2023-10-30 20:29:03 +04:00
let screenshot_ui = ScreenshotUi::new();
2023-08-09 11:03:38 +04:00
let socket_source = ListeningSocketSource::new_auto().unwrap();
let socket_name = socket_source.socket_name().to_os_string();
event_loop
2023-09-24 17:30:06 +04:00
.insert_source(socket_source, move |client, _, state| {
2024-01-15 16:01:01 +04:00
let config = state.niri.config.borrow();
let data = Arc::new(ClientState {
compositor_state: Default::default(),
can_view_decoration_globals: config.prefer_no_csd,
restricted: false,
2024-01-15 16:01:01 +04:00
});
2023-10-10 07:59:50 +04:00
if let Err(err) = state.niri.display_handle.insert_client(client, data) {
2023-08-09 11:03:38 +04:00
error!("error inserting client: {err}");
}
})
.unwrap();
2023-09-08 17:54:02 +04:00
let pipewire = match PipeWire::new(&event_loop) {
Ok(pipewire) => Some(pipewire),
Err(err) => {
warn!("error starting PipeWire: {err:?}");
None
}
};
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();
drop(config_);
Self {
config,
event_loop,
stop_signal,
socket_name,
2023-10-29 14:04:38 +04:00
display_handle,
start_time: Instant::now(),
layout,
global_space: Space::default(),
output_state: HashMap::new(),
output_by_name: HashMap::new(),
unmapped_windows: HashMap::new(),
monitors_active: true,
tablets: HashMap::new(),
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,
text_input_state,
input_method_state,
virtual_keyboard_state,
shm_state,
output_manager_state,
2023-12-31 12:02:39 +04:00
dmabuf_state,
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,
data_device_state,
primary_selection_state,
data_control_state,
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(),
presentation_state,
security_context_state,
seat,
2024-01-12 17:14:18 +04:00
keyboard_focus: None,
2023-10-29 14:04:38 +04:00
cursor_manager,
cursor_texture_cache: Default::default(),
cursor_shape_manager_state,
dnd_icon: None,
2023-10-15 16:30:52 +04:00
pointer_focus: None,
tablet_cursor_location: None,
2023-10-13 13:30:11 +04:00
lock_state: LockState::Unlocked,
2023-10-30 20:29:03 +04:00
screenshot_ui,
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")]
inhibit_power_key_fd: None,
2023-10-10 10:25:26 +04:00
pipewire,
casts: vec![],
}
}
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<()> {
let conn = zbus::blocking::ConnectionBuilder::system()?.build()?;
// logind-zbus has a wrong signature for this method, so do it manually.
// https://gitlab.com/flukejones/logind-zbus/-/merge_requests/5
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"),
)?;
let fd = message.body()?;
self.inhibit_power_key_fd = Some(fd);
Ok(())
}
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,
name: String,
position: Option<Point<i32, Logical>>,
config: Option<niri_config::Position>,
}
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) {
let name = output.name();
let position = self.global_space.output_geometry(output).map(|geo| geo.loc);
let config = config
.outputs
.iter()
.find(|o| o.name == name)
.and_then(|c| c.position);
outputs.push(Data {
output: output.clone(),
name,
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.
//
// All outputs must have different (connector) names.
outputs.sort_unstable_by(|a, b| Ord::cmp(&a.name, &b.name));
// 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: {:?}",
outputs.iter().map(|d| &d.name)
2023-09-30 11:33:02 +04:00
);
2024-01-16 08:43:28 +04:00
for data in outputs.into_iter() {
let Data {
output,
name,
position,
config,
} = data;
let size = output_size(&output);
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.
let target_geom = Rectangle::from_loc_and_size(*pos, size);
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!(
"output {name} at x={} y={} sized {}x{} \
overlaps an existing output at x={} y={} sized {}x{}, \
falling back to automatic placement",
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!(
"putting output {name} at x={} y={}",
new_position.x, new_position.y
);
output.change_current_state(None, None, None, Some(new_position));
self.queue_redraw(output);
}
}
}
pub fn add_output(&mut self, output: Output, refresh_interval: Option<Duration>) {
let global = output.create_global::<State>(&self.display_handle);
2024-01-16 09:42:31 +04:00
let name = output.name();
let scale = self
.config
.borrow()
.outputs
.iter()
.find(|o| o.name == name)
.map(|c| c.scale)
.unwrap_or(1.);
let scale = scale.clamp(1., 10.).ceil() as i32;
// Set scale before adding to the layout since that will read the output size.
output.change_current_state(None, None, Some(output::Scale::Integer(scale)), None);
self.layout.add_output(output.clone());
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
};
2024-01-16 08:43:28 +04:00
let size = output_size(&output);
let state = OutputState {
global,
2023-09-30 17:13:56 +04:00
redraw_state: RedrawState::Idle,
unfinished_animations_remain: false,
2023-08-14 15:53:24 +04:00
frame_clock: FrameClock::new(refresh_interval),
current_estimated_sequence: None,
2023-10-27 08:34:00 +04:00
background_buffer: SolidColorBuffer::new(size, CLEAR_COLOR),
2023-10-13 13:30:11 +04:00
lock_render_state,
lock_surface: None,
lock_color_buffer: SolidColorBuffer::new(size, CLEAR_COLOR_LOCKED),
};
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 09:42:31 +04:00
let rv = self.output_by_name.insert(name, output.clone());
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));
}
pub fn remove_output(&mut self, output: &Output) {
self.layout.remove_output(output);
self.global_space.unmap_output(output);
2024-01-16 08:43:28 +04:00
self.reposition_outputs(None);
2023-09-30 17:13:56 +04:00
let state = self.output_state.remove(output).unwrap();
2023-10-03 17:02:07 +04:00
self.output_by_name.remove(&output.name()).unwrap();
2023-09-30 17:13:56 +04:00
match state.redraw_state {
RedrawState::Idle => (),
RedrawState::Queued(idle) => idle.cancel(),
RedrawState::WaitingForVBlank { .. } => (),
RedrawState::WaitingForEstimatedVBlank(token) => self.event_loop.remove(token),
RedrawState::WaitingForEstimatedVBlankAndQueued((token, idle)) => {
self.event_loop.remove(token);
idle.cancel();
}
2023-08-09 11:03:38 +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
.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 {
confirmation.lock();
self.lock_state = LockState::Locked;
} else {
// Still waiting.
self.lock_state = LockState::Locking(confirmation);
}
}
lock_state => self.lock_state = lock_state,
}
2023-10-30 20:29:03 +04:00
if self.screenshot_ui.close() {
self.cursor_manager
.set_cursor_image(CursorImageStatus::default_named());
2023-10-30 20:29:03 +04:00
self.queue_redraw_all();
}
}
pub fn output_resized(&mut self, output: Output) {
2023-10-27 08:34:00 +04:00
let output_size = output_size(&output);
let is_locked = self.is_locked();
2023-08-15 12:49:26 +04:00
layer_map_for_output(&output).arrange();
self.layout.update_output_size(&output);
2023-10-13 13:30:11 +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);
state.lock_color_buffer.resize(output_size);
2023-10-13 13:30:11 +04:00
if is_locked {
if let Some(lock_surface) = &state.lock_surface {
configure_lock_surface(lock_surface, &output);
}
}
}
2023-10-30 20:29:03 +04:00
// If the output size changed with an open screenshot UI, close the screenshot UI.
2024-01-16 09:45:47 +04:00
if let Some((old_size, old_scale)) = self.screenshot_ui.output_size(&output) {
2023-10-30 20:29:03 +04:00
let output_transform = output.current_transform();
let output_mode = output.current_mode().unwrap();
let size = output_transform.transform_size(output_mode.size);
2024-01-16 09:45:47 +04:00
let scale = output.current_scale().integer_scale();
// FIXME: scale changes 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 {
2023-10-30 20:29:03 +04:00
self.screenshot_ui.close();
self.cursor_manager
.set_cursor_image(CursorImageStatus::default_named());
2023-10-30 20:29:03 +04:00
self.queue_redraw_all();
return;
}
}
self.queue_redraw(output);
}
2023-10-09 18:37:43 +04:00
pub fn deactivate_monitors(&mut self, backend: &Backend) {
if !self.monitors_active {
return;
}
self.monitors_active = false;
backend.set_monitors_active(false);
}
pub fn activate_monitors(&mut self, backend: &Backend) {
if self.monitors_active {
return;
}
self.monitors_active = true;
backend.set_monitors_active(true);
self.queue_redraw_all();
}
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))
}
/// 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<&Window> {
2023-10-30 20:29:03 +04:00
if self.is_locked() || self.screenshot_ui.is_open() {
2023-10-13 13:30:11 +04:00
return None;
}
let (output, pos_within_output) = self.output_under(pos)?;
// Check if some layer-shell surface is on top.
let layers = layer_map_for_output(output);
let layer_under = |layer| layers.layer_under(layer, pos_within_output).is_some();
if layer_under(Layer::Overlay) {
return None;
}
let mon = self.layout.monitor_for_output(output).unwrap();
if !mon.render_above_top_layer() && layer_under(Layer::Top) {
return None;
}
let (window, _loc) = self.layout.window_under(output, pos_within_output)?;
Some(window)
2023-08-09 11:03:38 +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.
pub fn window_under_cursor(&self) -> Option<&Window> {
let pos = self.seat.get_pointer().unwrap().current_location();
self.window_under(pos)
}
/// Returns the surface under cursor and its position in the global space.
///
/// Pointer needs location in global space, and focused window location compatible with that
/// global space. We don't have a global space for all windows, but this function converts the
/// window location temporarily to the current global space.
pub fn surface_under_and_global_space(
&mut self,
pos: Point<f64, Logical>,
2023-10-15 16:30:52 +04:00
) -> Option<PointerFocus> {
let (output, pos_within_output) = self.output_under(pos)?;
2023-10-15 16:30:52 +04:00
2023-10-13 13:30:11 +04:00
if self.is_locked() {
2023-11-01 10:13:16 +01:00
let state = self.output_state.get(output)?;
2023-10-13 13:30:11 +04:00
let surface = state.lock_surface.as_ref()?;
// We put lock surfaces at (0, 0).
let point = pos_within_output;
let (surface, point) = under_from_surface_tree(
surface.wl_surface(),
point,
(0, 0),
WindowSurfaceType::ALL,
)?;
return Some(PointerFocus {
2023-11-01 10:13:16 +01:00
output: output.clone(),
2023-10-13 13:30:11 +04:00
surface: (surface, point),
});
}
2023-10-30 20:29:03 +04:00
if self.screenshot_ui.is_open() {
return None;
}
2023-11-01 10:13:16 +01:00
let layers = layer_map_for_output(output);
2023-11-02 13:01:34 +04:00
let layer_surface_under = |layer| {
2023-11-01 10:13:16 +01:00
layers
2023-11-02 13:01:34 +04:00
.layer_under(layer, pos_within_output)
2023-11-01 10:13:16 +01:00
.and_then(|layer| {
let layer_pos_within_output = layers.layer_geometry(layer).unwrap().loc;
layer
.surface_under(
pos_within_output - layer_pos_within_output.to_f64(),
WindowSurfaceType::ALL,
)
.map(|(surface, pos_within_layer)| {
(surface, pos_within_layer + layer_pos_within_output)
})
})
};
2023-11-02 13:01:34 +04:00
let window_under = || {
self.layout
.window_under(output, pos_within_output)
.and_then(|(window, win_pos_within_output)| {
let win_pos_within_output = win_pos_within_output?;
2023-11-02 13:01:34 +04:00
window
.surface_under(
pos_within_output - win_pos_within_output.to_f64(),
WindowSurfaceType::ALL,
)
.map(|(s, pos_within_window)| {
(s, pos_within_window + win_pos_within_output)
})
})
};
let mon = self.layout.monitor_for_output(output).unwrap();
let mut under = layer_surface_under(Layer::Overlay);
if mon.render_above_top_layer() {
under = under
.or_else(window_under)
.or_else(|| layer_surface_under(Layer::Top));
} else {
under = under
.or_else(|| layer_surface_under(Layer::Top))
.or_else(window_under);
}
let (surface, surface_pos_within_output) = under
2023-11-02 13:01:34 +04:00
.or_else(|| layer_surface_under(Layer::Bottom))
.or_else(|| layer_surface_under(Layer::Background))?;
2023-11-01 10:13:16 +01:00
let output_pos_in_global_space = self.global_space.output_geometry(output).unwrap().loc;
let surface_loc_in_global_space = surface_pos_within_output + output_pos_in_global_space;
2023-10-15 16:30:52 +04:00
Some(PointerFocus {
2023-11-01 10:13:16 +01:00
output: output.clone(),
2023-10-15 16:30:52 +04:00
surface: (surface, surface_loc_in_global_space),
})
}
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()
}
2023-08-16 08:03:20 +04:00
pub fn output_left(&self) -> Option<Output> {
let active = self.layout.active_output()?;
2023-08-16 08:03:20 +04:00
let active_geo = self.global_space.output_geometry(active).unwrap();
let extended_geo = Rectangle::from_loc_and_size(
(i32::MIN / 2, active_geo.loc.y),
(i32::MAX, active_geo.size.h),
);
self.global_space
.outputs()
.map(|output| (output, self.global_space.output_geometry(output).unwrap()))
.filter(|(_, geo)| center(*geo).x < center(active_geo).x && geo.overlaps(extended_geo))
.min_by_key(|(_, geo)| center(active_geo).x - center(*geo).x)
.map(|(output, _)| output)
.cloned()
}
pub fn output_right(&self) -> Option<Output> {
let active = self.layout.active_output()?;
2023-08-16 08:03:20 +04:00
let active_geo = self.global_space.output_geometry(active).unwrap();
let extended_geo = Rectangle::from_loc_and_size(
(i32::MIN / 2, active_geo.loc.y),
(i32::MAX, active_geo.size.h),
);
self.global_space
.outputs()
.map(|output| (output, self.global_space.output_geometry(output).unwrap()))
.filter(|(_, geo)| center(*geo).x > center(active_geo).x && geo.overlaps(extended_geo))
.min_by_key(|(_, geo)| center(*geo).x - center(active_geo).x)
.map(|(output, _)| output)
.cloned()
}
pub fn output_up(&self) -> Option<Output> {
let active = self.layout.active_output()?;
2023-08-16 08:03:20 +04:00
let active_geo = self.global_space.output_geometry(active).unwrap();
let extended_geo = Rectangle::from_loc_and_size(
(active_geo.loc.x, i32::MIN / 2),
(active_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(active_geo).y && geo.overlaps(extended_geo))
.min_by_key(|(_, geo)| center(active_geo).y - center(*geo).y)
.map(|(output, _)| output)
.cloned()
}
pub fn output_down(&self) -> Option<Output> {
let active = self.layout.active_output()?;
2023-08-16 08:03:20 +04:00
let active_geo = self.global_space.output_geometry(active).unwrap();
let extended_geo = Rectangle::from_loc_and_size(
(active_geo.loc.x, i32::MIN / 2),
(active_geo.size.w, i32::MAX),
);
self.global_space
.outputs()
.map(|output| (output, self.global_space.output_geometry(output).unwrap()))
.filter(|(_, geo)| center(active_geo).y < center(*geo).y && geo.overlaps(extended_geo))
.min_by_key(|(_, geo)| center(*geo).y - center(active_geo).y)
.map(|(output, _)| output)
.cloned()
}
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();
map_to_output
.and_then(|name| self.output_by_name.get(name))
.or_else(|| self.global_space.outputs().next())
}
2023-12-24 17:38:13 +04:00
pub fn output_for_root(&self, root: &WlSurface) -> Option<&Output> {
// Check the main layout.
let win_out = self.layout.find_window_and_output(root);
let layout_output = win_out.map(|(_, output)| output);
// Check layer-shell.
let has_layer_surface = |o: &&Output| {
layer_map_for_output(o)
.layer_for_surface(root, WindowSurfaceType::TOPLEVEL)
.is_some()
};
2023-12-24 17:38:13 +04:00
let layer_shell_output = || self.layout.outputs().find(has_layer_surface);
layout_output.or_else(layer_shell_output)
}
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()
}
2024-01-12 17:53:35 +04:00
pub fn refresh_popup_grab(&mut self) {
if let Some(grab) = &self.popup_grab {
if grab.grab.has_ended() {
self.popup_grab = None;
}
}
}
/// Schedules an immediate redraw on all outputs if one is not already scheduled.
pub fn queue_redraw_all(&mut self) {
let outputs: Vec<_> = self.output_state.keys().cloned().collect();
for output in outputs {
self.queue_redraw(output);
}
}
2023-08-10 09:58:26 +04:00
/// Schedules an immediate redraw if one is not already scheduled.
pub fn queue_redraw(&mut self, output: Output) {
let state = self.output_state.get_mut(&output).unwrap();
2023-09-30 17:13:56 +04:00
let token = match mem::take(&mut state.redraw_state) {
RedrawState::Idle => None,
RedrawState::WaitingForEstimatedVBlank(token) => Some(token),
// A redraw is already queued, put it back and do nothing.
value @ (RedrawState::Queued(_)
| RedrawState::WaitingForEstimatedVBlankAndQueued(_)) => {
state.redraw_state = value;
return;
}
2023-09-30 17:13:56 +04:00
// We're waiting for VBlank, request a redraw afterwards.
RedrawState::WaitingForVBlank { .. } => {
state.redraw_state = RedrawState::WaitingForVBlank {
redraw_needed: true,
};
return;
}
};
2023-08-10 09:58:26 +04:00
2023-09-24 17:30:06 +04:00
let idle = self.event_loop.insert_idle(move |state| {
state.niri.redraw(&mut state.backend, &output);
2023-08-10 14:06:32 +04:00
});
2023-09-30 17:13:56 +04:00
state.redraw_state = match token {
Some(token) => RedrawState::WaitingForEstimatedVBlankAndQueued((token, idle)),
None => RedrawState::Queued(idle),
};
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>> {
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;
// 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());
let (mut pointer_elements, pointer_pos) = match render_cursor {
RenderCursor::Hidden => (vec![], pointer_pos.to_physical_precise_round(output_scale)),
RenderCursor::Surface { surface, hotspot } => {
let pointer_pos =
(pointer_pos - hotspot.to_f64()).to_physical_precise_round(output_scale);
let pointer_elements = render_elements_from_surface_tree(
renderer,
&surface,
pointer_pos,
output_scale,
1.,
2023-09-11 19:24:09 +04:00
Kind::Cursor,
2023-10-29 14:04:38 +04:00
);
(pointer_elements, pointer_pos)
}
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-03 11:38:36 +04:00
let texture = self.cursor_texture_cache.get(
renderer.as_gles_renderer(),
icon,
scale,
&cursor,
idx,
);
2023-10-29 14:04:38 +04:00
let pointer_elements = vec![OutputRenderElements::NamedPointer(
2024-01-03 11:38:36 +04:00
PrimaryGpuTextureRenderElement(TextureRenderElement::from_texture_buffer(
2023-10-29 14:04:38 +04:00
pointer_pos.to_f64(),
&texture,
None,
None,
None,
Kind::Cursor,
2024-01-03 11:38:36 +04:00
)),
2023-10-29 14:04:38 +04:00
)];
(pointer_elements, pointer_pos)
}
2023-08-15 16:40:54 +04:00
};
if let Some(dnd_icon) = &self.dnd_icon {
pointer_elements.extend(render_elements_from_surface_tree(
renderer,
dnd_icon,
pointer_pos,
output_scale,
2023-08-15 16:40:54 +04:00
1.,
2023-09-11 19:24:09 +04:00
Kind::Unspecified,
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) {
let _span = tracy_client::span!("Niri::refresh_pointer_outputs");
// 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());
2023-10-29 14:04:38 +04:00
match self.cursor_manager.cursor_image().clone() {
CursorImageStatus::Surface(ref surface) => {
let hotspot = with_states(surface, |states| {
states
.data_map
.get::<Mutex<CursorImageAttributes>>()
.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()
.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.
let mut cursor_scale = 1;
let mut dnd_scale = 1;
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;
2023-10-26 00:15:46 +04:00
cursor_scale = cursor_scale.max(output.current_scale().integer_scale());
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;
2023-10-26 00:15:46 +04:00
dnd_scale = dnd_scale.max(output.current_scale().integer_scale());
output_update(output, Some(overlap), surface);
} else {
output_update(output, None, surface);
}
}
}
2023-10-26 00:15:46 +04:00
with_states(surface, |data| {
send_surface_state(surface, data, cursor_scale, Transform::Normal);
});
if let Some((surface, _)) = dnd {
with_states(surface, |data| {
send_surface_state(surface, data, dnd_scale, Transform::Normal);
});
}
}
2023-10-29 14:04:38 +04:00
cursor_image => {
// There's no cursor surface, but there might be a DnD icon.
let Some(surface) = &self.dnd_icon else {
return;
};
let icon = if let CursorImageStatus::Named(icon) = cursor_image {
icon
} else {
Default::default()
};
let mut dnd_scale = 1;
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;
dnd_scale = dnd_scale.max(output.current_scale().integer_scale());
output_update(output, Some(overlap), surface);
} else {
output_update(output, None, surface);
}
with_states(surface, |data| {
send_surface_state(surface, data, dnd_scale, Transform::Normal);
});
}
}
}
}
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,
output: &Output,
include_pointer: bool,
2024-01-03 11:38:36 +04:00
) -> Vec<OutputRenderElements<R>> {
let _span = tracy_client::span!("Niri::render");
2023-08-15 12:49:26 +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.
let mut elements = vec![];
if include_pointer {
elements = self.pointer_element(renderer, output);
}
2023-08-15 12:49:26 +04:00
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.,
Kind::Unspecified,
));
}
// Draw the solid color background.
elements.push(
SolidColorRenderElement::from_buffer(
&state.lock_color_buffer,
(0, 0),
output_scale,
1.,
Kind::Unspecified,
)
.into(),
);
return elements;
}
2023-10-30 20:29:03 +04:00
// Prepare the background element.
let state = self.output_state.get(output).unwrap();
let background = SolidColorRenderElement::from_buffer(
&state.background_buffer,
(0, 0),
output_scale,
1.,
Kind::Unspecified,
)
.into();
// If the screenshot UI is open, draw it.
if self.screenshot_ui.is_open() {
elements.extend(
self.screenshot_ui
.render_output(output)
.into_iter()
.map(OutputRenderElements::from),
);
// Add the background for outputs that were connected while the screenshot UI was open.
elements.push(background);
return elements;
}
2023-10-13 13:30:11 +04:00
// Get monitor elements.
let mon = self.layout.monitor_for_output(output).unwrap();
let monitor_elements = mon.render_elements(renderer);
2023-09-27 08:50:00 +04:00
// Get layer-shell elements.
let layer_map = layer_map_for_output(output);
2024-01-03 11:38:36 +04:00
let mut extend_from_layer = |elements: &mut Vec<OutputRenderElements<R>>, layer| {
2023-09-27 08:50:00 +04:00
let iter = layer_map
.layers_on(layer)
2023-08-15 12:49:26 +04:00
.filter_map(|surface| {
layer_map
.layer_geometry(surface)
.map(|geo| (geo.loc, surface))
})
.flat_map(|(loc, surface)| {
surface
.render_elements(
renderer,
loc.to_physical_precise_round(output_scale),
output_scale,
2023-08-15 12:49:26 +04:00
1.,
)
.into_iter()
.map(OutputRenderElements::Wayland)
2023-09-27 08:50:00 +04:00
});
elements.extend(iter);
};
// The upper layer-shell elements go next.
extend_from_layer(&mut elements, Layer::Overlay);
2023-08-15 12:49:26 +04:00
// Then the regular monitor elements and the top layer in varying order.
if mon.render_above_top_layer() {
elements.extend(monitor_elements.into_iter().map(OutputRenderElements::from));
extend_from_layer(&mut elements, Layer::Top);
} else {
extend_from_layer(&mut elements, Layer::Top);
elements.extend(monitor_elements.into_iter().map(OutputRenderElements::from));
}
2023-08-15 12:49:26 +04:00
// Then the lower layer-shell elements.
2023-09-27 08:50:00 +04:00
extend_from_layer(&mut elements, Layer::Bottom);
extend_from_layer(&mut elements, Layer::Background);
2023-08-10 09:57:13 +04:00
2023-10-27 08:34:00 +04:00
// Then the background.
2023-10-30 20:29:03 +04:00
elements.push(background);
2023-10-27 08:34:00 +04:00
elements
}
2023-09-14 22:28:26 +04:00
fn redraw(&mut self, backend: &mut Backend, output: &Output) {
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,
RedrawState::Queued(_) | RedrawState::WaitingForEstimatedVBlankAndQueued(_)
));
let target_presentation_time = state.frame_clock.next_presentation_time();
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 {
// Update from the config and advance the animations.
self.layout.advance_animations(target_presentation_time);
state.unfinished_animations_remain = self
.layout
.monitor_for_output(output)
.unwrap()
.are_animations_ongoing();
2023-10-29 14:04:38 +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
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.
state.redraw_state =
if let RedrawState::WaitingForEstimatedVBlank(token)
| RedrawState::WaitingForEstimatedVBlankAndQueued((token, _)) =
state.redraw_state
{
RedrawState::WaitingForEstimatedVBlank(token)
} else {
RedrawState::Idle
};
} else {
// Update the lock render state on successful render.
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-01-03 11:31:04 +04:00
if res == RenderResult::Skipped {
2023-10-13 13:30:11 +04:00
if state.lock_render_state == LockRenderState::Unlocked {
// We needed to render a locked frame on this output but failed.
self.unlock();
} else {
// Rendering failed but this output is already locked, so it's fine.
self.lock_state = LockState::Locking(confirmation);
}
} else {
// Rendering succeeded, check if this was the last output.
let all_locked = self
.output_state
.values()
.all(|state| state.lock_render_state == LockRenderState::Locked);
if all_locked {
confirmation.lock();
self.lock_state = LockState::Locked;
} else {
// Still waiting.
self.lock_state = LockState::Locking(confirmation);
}
}
}
lock_state => self.lock_state = lock_state,
}
2023-08-09 11:03:38 +04:00
// Send the frame callbacks.
//
// 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.
self.send_frame_callbacks(output);
2023-09-08 17:54:02 +04:00
// Render and send to PipeWire screencast streams.
#[cfg(feature = "xdp-gnome-screencast")]
{
backend.with_primary_renderer(|renderer| {
self.render_for_screen_cast(renderer, output, target_presentation_time);
});
}
}
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() {
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,
);
}
if let Some(surface) = &self.dnd_icon {
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.
for win in self.layout.windows_for_output(output) {
win.with_surfaces(|surface, states| {
update_surface_primary_scanout_output(
surface,
output,
states,
render_element_states,
// Windows are shown only on one output at a time.
|_, _, output, _| output,
);
});
}
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,
);
}
}
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");
// 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.
for win in self.layout.windows_for_output(output) {
2024-01-03 18:16:20 +04:00
win.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
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
);
}
2023-09-03 15:15:55 +04:00
if let Some(surface) = &self.dnd_icon {
send_dmabuf_feedback_surface_tree(
surface,
output,
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,
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
);
}
}
pub fn send_frame_callbacks(&self, output: &Output) {
let _span = tracy_client::span!("Niri::send_frame_callbacks");
let state = self.output_state.get(output).unwrap();
let sequence = state.current_estimated_sequence;
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.
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 {
if last_output == output && Some(*last_sequence) == sequence {
send = false;
}
}
if send {
if let Some(sequence) = sequence {
*last_sent_at = Some((output.clone(), sequence));
}
Some(output.clone())
} else {
None
}
};
2023-09-04 14:27:16 +04:00
let frame_callback_time = get_monotonic_time();
for win in self.layout.windows_for_output(output) {
win.send_frame(output, frame_callback_time, None, should_send);
}
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() {
surface.send_frame(output, frame_callback_time, None, 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,
None,
should_send,
);
}
2023-08-16 07:08:57 +04:00
if let Some(surface) = &self.dnd_icon {
send_frames_surface_tree(surface, output, frame_callback_time, None, should_send);
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() {
send_frames_surface_tree(surface, output, frame_callback_time, None, should_send);
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,
surface_primary_scanout_output,
2023-08-16 10:59:34 +04:00
|surface, _| {
surface_presentation_feedback_flags_from_states(surface, render_element_states)
},
);
}
if let Some(surface) = &self.dnd_icon {
take_presentation_feedback_surface_tree(
surface,
&mut feedback,
surface_primary_scanout_output,
2023-08-16 10:59:34 +04:00
|surface, _| {
surface_presentation_feedback_flags_from_states(surface, render_element_states)
},
);
}
for win in self.layout.windows_for_output(output) {
2023-08-16 10:59:34 +04:00
win.take_presentation_feedback(
&mut feedback,
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,
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
#[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,
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
let size = output.current_mode().unwrap().size;
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;
let mut casts = mem::take(&mut self.casts);
for cast in &mut casts {
2023-09-08 17:54:02 +04:00
if !cast.is_active.get() {
continue;
}
2023-08-27 19:34:37 +04:00
2023-09-08 17:54:02 +04:00
if &cast.output != output {
continue;
}
let last = cast.last_frame_time;
let min = cast.min_time_between_frames.get();
if last.is_zero() {
trace!(?target_presentation_time, ?last, "last is zero, recording");
} else if target_presentation_time < last {
// Record frame with a warning; in case it was an overflow this will fix it.
warn!(
?target_presentation_time,
?last,
"target presentation time is below last, did it overflow or did we mispredict?"
2023-09-08 17:54:02 +04:00
);
} else {
let diff = target_presentation_time - last;
if diff < min {
trace!(
?target_presentation_time,
?last,
"skipping frame because it is too soon: diff={diff:?} < min={min:?}",
);
continue;
}
2023-09-08 17:54:02 +04:00
}
{
let mut buffer = match cast.stream.dequeue_buffer() {
Some(buffer) => buffer,
None => {
warn!("no available buffer in pw stream, skipping frame");
continue;
}
};
2023-08-27 19:34:37 +04:00
2023-09-08 17:54:02 +04:00
let data = &mut buffer.datas_mut()[0];
let fd = data.as_raw().fd as i32;
let dmabuf = cast.dmabufs.borrow()[&fd].clone();
// FIXME: Hidden / embedded / metadata cursor
2024-01-03 11:38:36 +04:00
let elements = elements
.get_or_insert_with(|| self.render::<GlesRenderer>(renderer, output, true));
2024-01-03 11:22:11 +04:00
2023-10-13 13:18:50 +04:00
if let Err(err) = render_to_dmabuf(renderer, dmabuf, size, scale, elements) {
2023-09-26 10:06:07 +04:00
error!("error rendering to dmabuf: {err:?}");
continue;
}
2023-09-08 17:54:02 +04:00
let maxsize = data.as_raw().maxsize;
let chunk = data.chunk_mut();
*chunk.size_mut() = maxsize;
*chunk.stride_mut() = maxsize as i32 / size.h;
}
cast.last_frame_time = target_presentation_time;
2023-08-27 19:34:37 +04:00
}
2024-01-03 11:22:11 +04:00
self.casts = casts;
2023-09-08 17:54:02 +04:00
}
2023-08-27 19:34:37 +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();
let path = format!("/org/gnome/Mutter/ScreenCast/Session/u{}", session_id);
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()
.stop(&server, iface.signal_context().clone())
.await
});
}
}
2023-10-30 20:29:03 +04:00
pub fn open_screenshot_ui(&mut self, renderer: &mut GlesRenderer) {
if self.is_locked() || self.screenshot_ui.is_open() {
return;
}
let Some(default_output) = self.output_under_cursor() else {
return;
};
let screenshots = self
.global_space
.outputs()
.cloned()
.filter_map(|output| {
let size = output.current_mode().unwrap().size;
let scale = Scale::from(output.current_scale().fractional_scale());
2024-01-03 11:38:36 +04:00
let elements = self.render::<GlesRenderer>(renderer, &output, true);
2023-10-30 20:29:03 +04:00
let res = render_to_texture(renderer, size, scale, Fourcc::Abgr8888, &elements);
let screenshot = match res {
Ok((texture, _)) => texture,
Err(err) => {
warn!("error rendering output {}: {err:?}", output.name());
return None;
}
};
Some((output, screenshot))
})
.collect();
self.screenshot_ui
.open(renderer, screenshots, default_output);
self.cursor_manager
.set_cursor_image(CursorImageStatus::Named(CursorIcon::Crosshair));
2023-10-30 20:29:03 +04:00
self.queue_redraw_all();
}
2023-10-26 17:30:03 +04:00
pub fn screenshot(&self, renderer: &mut GlesRenderer, output: &Output) -> anyhow::Result<()> {
2023-09-08 17:54:02 +04:00
let _span = tracy_client::span!("Niri::screenshot");
let size = output.current_mode().unwrap().size;
let scale = Scale::from(output.current_scale().fractional_scale());
2024-01-03 11:38:36 +04:00
let elements = self.render::<GlesRenderer>(renderer, output, true);
2023-10-30 20:28:13 +04:00
let pixels = render_to_vec(renderer, size, scale, Fourcc::Abgr8888, &elements)?;
2023-08-27 19:34:37 +04:00
2023-10-10 12:42:24 +04:00
self.save_screenshot(size, pixels)
.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,
window: &Window,
) -> anyhow::Result<()> {
let _span = tracy_client::span!("Niri::screenshot_window");
let scale = Scale::from(output.current_scale().fractional_scale());
let bbox = window.bbox_with_popups();
let size = bbox.size.to_physical_precise_ceil(scale);
let buf_pos = Point::from((0, 0)) - bbox.loc;
// FIXME: pointer.
let elements = window.render_elements::<WaylandSurfaceRenderElement<GlesRenderer>>(
renderer,
buf_pos.to_physical_precise_ceil(scale),
scale,
1.,
);
2023-10-30 20:28:13 +04:00
let pixels = render_to_vec(renderer, size, scale, Fourcc::Abgr8888, &elements)?;
2023-10-10 12:42:24 +04:00
self.save_screenshot(size, pixels)
.context("error saving screenshot")
}
2023-10-30 20:29:03 +04:00
pub fn save_screenshot(
&self,
size: Size<i32, Physical>,
pixels: Vec<u8>,
) -> anyhow::Result<()> {
2023-10-31 08:57:44 +04:00
let path = match make_screenshot_path(&self.config.borrow()) {
Ok(path) => path,
Err(err) => {
warn!("error making screenshot path: {err:?}");
None
}
};
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
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")]
crate::utils::show_screenshot_notification(image_path);
#[cfg(not(feature = "dbus"))]
drop(image_path);
2023-08-27 19:34:37 +04:00
});
Ok(())
}
2023-10-10 10:25:26 +04:00
#[cfg(feature = "dbus")]
pub fn screenshot_all_outputs(
2023-10-26 17:30:03 +04:00
&self,
renderer: &mut GlesRenderer,
include_pointer: bool,
on_done: impl FnOnce(PathBuf) + Send + 'static,
) -> anyhow::Result<()> {
2023-10-10 10:25:26 +04:00
use std::cmp::max;
use smithay::backend::renderer::element::utils::{Relocate, RelocateRenderElement};
let _span = tracy_client::span!("Niri::screenshot_all_outputs");
let mut elements = vec![];
let mut size = Size::from((0, 0));
let outputs: Vec<_> = self.global_space.outputs().cloned().collect();
for output in outputs {
let geom = self.global_space.output_geometry(&output).unwrap();
// FIXME: this does not work when outputs can have non-1 scale.
let geom = geom.to_physical(1);
size.w = max(size.w, geom.loc.x + geom.size.w);
size.h = max(size.h, geom.loc.y + geom.size.h);
2024-01-03 11:38:36 +04:00
let output_elements = self.render::<GlesRenderer>(renderer, &output, include_pointer);
elements.extend(output_elements.into_iter().map(|elem| {
RelocateRenderElement::from_element(elem, geom.loc, Relocate::Relative)
}));
}
// FIXME: scale.
2023-10-30 20:28:13 +04:00
let pixels = render_to_vec(renderer, size, Scale::from(1.), Fourcc::Abgr8888, &elements)?;
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
});
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-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:?}");
return;
}
on_done(path);
});
Ok(())
}
2023-10-13 13:30:11 +04:00
pub fn is_locked(&self) -> bool {
!matches!(self.lock_state, LockState::Unlocked)
}
pub fn lock(&mut self, confirmation: SessionLocker) {
info!("locking session");
2023-10-30 20:29:03 +04:00
self.screenshot_ui.close();
self.cursor_manager
.set_cursor_image(CursorImageStatus::default_named());
2023-10-30 20:29:03 +04:00
2023-10-13 13:30:11 +04:00
self.lock_state = LockState::Locking(confirmation);
self.queue_redraw_all();
}
pub fn unlock(&mut self) {
info!("unlocking session");
self.lock_state = LockState::Unlocked;
for output_state in self.output_state.values_mut() {
output_state.lock_surface = None;
}
self.queue_redraw_all();
}
pub fn new_lock_surface(&mut self, surface: LockSurface, output: &Output) {
if !self.is_locked() {
error!("tried to add a lock surface on an unlocked session");
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
pub fn maybe_activate_pointer_constraint(
&self,
new_pos: Point<f64, Logical>,
new_under: &Option<PointerFocus>,
) {
let Some(under) = new_under else { return };
let pointer = &self.seat.get_pointer().unwrap();
with_pointer_constraint(&under.surface.0, pointer, |constraint| {
let Some(constraint) = constraint else { return };
if constraint.is_active() {
return;
}
// Constraint does not apply if not within region.
if let Some(region) = constraint.region() {
let new_pos_within_surface = new_pos.to_i32_round() - under.surface.1;
if !region.contains(new_pos_within_surface) {
return;
}
}
constraint.activate();
});
}
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,
/// Whether this client is denied from the restricted protocols such as security-context.
pub restricted: 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
2023-10-30 20:28:13 +04:00
fn render_to_texture(
2023-09-08 17:54:02 +04:00
renderer: &mut GlesRenderer,
size: Size<i32, Physical>,
scale: Scale<f64>,
2023-10-30 20:28:13 +04:00
fourcc: Fourcc,
elements: &[impl RenderElement<GlesRenderer>],
2023-10-30 20:28:13 +04:00
) -> anyhow::Result<(GlesTexture, SyncPoint)> {
let _span = tracy_client::span!("render_to_texture");
2023-09-08 17:54:02 +04:00
let output_rect = Rectangle::from_loc_and_size((0, 0), size);
let buffer_size = size.to_logical(1).to_buffer(1, Transform::Normal);
let texture: GlesTexture = renderer
.create_buffer(fourcc, buffer_size)
.context("error creating texture")?;
2023-10-30 20:28:13 +04:00
renderer
.bind(texture.clone())
.context("error binding texture")?;
2023-09-08 17:54:02 +04:00
let mut frame = renderer
.render(size, Transform::Normal)
.context("error starting frame")?;
for element in elements.iter().rev() {
let src = element.src();
let dst = element.geometry(scale);
2023-09-08 17:54:02 +04:00
element
.draw(&mut frame, src, dst, &[output_rect])
.context("error drawing element")?;
}
let sync_point = frame.finish().context("error finishing frame")?;
2023-10-30 20:28:13 +04:00
Ok((texture, sync_point))
}
fn render_and_download(
renderer: &mut GlesRenderer,
size: Size<i32, Physical>,
scale: Scale<f64>,
fourcc: Fourcc,
elements: &[impl RenderElement<GlesRenderer>],
) -> anyhow::Result<GlesMapping> {
let _span = tracy_client::span!("render_and_download");
let (_, sync_point) = render_to_texture(renderer, size, scale, fourcc, elements)?;
2023-09-08 17:54:02 +04:00
sync_point.wait();
2023-10-30 20:28:13 +04:00
let buffer_size = size.to_logical(1).to_buffer(1, Transform::Normal);
2023-09-08 17:54:02 +04:00
let mapping = renderer
.copy_framebuffer(Rectangle::from_loc_and_size((0, 0), buffer_size), fourcc)
.context("error copying framebuffer")?;
Ok(mapping)
}
2023-09-19 19:08:45 +04:00
fn render_to_vec(
renderer: &mut GlesRenderer,
size: Size<i32, Physical>,
scale: Scale<f64>,
2023-10-30 20:28:13 +04:00
fourcc: Fourcc,
elements: &[impl RenderElement<GlesRenderer>],
2023-09-19 19:08:45 +04:00
) -> anyhow::Result<Vec<u8>> {
let _span = tracy_client::span!("render_to_vec");
let mapping =
2023-10-30 20:28:13 +04:00
render_and_download(renderer, size, scale, fourcc, elements).context("error rendering")?;
2023-09-19 19:08:45 +04:00
let copy = renderer
.map_texture(&mapping)
.context("error mapping texture")?;
Ok(copy.to_vec())
}
#[cfg(feature = "xdp-gnome-screencast")]
2023-09-08 17:54:02 +04:00
fn render_to_dmabuf(
renderer: &mut GlesRenderer,
dmabuf: smithay::backend::allocator::dmabuf::Dmabuf,
2023-09-08 17:54:02 +04:00
size: Size<i32, Physical>,
scale: Scale<f64>,
2023-09-08 17:54:02 +04:00
elements: &[OutputRenderElements<GlesRenderer>],
) -> anyhow::Result<()> {
let _span = tracy_client::span!("render_to_dmabuf");
let output_rect = Rectangle::from_loc_and_size((0, 0), size);
renderer.bind(dmabuf).context("error binding texture")?;
let mut frame = renderer
.render(size, Transform::Normal)
.context("error starting frame")?;
for element in elements.iter().rev() {
let src = element.src();
let dst = element.geometry(scale);
2023-09-08 17:54:02 +04:00
element
.draw(&mut frame, src, dst, &[output_rect])
.context("error drawing element")?;
}
let _sync_point = frame.finish().context("error finishing frame")?;
Ok(())
}
2024-01-03 11:38:36 +04:00
// Manual RenderElement implementation due to AsGlesFrame requirement.
#[derive(Debug)]
pub enum OutputRenderElements<R: NiriRenderer> {
Monitor(MonitorRenderElement<R>),
Wayland(WaylandSurfaceRenderElement<R>),
NamedPointer(PrimaryGpuTextureRenderElement),
SolidColor(SolidColorRenderElement),
ScreenshotUi(ScreenshotUiRenderElement),
}
impl<R: NiriRenderer> Element for OutputRenderElements<R> {
fn id(&self) -> &Id {
match self {
Self::Monitor(elem) => elem.id(),
Self::Wayland(elem) => elem.id(),
Self::NamedPointer(elem) => elem.id(),
Self::SolidColor(elem) => elem.id(),
Self::ScreenshotUi(elem) => elem.id(),
}
}
fn current_commit(&self) -> CommitCounter {
match self {
Self::Monitor(elem) => elem.current_commit(),
Self::Wayland(elem) => elem.current_commit(),
Self::NamedPointer(elem) => elem.current_commit(),
Self::SolidColor(elem) => elem.current_commit(),
Self::ScreenshotUi(elem) => elem.current_commit(),
}
}
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
match self {
Self::Monitor(elem) => elem.geometry(scale),
Self::Wayland(elem) => elem.geometry(scale),
Self::NamedPointer(elem) => elem.geometry(scale),
Self::SolidColor(elem) => elem.geometry(scale),
Self::ScreenshotUi(elem) => elem.geometry(scale),
}
}
fn transform(&self) -> Transform {
match self {
Self::Monitor(elem) => elem.transform(),
Self::Wayland(elem) => elem.transform(),
Self::NamedPointer(elem) => elem.transform(),
Self::SolidColor(elem) => elem.transform(),
Self::ScreenshotUi(elem) => elem.transform(),
}
}
fn src(&self) -> Rectangle<f64, Buffer> {
match self {
Self::Monitor(elem) => elem.src(),
Self::Wayland(elem) => elem.src(),
Self::NamedPointer(elem) => elem.src(),
Self::SolidColor(elem) => elem.src(),
Self::ScreenshotUi(elem) => elem.src(),
}
}
fn damage_since(
&self,
scale: Scale<f64>,
commit: Option<CommitCounter>,
) -> Vec<Rectangle<i32, Physical>> {
match self {
Self::Monitor(elem) => elem.damage_since(scale, commit),
Self::Wayland(elem) => elem.damage_since(scale, commit),
Self::NamedPointer(elem) => elem.damage_since(scale, commit),
Self::SolidColor(elem) => elem.damage_since(scale, commit),
Self::ScreenshotUi(elem) => elem.damage_since(scale, commit),
}
}
fn opaque_regions(&self, scale: Scale<f64>) -> Vec<Rectangle<i32, Physical>> {
match self {
Self::Monitor(elem) => elem.opaque_regions(scale),
Self::Wayland(elem) => elem.opaque_regions(scale),
Self::NamedPointer(elem) => elem.opaque_regions(scale),
Self::SolidColor(elem) => elem.opaque_regions(scale),
Self::ScreenshotUi(elem) => elem.opaque_regions(scale),
}
}
fn alpha(&self) -> f32 {
match self {
Self::Monitor(elem) => elem.alpha(),
Self::Wayland(elem) => elem.alpha(),
Self::NamedPointer(elem) => elem.alpha(),
Self::SolidColor(elem) => elem.alpha(),
Self::ScreenshotUi(elem) => elem.alpha(),
}
}
fn kind(&self) -> Kind {
match self {
Self::Monitor(elem) => elem.kind(),
Self::Wayland(elem) => elem.kind(),
Self::NamedPointer(elem) => elem.kind(),
Self::SolidColor(elem) => elem.kind(),
Self::ScreenshotUi(elem) => elem.kind(),
}
}
}
impl RenderElement<GlesRenderer> for OutputRenderElements<GlesRenderer> {
fn draw(
&self,
frame: &mut GlesFrame<'_>,
src: Rectangle<f64, Buffer>,
dst: Rectangle<i32, Physical>,
damage: &[Rectangle<i32, Physical>],
) -> Result<(), GlesError> {
match self {
Self::Monitor(elem) => elem.draw(frame, src, dst, damage),
Self::Wayland(elem) => elem.draw(frame, src, dst, damage),
Self::NamedPointer(elem) => {
RenderElement::<GlesRenderer>::draw(&elem, frame, src, dst, damage)
}
Self::SolidColor(elem) => {
RenderElement::<GlesRenderer>::draw(&elem, frame, src, dst, damage)
}
Self::ScreenshotUi(elem) => {
RenderElement::<GlesRenderer>::draw(&elem, frame, src, dst, damage)
}
}
}
fn underlying_storage(&self, renderer: &mut GlesRenderer) -> Option<UnderlyingStorage> {
match self {
Self::Monitor(elem) => elem.underlying_storage(renderer),
Self::Wayland(elem) => elem.underlying_storage(renderer),
Self::NamedPointer(elem) => elem.underlying_storage(renderer),
Self::SolidColor(elem) => elem.underlying_storage(renderer),
2024-01-03 18:16:20 +04:00
Self::ScreenshotUi(elem) => elem.underlying_storage(renderer),
}
}
}
impl<'render, 'alloc> RenderElement<TtyRenderer<'render, 'alloc>>
for OutputRenderElements<TtyRenderer<'render, 'alloc>>
{
fn draw(
&self,
frame: &mut TtyFrame<'render, 'alloc, '_>,
src: Rectangle<f64, Buffer>,
dst: Rectangle<i32, Physical>,
damage: &[Rectangle<i32, Physical>],
) -> Result<(), TtyRendererError<'render, 'alloc>> {
match self {
Self::Monitor(elem) => elem.draw(frame, src, dst, damage),
Self::Wayland(elem) => elem.draw(frame, src, dst, damage),
Self::NamedPointer(elem) => {
RenderElement::<TtyRenderer<'render, 'alloc>>::draw(&elem, frame, src, dst, damage)
}
Self::SolidColor(elem) => {
RenderElement::<TtyRenderer<'render, 'alloc>>::draw(&elem, frame, src, dst, damage)
}
Self::ScreenshotUi(elem) => {
RenderElement::<TtyRenderer<'render, 'alloc>>::draw(&elem, frame, src, dst, damage)
}
}
}
fn underlying_storage(
&self,
renderer: &mut TtyRenderer<'render, 'alloc>,
) -> Option<UnderlyingStorage> {
match self {
Self::Monitor(elem) => elem.underlying_storage(renderer),
Self::Wayland(elem) => elem.underlying_storage(renderer),
Self::NamedPointer(elem) => elem.underlying_storage(renderer),
Self::SolidColor(elem) => elem.underlying_storage(renderer),
2024-01-03 11:38:36 +04:00
Self::ScreenshotUi(elem) => elem.underlying_storage(renderer),
}
}
}
impl<R: NiriRenderer> From<MonitorRenderElement<R>> for OutputRenderElements<R> {
fn from(x: MonitorRenderElement<R>) -> Self {
Self::Monitor(x)
}
}
impl<R: NiriRenderer> From<WaylandSurfaceRenderElement<R>> for OutputRenderElements<R> {
fn from(x: WaylandSurfaceRenderElement<R>) -> Self {
Self::Wayland(x)
}
}
impl<R: NiriRenderer> From<PrimaryGpuTextureRenderElement> for OutputRenderElements<R> {
fn from(x: PrimaryGpuTextureRenderElement) -> Self {
Self::NamedPointer(x)
}
}
impl<R: NiriRenderer> From<SolidColorRenderElement> for OutputRenderElements<R> {
fn from(x: SolidColorRenderElement) -> Self {
Self::SolidColor(x)
}
}
impl<R: NiriRenderer> From<ScreenshotUiRenderElement> for OutputRenderElements<R> {
fn from(x: ScreenshotUiRenderElement) -> Self {
Self::ScreenshotUi(x)
}
}