2023-09-14 22:28:26 +04:00
|
|
|
use std::cell::RefCell;
|
2023-08-13 12:46:53 +04:00
|
|
|
use std::collections::HashMap;
|
2023-10-10 08:49:47 +04:00
|
|
|
use std::ffi::OsString;
|
2023-09-20 09:28:23 +04:00
|
|
|
use std::path::PathBuf;
|
2023-09-14 22:28:26 +04:00
|
|
|
use std::rc::Rc;
|
2023-08-15 16:19:05 +04:00
|
|
|
use std::sync::{Arc, Mutex};
|
2023-08-14 15:53:24 +04:00
|
|
|
use std::time::Duration;
|
2023-09-30 17:13:56 +04:00
|
|
|
use std::{env, mem, thread};
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2023-09-27 16:08:11 +04:00
|
|
|
use _server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as KdeDecorationsMode;
|
2023-08-27 19:34:37 +04:00
|
|
|
use anyhow::Context;
|
2023-10-10 12:11:05 +04:00
|
|
|
use image::ImageFormat;
|
2023-08-27 19:34:37 +04:00
|
|
|
use smithay::backend::allocator::Fourcc;
|
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;
|
2023-08-27 19:34:37 +04:00
|
|
|
use smithay::backend::renderer::element::{
|
2023-10-01 19:41:42 +04:00
|
|
|
default_primary_scanout_output_compare, render_elements, AsRenderElements, Kind, RenderElement,
|
|
|
|
|
RenderElementStates,
|
2023-08-27 19:34:37 +04:00
|
|
|
};
|
2023-09-08 17:54:02 +04:00
|
|
|
use smithay::backend::renderer::gles::{GlesMapping, GlesRenderer, GlesTexture};
|
2023-08-27 19:34:37 +04:00
|
|
|
use smithay::backend::renderer::{Bind, ExportMem, Frame, ImportAll, Offscreen, Renderer};
|
2023-08-16 10:59:34 +04:00
|
|
|
use smithay::desktop::utils::{
|
2023-10-02 18:48:41 +04:00
|
|
|
bbox_from_surface_tree, output_update, send_dmabuf_feedback_surface_tree,
|
|
|
|
|
send_frames_surface_tree, surface_presentation_feedback_flags_from_states,
|
|
|
|
|
surface_primary_scanout_output, take_presentation_feedback_surface_tree,
|
|
|
|
|
update_surface_primary_scanout_output, OutputPresentationFeedback,
|
2023-08-16 10:59:34 +04:00
|
|
|
};
|
2023-09-27 08:50:00 +04:00
|
|
|
use smithay::desktop::{layer_map_for_output, PopupManager, Space, Window, WindowSurfaceType};
|
2023-08-09 11:03:38 +04:00
|
|
|
use smithay::input::keyboard::XkbConfig;
|
2023-08-16 08:03:20 +04:00
|
|
|
use smithay::input::pointer::{CursorImageAttributes, CursorImageStatus, MotionEvent};
|
2023-08-09 11:03:38 +04:00
|
|
|
use smithay::input::{Seat, SeatState};
|
|
|
|
|
use smithay::output::Output;
|
|
|
|
|
use smithay::reexports::calloop::generic::Generic;
|
2023-09-21 13:39:37 +04:00
|
|
|
use smithay::reexports::calloop::timer::{TimeoutAction, Timer};
|
2023-09-29 13:12:50 +04:00
|
|
|
use smithay::reexports::calloop::{
|
2023-10-10 12:11:05 +04:00
|
|
|
self, Idle, Interest, LoopHandle, LoopSignal, Mode, PostAction, RegistrationToken,
|
2023-09-29 13:12:50 +04:00
|
|
|
};
|
2023-08-16 10:59:34 +04:00
|
|
|
use smithay::reexports::nix::libc::CLOCK_MONOTONIC;
|
2023-08-10 17:17:17 +04:00
|
|
|
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::WmCapabilities;
|
2023-09-27 16:08:11 +04:00
|
|
|
use smithay::reexports::wayland_protocols_misc::server_decoration as _server_decoration;
|
2023-08-13 12:46:53 +04:00
|
|
|
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::{
|
2023-09-08 17:54:02 +04:00
|
|
|
IsAlive, Logical, Physical, Point, Rectangle, Scale, Size, Transform, SERIAL_COUNTER,
|
2023-08-27 19:34:37 +04:00
|
|
|
};
|
2023-09-29 13:12:50 +04:00
|
|
|
use smithay::wayland::compositor::{
|
2023-10-01 19:41:42 +04:00
|
|
|
with_states, with_surface_tree_downward, CompositorClientState, CompositorState, SurfaceData,
|
|
|
|
|
TraversalAction,
|
2023-09-29 13:12:50 +04:00
|
|
|
};
|
2023-09-03 15:15:55 +04:00
|
|
|
use smithay::wayland::dmabuf::DmabufFeedback;
|
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-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-10-10 12:11:05 +04:00
|
|
|
use smithay::wayland::selection::data_device::{set_data_device_selection, DataDeviceState};
|
|
|
|
|
use smithay::wayland::selection::primary_selection::{
|
|
|
|
|
set_primary_selection, PrimarySelectionState,
|
|
|
|
|
};
|
2023-09-30 21:05:13 +04:00
|
|
|
use smithay::wayland::selection::wlr_data_control::DataControlState;
|
2023-09-27 16:08:11 +04:00
|
|
|
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;
|
2023-08-16 11:43:52 +04:00
|
|
|
use smithay::wayland::tablet_manager::TabletManagerState;
|
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-09-03 13:25:43 +04:00
|
|
|
use crate::backend::{Backend, Tty, Winit};
|
2023-09-05 12:58:51 +04:00
|
|
|
use crate::config::Config;
|
2023-10-01 07:59:28 +04:00
|
|
|
use crate::cursor::Cursor;
|
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};
|
2023-09-30 09:58:34 +04:00
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
2023-10-10 09:55:44 +04:00
|
|
|
use crate::dbus::mutter_screen_cast::{self, ScreenCastToNiri};
|
2023-08-14 15:53:24 +04:00
|
|
|
use crate::frame_clock::FrameClock;
|
2023-10-05 09:25:07 +04:00
|
|
|
use crate::layout::{output_size, Layout, MonitorRenderElement};
|
2023-09-08 17:54:02 +04:00
|
|
|
use crate::pw_utils::{Cast, PipeWire};
|
2023-10-01 07:59:28 +04:00
|
|
|
use crate::utils::{center, get_monotonic_time, make_screenshot_path};
|
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,
|
2023-10-10 08:49:47 +04:00
|
|
|
pub socket_name: OsString,
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
// 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).
|
2023-10-05 09:25:07 +04:00
|
|
|
pub layout: Layout<Window>,
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
// This space does not actually contain any windows, but all outputs are mapped into it
|
|
|
|
|
// according to their global position.
|
|
|
|
|
pub global_space: Space<Window>,
|
|
|
|
|
|
|
|
|
|
// 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,
|
|
|
|
|
|
2023-08-09 11:03:38 +04:00
|
|
|
// Smithay state.
|
|
|
|
|
pub compositor_state: CompositorState,
|
|
|
|
|
pub xdg_shell_state: XdgShellState,
|
2023-09-26 13:44:37 +04:00
|
|
|
pub xdg_decoration_state: XdgDecorationState,
|
2023-09-27 16:08:11 +04:00
|
|
|
pub kde_decoration_state: KdeDecorationState,
|
2023-08-15 12:49:26 +04:00
|
|
|
pub layer_shell_state: WlrLayerShellState,
|
2023-08-09 11:03:38 +04:00
|
|
|
pub shm_state: ShmState,
|
|
|
|
|
pub output_manager_state: OutputManagerState,
|
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-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,
|
2023-08-16 10:59:34 +04:00
|
|
|
pub presentation_state: PresentationState,
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2023-09-03 14:10:02 +04:00
|
|
|
pub seat: Seat<State>,
|
2023-08-10 09:58:26 +04:00
|
|
|
|
2023-10-01 07:59:28 +04:00
|
|
|
pub default_cursor: Cursor,
|
2023-08-15 16:19:05 +04:00
|
|
|
pub cursor_image: CursorImageStatus,
|
2023-08-15 16:40:54 +04:00
|
|
|
pub dnd_icon: Option<WlSurface>,
|
2023-08-27 10:29:06 +04:00
|
|
|
|
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-13 12:46:53 +04:00
|
|
|
}
|
2023-08-10 14:27:09 +04:00
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
pub struct OutputState {
|
|
|
|
|
pub global: GlobalId,
|
2023-08-14 15:53:24 +04:00
|
|
|
pub frame_clock: FrameClock,
|
2023-09-30 17:13:56 +04:00
|
|
|
pub redraw_state: RedrawState,
|
|
|
|
|
// After the last redraw, some ongoing animations still remain.
|
|
|
|
|
pub unfinished_animations_remain: bool,
|
2023-09-29 13:12:50 +04:00
|
|
|
/// 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-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>)),
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-29 13:12:50 +04:00
|
|
|
// Not related to the one in Smithay.
|
|
|
|
|
//
|
|
|
|
|
// This state keeps track of when a surface last received a frame callback.
|
|
|
|
|
struct SurfaceFrameThrottlingState {
|
|
|
|
|
/// Output and sequence that the frame callback was last sent at.
|
|
|
|
|
last_sent_at: RefCell<Option<(Output, u32)>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
self.niri.refresh_pointer_outputs();
|
|
|
|
|
self.niri.popups.cleanup();
|
|
|
|
|
self.update_focus();
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
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-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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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_focus(&mut self) {
|
|
|
|
|
let focus = self.niri.layer_surface_focus().or_else(|| {
|
|
|
|
|
self.niri
|
2023-10-05 09:25:07 +04:00
|
|
|
.layout
|
2023-09-03 14:10:02 +04:00
|
|
|
.focus()
|
|
|
|
|
.map(|win| win.toplevel().wl_surface().clone())
|
|
|
|
|
});
|
|
|
|
|
let keyboard = self.niri.seat.get_keyboard().unwrap();
|
|
|
|
|
if keyboard.current_focus() != focus {
|
|
|
|
|
keyboard.set_focus(self, focus, SERIAL_COUNTER.next_serial());
|
|
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
self.niri.layout.update_config(&config);
|
|
|
|
|
|
2023-10-01 17:42:56 +04:00
|
|
|
let mut old_config = self.niri.config.borrow_mut();
|
|
|
|
|
|
|
|
|
|
if config.cursor != old_config.cursor {
|
|
|
|
|
self.niri.default_cursor =
|
|
|
|
|
Cursor::load(&config.cursor.xcursor_theme, config.cursor.xcursor_size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*old_config = config;
|
|
|
|
|
|
|
|
|
|
// Release the borrow.
|
|
|
|
|
drop(old_config);
|
|
|
|
|
|
2023-09-26 19:24:50 +04:00
|
|
|
self.niri.queue_redraw_all();
|
|
|
|
|
// FIXME: apply output scale and whatnot.
|
|
|
|
|
// FIXME: apply libinput device settings.
|
|
|
|
|
// FIXME: apply xkb settings.
|
|
|
|
|
// FIXME: apply xdg decoration settings.
|
|
|
|
|
}
|
2023-10-10 08:49:47 +04:00
|
|
|
|
|
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
2023-10-10 09:55:44 +04:00
|
|
|
pub fn on_screen_cast_msg(
|
2023-10-10 08:49:47 +04:00
|
|
|
&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,
|
2023-10-10 08:49:47 +04:00
|
|
|
) {
|
|
|
|
|
match msg {
|
2023-10-10 08:55:54 +04:00
|
|
|
ScreenCastToNiri::StartCast {
|
2023-10-10 08:49:47 +04:00
|
|
|
session_id,
|
|
|
|
|
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:49:47 +04:00
|
|
|
}
|
|
|
|
|
}
|
2023-10-10 08:54:15 +04:00
|
|
|
|
2023-10-10 10:25:26 +04:00
|
|
|
#[cfg(feature = "dbus")]
|
2023-10-10 09:55:44 +04:00
|
|
|
pub fn on_screen_shot_msg(
|
2023-10-10 08:54:15 +04:00
|
|
|
&mut self,
|
|
|
|
|
to_screenshot: &async_channel::Sender<NiriToScreenshot>,
|
|
|
|
|
msg: ScreenshotToNiri,
|
|
|
|
|
) {
|
|
|
|
|
let ScreenshotToNiri::TakeScreenshot { include_cursor } = msg;
|
|
|
|
|
let _span = tracy_client::span!("TakeScreenshot");
|
|
|
|
|
|
|
|
|
|
let Some(renderer) = self.backend.renderer() else {
|
|
|
|
|
let msg = NiriToScreenshot::ScreenshotResult(None);
|
|
|
|
|
if let Err(err) = to_screenshot.send_blocking(msg) {
|
|
|
|
|
warn!("error sending None to screenshot: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
if let Err(err) = res {
|
|
|
|
|
warn!("error taking a screenshot: {err:?}");
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
let layout = Layout::new(&config_);
|
|
|
|
|
|
2023-09-03 14:10:02 +04:00
|
|
|
let compositor_state = CompositorState::new::<State>(&display_handle);
|
|
|
|
|
let xdg_shell_state = XdgShellState::new_with_capabilities::<State>(
|
2023-08-10 17:17:17 +04:00
|
|
|
&display_handle,
|
2023-08-16 07:10:44 +04:00
|
|
|
[WmCapabilities::Fullscreen],
|
2023-08-10 17:17:17 +04:00
|
|
|
);
|
2023-09-26 13:44:37 +04:00
|
|
|
let xdg_decoration_state = XdgDecorationState::new::<State>(&display_handle);
|
2023-09-27 16:08:11 +04:00
|
|
|
let kde_decoration_state = KdeDecorationState::new::<State>(
|
|
|
|
|
&display_handle,
|
|
|
|
|
if config_.prefer_no_csd {
|
|
|
|
|
KdeDecorationsMode::Server
|
|
|
|
|
} else {
|
|
|
|
|
KdeDecorationsMode::Client
|
|
|
|
|
},
|
|
|
|
|
);
|
2023-09-03 14:10:02 +04:00
|
|
|
let layer_shell_state = WlrLayerShellState::new::<State>(&display_handle);
|
|
|
|
|
let shm_state = ShmState::new::<State>(&display_handle, vec![]);
|
|
|
|
|
let output_manager_state =
|
|
|
|
|
OutputManagerState::new_with_xdg_output::<State>(&display_handle);
|
2023-08-09 11:03:38 +04:00
|
|
|
let mut seat_state = SeatState::new();
|
2023-09-03 14:10:02 +04:00
|
|
|
let tablet_state = TabletManagerState::new::<State>(&display_handle);
|
|
|
|
|
let pointer_gestures_state = PointerGesturesState::new::<State>(&display_handle);
|
|
|
|
|
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),
|
|
|
|
|
|_| true,
|
|
|
|
|
);
|
2023-08-16 10:59:34 +04:00
|
|
|
let presentation_state =
|
2023-09-03 14:10:02 +04:00
|
|
|
PresentationState::new::<State>(&display_handle, CLOCK_MONOTONIC as u32);
|
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);
|
|
|
|
|
let input_method_state = InputMethodManagerState::new::<State>(&display_handle);
|
|
|
|
|
let virtual_keyboard_state =
|
|
|
|
|
VirtualKeyboardManagerState::new::<State, _>(&display_handle, |_| true);
|
|
|
|
|
|
2023-09-08 17:54:02 +04:00
|
|
|
let mut seat: Seat<State> = seat_state.new_wl_seat(&display_handle, backend.seat_name());
|
2023-08-10 18:14:11 +04:00
|
|
|
let xkb = XkbConfig {
|
2023-09-14 22:28:26 +04:00
|
|
|
rules: &config_.input.keyboard.xkb.rules,
|
|
|
|
|
model: &config_.input.keyboard.xkb.model,
|
|
|
|
|
layout: config_.input.keyboard.xkb.layout.as_deref().unwrap_or("us"),
|
|
|
|
|
variant: &config_.input.keyboard.xkb.variant,
|
|
|
|
|
options: config_.input.keyboard.xkb.options.clone(),
|
2023-08-10 18:14:11 +04:00
|
|
|
};
|
2023-09-16 12:37:23 +04:00
|
|
|
seat.add_keyboard(
|
|
|
|
|
xkb,
|
|
|
|
|
config_.input.keyboard.repeat_delay as i32,
|
|
|
|
|
config_.input.keyboard.repeat_rate as i32,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
2023-08-09 11:03:38 +04:00
|
|
|
seat.add_pointer();
|
|
|
|
|
|
2023-10-01 17:42:56 +04:00
|
|
|
let default_cursor =
|
|
|
|
|
Cursor::load(&config_.cursor.xcursor_theme, config_.cursor.xcursor_size);
|
2023-10-01 07:59:28 +04:00
|
|
|
|
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| {
|
2023-10-10 07:59:50 +04:00
|
|
|
let data = Arc::new(ClientState::default());
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-10-10 08:49:47 +04:00
|
|
|
let display_source = Generic::new(display, Interest::READ, Mode::Level);
|
|
|
|
|
event_loop
|
|
|
|
|
.insert_source(display_source, |_, display, state| {
|
|
|
|
|
// SAFETY: we don't drop the display.
|
|
|
|
|
unsafe {
|
|
|
|
|
display.get_mut().dispatch_clients(state).unwrap();
|
|
|
|
|
}
|
|
|
|
|
Ok(PostAction::Continue)
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
drop(config_);
|
|
|
|
|
Self {
|
|
|
|
|
config,
|
|
|
|
|
|
|
|
|
|
event_loop,
|
|
|
|
|
stop_signal,
|
|
|
|
|
display_handle,
|
|
|
|
|
socket_name,
|
|
|
|
|
|
|
|
|
|
layout,
|
|
|
|
|
global_space: Space::default(),
|
|
|
|
|
output_state: HashMap::new(),
|
|
|
|
|
output_by_name: HashMap::new(),
|
|
|
|
|
unmapped_windows: HashMap::new(),
|
|
|
|
|
monitors_active: true,
|
|
|
|
|
|
|
|
|
|
compositor_state,
|
|
|
|
|
xdg_shell_state,
|
|
|
|
|
xdg_decoration_state,
|
|
|
|
|
kde_decoration_state,
|
|
|
|
|
layer_shell_state,
|
|
|
|
|
text_input_state,
|
|
|
|
|
input_method_state,
|
|
|
|
|
virtual_keyboard_state,
|
|
|
|
|
shm_state,
|
|
|
|
|
output_manager_state,
|
|
|
|
|
seat_state,
|
|
|
|
|
tablet_state,
|
|
|
|
|
pointer_gestures_state,
|
|
|
|
|
data_device_state,
|
|
|
|
|
primary_selection_state,
|
|
|
|
|
data_control_state,
|
|
|
|
|
popups: PopupManager::default(),
|
|
|
|
|
presentation_state,
|
|
|
|
|
|
|
|
|
|
seat,
|
|
|
|
|
default_cursor,
|
|
|
|
|
cursor_image: CursorImageStatus::default_named(),
|
|
|
|
|
dnd_icon: None,
|
|
|
|
|
|
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")]
|
2023-10-10 08:49:47 +04:00
|
|
|
inhibit_power_key_fd: None,
|
2023-10-10 10:25:26 +04:00
|
|
|
|
2023-10-10 08:49:47 +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(())
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-14 15:53:24 +04:00
|
|
|
pub fn add_output(&mut self, output: Output, refresh_interval: Option<Duration>) {
|
2023-09-21 13:39:37 +04:00
|
|
|
let global = output.create_global::<State>(&self.display_handle);
|
|
|
|
|
|
2023-09-30 11:33:02 +04:00
|
|
|
let name = output.name();
|
|
|
|
|
let config = self
|
|
|
|
|
.config
|
|
|
|
|
.borrow()
|
|
|
|
|
.outputs
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|o| o.name == name)
|
|
|
|
|
.cloned()
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
|
|
|
|
|
let size = output_size(&output);
|
|
|
|
|
let position = config
|
|
|
|
|
.position
|
|
|
|
|
.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!(
|
|
|
|
|
"new 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))
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
debug!(
|
|
|
|
|
"putting new output {name} at x={} y={}",
|
|
|
|
|
position.x, position.y
|
|
|
|
|
);
|
|
|
|
|
self.global_space.map_output(&output, position);
|
2023-10-05 09:25:07 +04:00
|
|
|
self.layout.add_output(output.clone());
|
2023-10-07 21:02:37 +04:00
|
|
|
output.change_current_state(None, None, None, Some(position));
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
let state = OutputState {
|
2023-09-21 13:39:37 +04:00
|
|
|
global,
|
2023-09-30 17:13:56 +04:00
|
|
|
redraw_state: RedrawState::Idle,
|
|
|
|
|
unfinished_animations_remain: false,
|
2023-08-14 15:53:24 +04:00
|
|
|
frame_clock: FrameClock::new(refresh_interval),
|
2023-09-29 13:12:50 +04:00
|
|
|
current_estimated_sequence: None,
|
2023-08-13 12:46:53 +04:00
|
|
|
};
|
2023-10-03 17:02:07 +04:00
|
|
|
let rv = self.output_state.insert(output.clone(), state);
|
|
|
|
|
assert!(rv.is_none(), "output was already tracked");
|
|
|
|
|
let rv = self.output_by_name.insert(name, output);
|
2023-08-13 12:46:53 +04:00
|
|
|
assert!(rv.is_none(), "output was already tracked");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn remove_output(&mut self, output: &Output) {
|
2023-10-05 09:25:07 +04:00
|
|
|
self.layout.remove_output(output);
|
2023-09-21 13:39:37 +04:00
|
|
|
self.global_space.unmap_output(output);
|
|
|
|
|
// FIXME: reposition outputs so they are adjacent.
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-09-30 17:13:56 +04:00
|
|
|
let state = self.output_state.remove(output).unwrap();
|
2023-10-03 17:02:07 +04:00
|
|
|
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
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-09-21 13:39:37 +04:00
|
|
|
// Disable the output global and remove some time later to give the clients some time to
|
|
|
|
|
// process it.
|
|
|
|
|
let global = state.global;
|
|
|
|
|
self.display_handle.disable_global::<State>(global.clone());
|
|
|
|
|
self.event_loop
|
|
|
|
|
.insert_source(
|
|
|
|
|
Timer::from_duration(Duration::from_secs(10)),
|
2023-09-24 17:30:06 +04:00
|
|
|
move |_, _, state| {
|
|
|
|
|
state
|
2023-09-21 13:39:37 +04:00
|
|
|
.niri
|
|
|
|
|
.display_handle
|
|
|
|
|
.remove_global::<State>(global.clone());
|
|
|
|
|
TimeoutAction::Drop
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn output_resized(&mut self, output: Output) {
|
2023-08-15 12:49:26 +04:00
|
|
|
layer_map_for_output(&output).arrange();
|
2023-10-05 09:25:07 +04:00
|
|
|
self.layout.update_output_size(&output);
|
2023-08-13 12:46:53 +04:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
pub fn output_under(&self, pos: Point<f64, Logical>) -> Option<(&Output, Point<f64, Logical>)> {
|
|
|
|
|
let output = self.global_space.output_under(pos).next()?;
|
|
|
|
|
let pos_within_output = pos
|
|
|
|
|
- self
|
|
|
|
|
.global_space
|
|
|
|
|
.output_geometry(output)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.loc
|
|
|
|
|
.to_f64();
|
|
|
|
|
|
|
|
|
|
Some((output, pos_within_output))
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
pub fn window_under_cursor(&self) -> Option<&Window> {
|
|
|
|
|
let pos = self.seat.get_pointer().unwrap().current_location();
|
2023-08-13 12:46:53 +04:00
|
|
|
let (output, pos_within_output) = self.output_under(pos)?;
|
2023-10-05 09:25:07 +04:00
|
|
|
let (window, _loc) = self.layout.window_under(output, pos_within_output)?;
|
2023-08-13 19:55:37 +04:00
|
|
|
Some(window)
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
/// 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>,
|
|
|
|
|
) -> Option<(WlSurface, Point<i32, Logical>)> {
|
|
|
|
|
let (output, pos_within_output) = self.output_under(pos)?;
|
2023-08-13 19:55:37 +04:00
|
|
|
let (window, win_pos_within_output) =
|
2023-10-05 09:25:07 +04:00
|
|
|
self.layout.window_under(output, pos_within_output)?;
|
2023-08-13 19:55:37 +04:00
|
|
|
|
|
|
|
|
let (surface, surface_pos_within_output) = 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 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-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
Some((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> {
|
2023-10-05 09:25:07 +04:00
|
|
|
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> {
|
2023-10-05 09:25:07 +04:00
|
|
|
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> {
|
2023-10-05 09:25:07 +04:00
|
|
|
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> {
|
2023-10-05 09:25:07 +04:00
|
|
|
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-08-15 12:49:26 +04:00
|
|
|
fn layer_surface_focus(&self) -> Option<WlSurface> {
|
2023-10-05 09:25:07 +04:00
|
|
|
let output = self.layout.active_output()?;
|
2023-08-15 12:49:26 +04:00
|
|
|
let layers = layer_map_for_output(output);
|
|
|
|
|
let surface = layers
|
|
|
|
|
.layers_on(Layer::Overlay)
|
|
|
|
|
.chain(layers.layers_on(Layer::Top))
|
|
|
|
|
.find(|surface| surface.can_receive_keyboard_focus())?;
|
|
|
|
|
|
|
|
|
|
Some(surface.wl_surface().clone())
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
/// Schedules an immediate redraw on all outputs if one is not already scheduled.
|
|
|
|
|
pub fn queue_redraw_all(&mut self) {
|
|
|
|
|
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.
|
2023-08-13 12:46:53 +04:00
|
|
|
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-08-13 12:46:53 +04:00
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2023-08-15 16:19:05 +04:00
|
|
|
pub fn pointer_element(
|
|
|
|
|
&mut self,
|
|
|
|
|
renderer: &mut GlesRenderer,
|
|
|
|
|
output: &Output,
|
|
|
|
|
) -> Vec<OutputRenderElements<GlesRenderer>> {
|
2023-09-21 13:48:32 +04:00
|
|
|
let output_scale = Scale::from(output.current_scale().fractional_scale());
|
2023-08-15 16:08:37 +04:00
|
|
|
let output_pos = self.global_space.output_geometry(output).unwrap().loc;
|
|
|
|
|
let pointer_pos = self.seat.get_pointer().unwrap().current_location() - output_pos.to_f64();
|
|
|
|
|
|
2023-10-01 07:59:28 +04:00
|
|
|
let output_scale_int = output.current_scale().integer_scale();
|
|
|
|
|
let (default_buffer, default_hotspot) = self.default_cursor.get(renderer, output_scale_int);
|
|
|
|
|
let default_hotspot = default_hotspot.to_logical(output_scale_int);
|
2023-08-15 17:17:42 +04:00
|
|
|
|
2023-08-15 16:40:54 +04:00
|
|
|
let hotspot = if let CursorImageStatus::Surface(surface) = &mut self.cursor_image {
|
|
|
|
|
if surface.alive() {
|
|
|
|
|
with_states(surface, |states| {
|
2023-08-15 16:19:05 +04:00
|
|
|
states
|
|
|
|
|
.data_map
|
|
|
|
|
.get::<Mutex<CursorImageAttributes>>()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.lock()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.hotspot
|
2023-08-15 16:40:54 +04:00
|
|
|
})
|
|
|
|
|
} else {
|
2023-09-30 21:05:13 +04:00
|
|
|
self.cursor_image = CursorImageStatus::default_named();
|
2023-08-15 17:17:42 +04:00
|
|
|
default_hotspot
|
2023-08-15 16:40:54 +04:00
|
|
|
}
|
|
|
|
|
} else {
|
2023-08-15 17:17:42 +04:00
|
|
|
default_hotspot
|
2023-08-15 16:40:54 +04:00
|
|
|
};
|
2023-09-21 13:48:32 +04:00
|
|
|
let pointer_pos = (pointer_pos - hotspot.to_f64()).to_physical_precise_round(output_scale);
|
2023-08-15 16:19:05 +04:00
|
|
|
|
2023-08-15 16:40:54 +04:00
|
|
|
let mut pointer_elements = match &self.cursor_image {
|
|
|
|
|
CursorImageStatus::Hidden => vec![],
|
2023-09-30 21:05:13 +04:00
|
|
|
CursorImageStatus::Surface(surface) => render_elements_from_surface_tree(
|
|
|
|
|
renderer,
|
|
|
|
|
surface,
|
|
|
|
|
pointer_pos,
|
|
|
|
|
output_scale,
|
|
|
|
|
1.,
|
|
|
|
|
Kind::Cursor,
|
|
|
|
|
),
|
|
|
|
|
// Default shape catch-all
|
|
|
|
|
_ => vec![OutputRenderElements::DefaultPointer(
|
2023-08-15 17:17:42 +04:00
|
|
|
TextureRenderElement::from_texture_buffer(
|
|
|
|
|
pointer_pos.to_f64(),
|
2023-10-01 07:59:28 +04:00
|
|
|
&default_buffer,
|
2023-08-15 17:17:42 +04:00
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
2023-09-11 19:24:09 +04:00
|
|
|
Kind::Cursor,
|
2023-08-15 17:17:42 +04:00
|
|
|
),
|
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,
|
2023-09-21 13:48:32 +04:00
|
|
|
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-02 18:48:41 +04:00
|
|
|
pub fn refresh_pointer_outputs(&self) {
|
|
|
|
|
let _span = tracy_client::span!("Niri::refresh_pointer_outputs");
|
|
|
|
|
|
|
|
|
|
match &self.cursor_image {
|
|
|
|
|
CursorImageStatus::Hidden | CursorImageStatus::Named(_) => {
|
|
|
|
|
// There's no cursor surface, but there might be a DnD icon.
|
|
|
|
|
let Some(surface) = &self.dnd_icon else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let pointer_pos = self.seat.get_pointer().unwrap().current_location();
|
|
|
|
|
|
|
|
|
|
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 Some(hotspot) = self.default_cursor.get_cached_hotspot(output_scale) else {
|
|
|
|
|
// Oh well; it'll get cached next time we render.
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
let hotspot = hotspot.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;
|
|
|
|
|
output_update(output, Some(overlap), surface);
|
|
|
|
|
} else {
|
|
|
|
|
output_update(output, None, surface);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
CursorImageStatus::Surface(surface) => {
|
|
|
|
|
let hotspot = with_states(surface, |states| {
|
|
|
|
|
states
|
|
|
|
|
.data_map
|
|
|
|
|
.get::<Mutex<CursorImageAttributes>>()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.lock()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.hotspot
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let pointer_pos = self.seat.get_pointer().unwrap().current_location();
|
|
|
|
|
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)));
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
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;
|
|
|
|
|
output_update(output, Some(overlap), surface);
|
|
|
|
|
} else {
|
|
|
|
|
output_update(output, None, surface);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-27 17:44:22 +04:00
|
|
|
fn render(
|
|
|
|
|
&mut self,
|
|
|
|
|
renderer: &mut GlesRenderer,
|
|
|
|
|
output: &Output,
|
2023-09-20 09:22:39 +04:00
|
|
|
include_pointer: bool,
|
2023-08-27 17:44:22 +04:00
|
|
|
) -> Vec<OutputRenderElements<GlesRenderer>> {
|
|
|
|
|
let _span = tracy_client::span!("Niri::render");
|
2023-08-15 12:49:26 +04:00
|
|
|
|
2023-09-21 13:48:32 +04:00
|
|
|
let output_scale = Scale::from(output.current_scale().fractional_scale());
|
|
|
|
|
|
2023-08-15 12:49:26 +04:00
|
|
|
// Get monitor elements.
|
2023-10-05 09:25:07 +04:00
|
|
|
let mon = self.layout.monitor_for_output(output).unwrap();
|
2023-08-15 12:49:26 +04:00
|
|
|
let monitor_elements = mon.render_elements(renderer);
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-08-15 12:49:26 +04:00
|
|
|
// The pointer goes on the top.
|
2023-09-20 09:22:39 +04:00
|
|
|
let mut elements = vec![];
|
|
|
|
|
if include_pointer {
|
|
|
|
|
elements = self.pointer_element(renderer, output);
|
|
|
|
|
}
|
2023-08-15 12:49:26 +04:00
|
|
|
|
2023-09-27 08:50:00 +04:00
|
|
|
// Get layer-shell elements.
|
|
|
|
|
let layer_map = layer_map_for_output(output);
|
|
|
|
|
let mut extend_from_layer = |elements: &mut Vec<OutputRenderElements<GlesRenderer>>,
|
|
|
|
|
layer| {
|
|
|
|
|
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,
|
2023-09-21 13:48:32 +04:00
|
|
|
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);
|
|
|
|
|
// FIXME: hide top layer when a fullscreen surface is showing somehow.
|
|
|
|
|
extend_from_layer(&mut elements, Layer::Top);
|
2023-08-15 12:49:26 +04:00
|
|
|
|
|
|
|
|
// Then the regular monitor elements.
|
|
|
|
|
elements.extend(monitor_elements.into_iter().map(OutputRenderElements::from));
|
|
|
|
|
|
|
|
|
|
// 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-08-27 17:44:22 +04:00
|
|
|
elements
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-14 22:28:26 +04:00
|
|
|
fn redraw(&mut self, backend: &mut Backend, output: &Output) {
|
2023-08-27 17:44:22 +04:00
|
|
|
let _span = tracy_client::span!("Niri::redraw");
|
|
|
|
|
|
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(_)
|
|
|
|
|
));
|
|
|
|
|
|
2023-10-10 10:03:14 +04:00
|
|
|
let mut reset = || {
|
|
|
|
|
state.redraw_state =
|
|
|
|
|
if let RedrawState::WaitingForEstimatedVBlankAndQueued((token, _)) =
|
|
|
|
|
state.redraw_state
|
|
|
|
|
{
|
|
|
|
|
RedrawState::WaitingForEstimatedVBlank(token)
|
|
|
|
|
} else {
|
|
|
|
|
RedrawState::Idle
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-10-09 18:37:43 +04:00
|
|
|
if !self.monitors_active {
|
2023-10-10 10:03:14 +04:00
|
|
|
reset();
|
2023-10-09 18:37:43 +04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-26 10:42:56 +04:00
|
|
|
if !backend.is_active() {
|
2023-10-10 10:03:14 +04:00
|
|
|
reset();
|
2023-09-26 10:42:56 +04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-26 10:06:07 +04:00
|
|
|
let Some(renderer) = backend.renderer() else {
|
2023-10-10 10:03:14 +04:00
|
|
|
reset();
|
2023-09-26 10:06:07 +04:00
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
2023-09-30 17:13:56 +04:00
|
|
|
let presentation_time = state.frame_clock.next_presentation_time();
|
2023-08-27 17:44:22 +04:00
|
|
|
|
2023-09-26 13:09:33 +04:00
|
|
|
// Update from the config and advance the animations.
|
2023-10-05 09:25:07 +04:00
|
|
|
self.layout.advance_animations(presentation_time);
|
2023-09-30 17:13:56 +04:00
|
|
|
state.unfinished_animations_remain = self
|
2023-10-05 09:25:07 +04:00
|
|
|
.layout
|
2023-09-30 17:13:56 +04:00
|
|
|
.monitor_for_output(output)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.are_animations_ongoing();
|
2023-08-27 17:44:22 +04:00
|
|
|
|
|
|
|
|
// Render the elements.
|
2023-09-26 10:06:07 +04:00
|
|
|
let elements = self.render(renderer, output, true);
|
2023-08-16 10:59:34 +04:00
|
|
|
|
2023-08-15 12:49:26 +04:00
|
|
|
// Hand it over to the backend.
|
2023-09-20 11:51:25 +04:00
|
|
|
let dmabuf_feedback = backend.render(self, output, &elements, presentation_time);
|
2023-09-03 15:15:55 +04:00
|
|
|
|
|
|
|
|
// Send the dmabuf feedbacks.
|
|
|
|
|
if let Some(feedback) = dmabuf_feedback {
|
|
|
|
|
self.send_dmabuf_feedbacks(output, feedback);
|
|
|
|
|
}
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2023-08-27 17:44:22 +04:00
|
|
|
// Send the frame callbacks.
|
2023-10-01 19:41:42 +04:00
|
|
|
//
|
|
|
|
|
// FIXME: The logic here could be a bit smarter. Currently, during an animation, the
|
|
|
|
|
// surfaces that are visible for the very last frame (e.g. because the camera is moving
|
|
|
|
|
// away) will receive frame callbacks, and the surfaces that are invisible but will become
|
|
|
|
|
// visible next frame will not receive frame callbacks (so they will show stale contents for
|
|
|
|
|
// one frame). We could advance the animations for the next frame and send frame callbacks
|
|
|
|
|
// according to the expected new positions.
|
|
|
|
|
//
|
|
|
|
|
// However, this should probably be restricted to sending frame callbacks to more surfaces,
|
|
|
|
|
// to err on the safe side.
|
2023-08-27 17:44:22 +04:00
|
|
|
self.send_frame_callbacks(output);
|
2023-09-08 17:54:02 +04:00
|
|
|
|
|
|
|
|
// Render and send to PipeWire screencast streams.
|
2023-09-30 09:58:34 +04:00
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
|
|
|
|
{
|
|
|
|
|
let renderer = backend
|
|
|
|
|
.renderer()
|
|
|
|
|
.expect("renderer must not have disappeared");
|
|
|
|
|
self.send_for_screen_cast(renderer, output, &elements, presentation_time);
|
|
|
|
|
}
|
2023-08-27 17:44:22 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-01 19:41:42 +04:00
|
|
|
pub fn update_primary_scanout_output(
|
|
|
|
|
&self,
|
|
|
|
|
output: &Output,
|
|
|
|
|
render_element_states: &RenderElementStates,
|
|
|
|
|
) {
|
|
|
|
|
// FIXME: potentially tweak the compare function. The default one currently always prefers a
|
|
|
|
|
// higher refresh-rate output, which is not always desirable (i.e. with a very small
|
|
|
|
|
// overlap).
|
|
|
|
|
//
|
|
|
|
|
// While we only have cursors and DnD icons crossing output boundaries though, it doesn't
|
|
|
|
|
// matter all that much.
|
|
|
|
|
if let CursorImageStatus::Surface(surface) = &self.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.
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
for win in self.layout.windows_for_output(output) {
|
2023-10-01 19:41:42 +04:00
|
|
|
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-09-03 15:15:55 +04:00
|
|
|
fn send_dmabuf_feedbacks(&self, output: &Output, feedback: &DmabufFeedback) {
|
|
|
|
|
let _span = tracy_client::span!("Niri::send_dmabuf_feedbacks");
|
|
|
|
|
|
2023-10-01 19:41:42 +04:00
|
|
|
// We can unconditionally send the current output's feedback to regular and layer-shell
|
|
|
|
|
// surfaces, as they can only be displayed on a single output at a time. Even if a surface
|
|
|
|
|
// is currently invisible, this is the DMABUF feedback that it should know about.
|
2023-10-05 09:25:07 +04:00
|
|
|
for win in self.layout.windows_for_output(output) {
|
2023-10-01 19:41:42 +04:00
|
|
|
win.send_dmabuf_feedback(output, |_, _| Some(output.clone()), |_, _| feedback);
|
|
|
|
|
}
|
2023-09-03 15:15:55 +04:00
|
|
|
|
|
|
|
|
for surface in layer_map_for_output(output).layers() {
|
|
|
|
|
surface.send_dmabuf_feedback(output, |_, _| Some(output.clone()), |_, _| feedback);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(surface) = &self.dnd_icon {
|
|
|
|
|
send_dmabuf_feedback_surface_tree(
|
|
|
|
|
surface,
|
|
|
|
|
output,
|
2023-10-01 19:41:42 +04:00
|
|
|
surface_primary_scanout_output,
|
2023-09-03 15:15:55 +04:00
|
|
|
|_, _| feedback,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let CursorImageStatus::Surface(surface) = &self.cursor_image {
|
|
|
|
|
send_dmabuf_feedback_surface_tree(
|
|
|
|
|
surface,
|
|
|
|
|
output,
|
2023-10-01 19:41:42 +04:00
|
|
|
surface_primary_scanout_output,
|
2023-09-03 15:15:55 +04:00
|
|
|
|_, _| feedback,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-29 13:12:50 +04:00
|
|
|
pub fn send_frame_callbacks(&self, output: &Output) {
|
2023-08-27 17:44:22 +04:00
|
|
|
let _span = tracy_client::span!("Niri::send_frame_callbacks");
|
|
|
|
|
|
2023-09-29 13:12:50 +04:00
|
|
|
let state = self.output_state.get(output).unwrap();
|
|
|
|
|
let sequence = state.current_estimated_sequence;
|
|
|
|
|
|
2023-10-01 19:41:42 +04:00
|
|
|
let should_send = |surface: &WlSurface, states: &SurfaceData| {
|
|
|
|
|
// Do the standard primary scanout output check. For pointer surfaces it deduplicates
|
|
|
|
|
// the frame callbacks across potentially multiple outputs, and for regular windows and
|
|
|
|
|
// layer-shell surfaces it avoids sending frame callbacks to invisible surfaces.
|
|
|
|
|
let current_primary_output = surface_primary_scanout_output(surface, states);
|
|
|
|
|
if current_primary_output.as_ref() != Some(output) {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Next, check the throttling status.
|
2023-09-29 13:12:50 +04:00
|
|
|
let frame_throttling_state = states
|
|
|
|
|
.data_map
|
|
|
|
|
.get_or_insert(SurfaceFrameThrottlingState::default);
|
|
|
|
|
let mut last_sent_at = frame_throttling_state.last_sent_at.borrow_mut();
|
|
|
|
|
|
|
|
|
|
let mut send = true;
|
|
|
|
|
|
|
|
|
|
// If we already sent a frame callback to this surface this output refresh
|
|
|
|
|
// cycle, don't send one again to prevent empty-damage commit busy loops.
|
|
|
|
|
if let Some((last_output, last_sequence)) = &*last_sent_at {
|
|
|
|
|
if last_output == output && Some(*last_sequence) == sequence {
|
|
|
|
|
send = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if send {
|
|
|
|
|
if let Some(sequence) = sequence {
|
|
|
|
|
*last_sent_at = Some((output.clone(), sequence));
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-01 19:41:42 +04:00
|
|
|
Some(output.clone())
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
2023-09-29 13:12:50 +04:00
|
|
|
};
|
|
|
|
|
|
2023-09-04 14:27:16 +04:00
|
|
|
let frame_callback_time = get_monotonic_time();
|
2023-10-01 19:41:42 +04:00
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
for win in self.layout.windows_for_output(output) {
|
2023-10-01 19:41:42 +04:00
|
|
|
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() {
|
2023-10-01 19:41:42 +04:00
|
|
|
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
|
|
|
|
|
|
|
|
if let Some(surface) = &self.dnd_icon {
|
2023-10-01 19:41:42 +04:00
|
|
|
send_frames_surface_tree(surface, output, frame_callback_time, None, should_send);
|
2023-08-16 07:08:57 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let CursorImageStatus::Surface(surface) = &self.cursor_image {
|
2023-10-01 19:41:42 +04:00
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
if let CursorImageStatus::Surface(surface) = &self.cursor_image {
|
|
|
|
|
take_presentation_feedback_surface_tree(
|
|
|
|
|
surface,
|
|
|
|
|
&mut feedback,
|
2023-10-01 19:41:42 +04:00
|
|
|
surface_primary_scanout_output,
|
2023-08-16 10:59:34 +04:00
|
|
|
|surface, _| {
|
|
|
|
|
surface_presentation_feedback_flags_from_states(surface, render_element_states)
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(surface) = &self.dnd_icon {
|
|
|
|
|
take_presentation_feedback_surface_tree(
|
|
|
|
|
surface,
|
|
|
|
|
&mut feedback,
|
2023-10-01 19:41:42 +04:00
|
|
|
surface_primary_scanout_output,
|
2023-08-16 10:59:34 +04:00
|
|
|
|surface, _| {
|
|
|
|
|
surface_presentation_feedback_flags_from_states(surface, render_element_states)
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
for win in self.layout.windows_for_output(output) {
|
2023-08-16 10:59:34 +04:00
|
|
|
win.take_presentation_feedback(
|
|
|
|
|
&mut feedback,
|
2023-10-01 19:41:42 +04:00
|
|
|
surface_primary_scanout_output,
|
2023-08-16 10:59:34 +04:00
|
|
|
|surface, _| {
|
|
|
|
|
surface_presentation_feedback_flags_from_states(surface, render_element_states)
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for surface in layer_map_for_output(output).layers() {
|
|
|
|
|
surface.take_presentation_feedback(
|
|
|
|
|
&mut feedback,
|
2023-10-01 19:41:42 +04:00
|
|
|
surface_primary_scanout_output,
|
2023-08-16 10:59:34 +04:00
|
|
|
|surface, _| {
|
|
|
|
|
surface_presentation_feedback_flags_from_states(surface, render_element_states)
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
feedback
|
|
|
|
|
}
|
2023-08-27 19:34:37 +04:00
|
|
|
|
2023-09-30 09:58:34 +04:00
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
2023-09-08 17:54:02 +04:00
|
|
|
fn send_for_screen_cast(
|
2023-08-27 19:34:37 +04:00
|
|
|
&mut self,
|
2023-09-26 10:06:07 +04:00
|
|
|
renderer: &mut GlesRenderer,
|
2023-08-27 19:34:37 +04:00
|
|
|
output: &Output,
|
2023-09-08 17:54:02 +04:00
|
|
|
elements: &[OutputRenderElements<GlesRenderer>],
|
|
|
|
|
presentation_time: Duration,
|
|
|
|
|
) {
|
|
|
|
|
let _span = tracy_client::span!("Niri::send_for_screen_cast");
|
2023-08-27 19:34:37 +04:00
|
|
|
|
|
|
|
|
let size = output.current_mode().unwrap().size;
|
2023-09-21 13:48:32 +04:00
|
|
|
let scale = Scale::from(output.current_scale().fractional_scale());
|
2023-08-27 19:34:37 +04:00
|
|
|
|
2023-09-08 17:54:02 +04:00
|
|
|
for cast in &mut self.casts {
|
|
|
|
|
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() && presentation_time - last < min {
|
|
|
|
|
trace!(
|
|
|
|
|
"skipping frame because it is too soon \
|
|
|
|
|
last={last:?} now={presentation_time:?} diff={:?} < min={min:?}",
|
|
|
|
|
presentation_time - last,
|
|
|
|
|
);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
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
|
2023-09-26 10:06:07 +04:00
|
|
|
if let Err(err) = render_to_dmabuf(renderer, dmabuf, size, scale, elements) {
|
|
|
|
|
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 = presentation_time;
|
2023-08-27 19:34:37 +04:00
|
|
|
}
|
2023-09-08 17:54:02 +04:00
|
|
|
}
|
2023-08-27 19:34:37 +04:00
|
|
|
|
2023-10-10 08:49:47 +04:00
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
|
|
|
|
fn stop_cast(&mut self, session_id: usize) {
|
|
|
|
|
let _span = tracy_client::span!("Niri::stop_cast");
|
|
|
|
|
|
|
|
|
|
debug!(session_id, "StopCast");
|
|
|
|
|
|
|
|
|
|
for i in (0..self.casts.len()).rev() {
|
|
|
|
|
let cast = &self.casts[i];
|
|
|
|
|
if cast.session_id != session_id {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let cast = self.casts.swap_remove(i);
|
|
|
|
|
if let Err(err) = cast.stream.disconnect() {
|
|
|
|
|
warn!("error disconnecting stream: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-10 09:55:44 +04:00
|
|
|
let dbus = &self.dbus.as_ref().unwrap();
|
|
|
|
|
let server = dbus.conn_screen_cast.as_ref().unwrap().object_server();
|
2023-10-10 08:49:47 +04:00
|
|
|
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-09-08 17:54:02 +04:00
|
|
|
pub fn screenshot(
|
|
|
|
|
&mut self,
|
|
|
|
|
renderer: &mut GlesRenderer,
|
|
|
|
|
output: &Output,
|
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
|
let _span = tracy_client::span!("Niri::screenshot");
|
|
|
|
|
|
|
|
|
|
let size = output.current_mode().unwrap().size;
|
2023-09-21 13:48:32 +04:00
|
|
|
let scale = Scale::from(output.current_scale().fractional_scale());
|
2023-09-20 09:22:39 +04:00
|
|
|
let elements = self.render(renderer, output, true);
|
2023-09-21 13:48:32 +04:00
|
|
|
let pixels = render_to_vec(renderer, size, scale, &elements)?;
|
2023-08-27 19:34:37 +04:00
|
|
|
|
2023-09-19 19:05:03 +04:00
|
|
|
let path = make_screenshot_path().context("error making screenshot path")?;
|
2023-08-27 19:34:37 +04:00
|
|
|
debug!("saving screenshot to {path:?}");
|
|
|
|
|
|
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(),
|
|
|
|
|
);
|
|
|
|
|
set_primary_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![];
|
|
|
|
|
|
|
|
|
|
if let Err(err) = image::write_buffer_with_format(
|
|
|
|
|
&mut std::io::Cursor::new(&mut buf),
|
2023-08-27 19:34:37 +04:00
|
|
|
&pixels,
|
|
|
|
|
size.w as u32,
|
|
|
|
|
size.h as u32,
|
|
|
|
|
image::ColorType::Rgba8,
|
2023-10-10 12:11:05 +04:00
|
|
|
ImageFormat::Png,
|
2023-08-27 19:34:37 +04:00
|
|
|
) {
|
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());
|
|
|
|
|
|
|
|
|
|
if let Err(err) = std::fs::write(path, buf) {
|
2023-08-27 19:34:37 +04:00
|
|
|
warn!("error saving screenshot image: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2023-09-20 09:28:23 +04:00
|
|
|
|
2023-10-10 10:25:26 +04:00
|
|
|
#[cfg(feature = "dbus")]
|
2023-09-20 09:28:23 +04:00
|
|
|
pub fn screenshot_all_outputs(
|
|
|
|
|
&mut 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};
|
|
|
|
|
|
2023-09-20 09:28:23 +04:00
|
|
|
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();
|
2023-09-21 13:48:32 +04:00
|
|
|
// FIXME: this does not work when outputs can have non-1 scale.
|
2023-09-20 09:28:23 +04:00
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
let output_elements = self.render(renderer, &output, include_pointer);
|
|
|
|
|
elements.extend(output_elements.into_iter().map(|elem| {
|
|
|
|
|
RelocateRenderElement::from_element(elem, geom.loc, Relocate::Relative)
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-21 13:48:32 +04:00
|
|
|
// FIXME: scale.
|
|
|
|
|
let pixels = render_to_vec(renderer, size, Scale::from(1.), &elements)?;
|
2023-09-20 09:28:23 +04:00
|
|
|
|
|
|
|
|
let path = make_screenshot_path().context("error making screenshot path")?;
|
|
|
|
|
debug!("saving screenshot to {path:?}");
|
|
|
|
|
|
|
|
|
|
thread::spawn(move || {
|
|
|
|
|
let res = image::save_buffer(
|
|
|
|
|
&path,
|
|
|
|
|
&pixels,
|
|
|
|
|
size.w as u32,
|
|
|
|
|
size.h as u32,
|
|
|
|
|
image::ColorType::Rgba8,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if let Err(err) = res {
|
|
|
|
|
warn!("error saving screenshot image: {err:?}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
on_done(path);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-10 09:57:13 +04:00
|
|
|
render_elements! {
|
2023-08-13 19:55:37 +04:00
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub OutputRenderElements<R> where R: ImportAll;
|
2023-08-15 12:49:26 +04:00
|
|
|
Monitor = MonitorRenderElement<R>,
|
|
|
|
|
Wayland = WaylandSurfaceRenderElement<R>,
|
2023-08-15 17:17:42 +04:00
|
|
|
DefaultPointer = TextureRenderElement<<R as Renderer>::TextureId>,
|
2023-08-10 09:57:13 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-09 11:03:38 +04:00
|
|
|
#[derive(Default)]
|
|
|
|
|
pub struct ClientState {
|
|
|
|
|
pub compositor_state: CompositorClientState,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
fn render_and_download(
|
|
|
|
|
renderer: &mut GlesRenderer,
|
|
|
|
|
size: Size<i32, Physical>,
|
2023-09-21 13:48:32 +04:00
|
|
|
scale: Scale<f64>,
|
2023-09-20 09:22:15 +04:00
|
|
|
elements: &[impl RenderElement<GlesRenderer>],
|
2023-09-08 17:54:02 +04:00
|
|
|
) -> anyhow::Result<GlesMapping> {
|
|
|
|
|
let _span = tracy_client::span!("render_and_download");
|
|
|
|
|
|
|
|
|
|
let output_rect = Rectangle::from_loc_and_size((0, 0), size);
|
|
|
|
|
let buffer_size = size.to_logical(1).to_buffer(1, Transform::Normal);
|
|
|
|
|
let fourcc = Fourcc::Abgr8888;
|
|
|
|
|
|
|
|
|
|
let texture: GlesTexture = renderer
|
|
|
|
|
.create_buffer(fourcc, buffer_size)
|
|
|
|
|
.context("error creating texture")?;
|
|
|
|
|
|
|
|
|
|
renderer.bind(texture).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();
|
2023-09-21 13:48:32 +04:00
|
|
|
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")?;
|
|
|
|
|
sync_point.wait();
|
|
|
|
|
|
|
|
|
|
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>,
|
2023-09-21 13:48:32 +04:00
|
|
|
scale: Scale<f64>,
|
2023-09-20 09:22:15 +04:00
|
|
|
elements: &[impl RenderElement<GlesRenderer>],
|
2023-09-19 19:08:45 +04:00
|
|
|
) -> anyhow::Result<Vec<u8>> {
|
|
|
|
|
let _span = tracy_client::span!("render_to_vec");
|
|
|
|
|
|
2023-09-21 13:48:32 +04:00
|
|
|
let mapping =
|
|
|
|
|
render_and_download(renderer, size, scale, 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())
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-30 09:58:34 +04:00
|
|
|
#[cfg(feature = "xdp-gnome-screencast")]
|
2023-09-08 17:54:02 +04:00
|
|
|
fn render_to_dmabuf(
|
|
|
|
|
renderer: &mut GlesRenderer,
|
2023-09-30 09:58:34 +04:00
|
|
|
dmabuf: smithay::backend::allocator::dmabuf::Dmabuf,
|
2023-09-08 17:54:02 +04:00
|
|
|
size: Size<i32, Physical>,
|
2023-09-21 13:48:32 +04:00
|
|
|
scale: Scale<f64>,
|
2023-09-08 17:54:02 +04:00
|
|
|
elements: &[OutputRenderElements<GlesRenderer>],
|
|
|
|
|
) -> anyhow::Result<()> {
|
2023-09-30 09:58:34 +04:00
|
|
|
use smithay::backend::renderer::element::Element;
|
|
|
|
|
|
2023-09-08 17:54:02 +04:00
|
|
|
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")?;
|
|
|
|
|
|
|
|
|
|
frame
|
|
|
|
|
.clear([0.1, 0.1, 0.1, 1.], &[output_rect])
|
|
|
|
|
.context("error clearing")?;
|
|
|
|
|
|
|
|
|
|
for element in elements.iter().rev() {
|
|
|
|
|
let src = element.src();
|
2023-09-21 13:48:32 +04:00
|
|
|
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(())
|
|
|
|
|
}
|