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-08-09 11:03:38 +04:00
|
|
|
use std::os::unix::io::AsRawFd;
|
2023-08-27 07:37:36 +04:00
|
|
|
use std::process::Command;
|
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-03 13:25:43 +04:00
|
|
|
use std::{env, thread};
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2023-08-27 19:34:37 +04:00
|
|
|
use anyhow::Context;
|
2023-08-27 07:37:36 +04:00
|
|
|
use sd_notify::NotifyState;
|
2023-09-08 17:54:02 +04:00
|
|
|
use smithay::backend::allocator::dmabuf::Dmabuf;
|
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-08-15 17:17:42 +04:00
|
|
|
use smithay::backend::renderer::element::texture::{TextureBuffer, TextureRenderElement};
|
2023-08-27 19:34:37 +04:00
|
|
|
use smithay::backend::renderer::element::{
|
2023-09-11 19:24:09 +04:00
|
|
|
render_elements, AsRenderElements, Element, 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-09-03 15:15:55 +04:00
|
|
|
send_dmabuf_feedback_surface_tree, send_frames_surface_tree,
|
|
|
|
|
surface_presentation_feedback_flags_from_states, take_presentation_feedback_surface_tree,
|
|
|
|
|
OutputPresentationFeedback,
|
2023-08-16 10:59:34 +04:00
|
|
|
};
|
2023-08-15 12:49:26 +04:00
|
|
|
use smithay::desktop::{
|
|
|
|
|
layer_map_for_output, LayerSurface, 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-08 17:54:02 +04:00
|
|
|
use smithay::reexports::calloop::{self, Idle, Interest, LoopHandle, LoopSignal, Mode, PostAction};
|
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-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-08-15 16:19:05 +04:00
|
|
|
use smithay::wayland::compositor::{with_states, CompositorClientState, CompositorState};
|
2023-08-09 11:03:38 +04:00
|
|
|
use smithay::wayland::data_device::DataDeviceState;
|
2023-09-03 15:15:55 +04:00
|
|
|
use smithay::wayland::dmabuf::DmabufFeedback;
|
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-08-15 12:49:26 +04:00
|
|
|
use smithay::wayland::shell::wlr_layer::{Layer, WlrLayerShellState};
|
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-19 16:46:13 +04:00
|
|
|
use zbus::fdo::RequestNameFlags;
|
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-09-08 17:54:02 +04:00
|
|
|
use crate::dbus::mutter_display_config::DisplayConfig;
|
|
|
|
|
use crate::dbus::mutter_screen_cast::{self, ScreenCast, ToNiriMsg};
|
2023-08-27 10:29:06 +04:00
|
|
|
use crate::dbus::mutter_service_channel::ServiceChannel;
|
2023-08-14 15:53:24 +04:00
|
|
|
use crate::frame_clock::FrameClock;
|
2023-08-14 16:19:43 +04:00
|
|
|
use crate::layout::{MonitorRenderElement, MonitorSet};
|
2023-09-08 17:54:02 +04:00
|
|
|
use crate::pw_utils::{Cast, PipeWire};
|
2023-09-19 19:05:03 +04:00
|
|
|
use crate::utils::{center, get_monotonic_time, load_default_cursor, make_screenshot_path};
|
2023-09-03 14:10:02 +04:00
|
|
|
use crate::LoopData;
|
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-03 14:10:02 +04:00
|
|
|
pub event_loop: LoopHandle<'static, LoopData>,
|
2023-08-09 11:03:38 +04:00
|
|
|
pub stop_signal: LoopSignal,
|
|
|
|
|
pub display_handle: DisplayHandle,
|
|
|
|
|
|
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-08-13 19:55:37 +04:00
|
|
|
pub monitor_set: MonitorSet<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-08-09 11:03:38 +04:00
|
|
|
|
|
|
|
|
// Smithay state.
|
|
|
|
|
pub compositor_state: CompositorState,
|
|
|
|
|
pub xdg_shell_state: XdgShellState,
|
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-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-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-08-15 17:17:42 +04:00
|
|
|
pub pointer_buffer: Option<(TextureBuffer<GlesTexture>, Point<i32, Physical>)>,
|
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
|
|
|
|
|
|
|
|
pub zbus_conn: Option<zbus::blocking::Connection>,
|
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
|
|
|
pub screen_cast: ScreenCast,
|
|
|
|
|
|
|
|
|
|
// 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,
|
|
|
|
|
// Set if there's a redraw queued on the event loop. Reset in redraw() which means that you
|
|
|
|
|
// cannot queue more than one redraw at once.
|
|
|
|
|
pub queued_redraw: Option<Idle<'static>>,
|
|
|
|
|
// Set to `true` when the output was redrawn and is waiting for a VBlank. Upon VBlank a redraw
|
|
|
|
|
// will always be queued, so you cannot queue a redraw while waiting for a VBlank.
|
2023-08-10 09:58:26 +04:00
|
|
|
pub waiting_for_vblank: bool,
|
2023-08-14 15:53:24 +04:00
|
|
|
pub frame_clock: FrameClock,
|
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-03 14:10:02 +04:00
|
|
|
event_loop: LoopHandle<'static, LoopData>,
|
|
|
|
|
stop_signal: LoopSignal,
|
|
|
|
|
display: &mut Display<State>,
|
|
|
|
|
) -> Self {
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn move_cursor(&mut self, location: Point<f64, Logical>) {
|
|
|
|
|
let under = self.niri.surface_under_and_global_space(location);
|
|
|
|
|
self.niri.seat.get_pointer().unwrap().motion(
|
|
|
|
|
self,
|
|
|
|
|
under,
|
|
|
|
|
&MotionEvent {
|
|
|
|
|
location,
|
|
|
|
|
serial: SERIAL_COUNTER.next_serial(),
|
|
|
|
|
time: get_monotonic_time().as_millis() as u32,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
// 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
|
|
|
|
|
.monitor_set
|
|
|
|
|
.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-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-03 14:10:02 +04:00
|
|
|
event_loop: LoopHandle<'static, LoopData>,
|
2023-08-09 11:03:38 +04:00
|
|
|
stop_signal: LoopSignal,
|
2023-09-03 14:10:02 +04:00
|
|
|
display: &mut Display<State>,
|
2023-09-08 17:54:02 +04:00
|
|
|
backend: &Backend,
|
2023-08-09 11:03:38 +04:00
|
|
|
) -> Self {
|
|
|
|
|
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-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-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-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-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();
|
|
|
|
|
|
|
|
|
|
let socket_source = ListeningSocketSource::new_auto().unwrap();
|
|
|
|
|
let socket_name = socket_source.socket_name().to_os_string();
|
|
|
|
|
event_loop
|
|
|
|
|
.insert_source(socket_source, move |client, _, data| {
|
|
|
|
|
if let Err(err) = data
|
2023-09-03 14:10:02 +04:00
|
|
|
.state
|
2023-09-03 13:14:20 +04:00
|
|
|
.niri
|
2023-08-09 11:03:38 +04:00
|
|
|
.display_handle
|
|
|
|
|
.insert_client(client, Arc::new(ClientState::default()))
|
|
|
|
|
{
|
|
|
|
|
error!("error inserting client: {err}");
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
std::env::set_var("WAYLAND_DISPLAY", &socket_name);
|
|
|
|
|
info!(
|
|
|
|
|
"listening on Wayland socket: {}",
|
|
|
|
|
socket_name.to_string_lossy()
|
|
|
|
|
);
|
|
|
|
|
|
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 (to_niri, from_screen_cast) = calloop::channel::channel();
|
|
|
|
|
event_loop
|
|
|
|
|
.insert_source(from_screen_cast, {
|
|
|
|
|
let to_niri = to_niri.clone();
|
|
|
|
|
move |event, _, data| match event {
|
|
|
|
|
calloop::channel::Event::Msg(msg) => match msg {
|
|
|
|
|
ToNiriMsg::StartCast {
|
|
|
|
|
session_id,
|
|
|
|
|
output,
|
|
|
|
|
cursor_mode,
|
|
|
|
|
signal_ctx,
|
|
|
|
|
} => {
|
|
|
|
|
let _span = tracy_client::span!("StartCast");
|
|
|
|
|
|
|
|
|
|
debug!(session_id, "StartCast");
|
|
|
|
|
|
|
|
|
|
let gbm = match data.state.backend.gbm_device() {
|
|
|
|
|
Some(gbm) => gbm,
|
|
|
|
|
None => {
|
|
|
|
|
debug!("no GBM device available");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let pw = data.state.niri.pipewire.as_ref().unwrap();
|
|
|
|
|
match pw.start_cast(
|
|
|
|
|
to_niri.clone(),
|
|
|
|
|
gbm,
|
|
|
|
|
session_id,
|
|
|
|
|
output,
|
|
|
|
|
cursor_mode,
|
|
|
|
|
signal_ctx,
|
|
|
|
|
) {
|
|
|
|
|
Ok(cast) => {
|
|
|
|
|
data.state.niri.casts.push(cast);
|
|
|
|
|
}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
warn!("error starting screencast: {err:?}");
|
|
|
|
|
|
|
|
|
|
if let Err(err) =
|
|
|
|
|
to_niri.send(ToNiriMsg::StopCast { session_id })
|
|
|
|
|
{
|
|
|
|
|
warn!("error sending StopCast to niri: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ToNiriMsg::StopCast { session_id } => {
|
|
|
|
|
let _span = tracy_client::span!("StopCast");
|
|
|
|
|
|
|
|
|
|
debug!(session_id, "StopCast");
|
|
|
|
|
|
|
|
|
|
for i in (0..data.state.niri.casts.len()).rev() {
|
|
|
|
|
let cast = &data.state.niri.casts[i];
|
|
|
|
|
if cast.session_id != session_id {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let cast = data.state.niri.casts.swap_remove(i);
|
|
|
|
|
if let Err(err) = cast.stream.disconnect() {
|
|
|
|
|
warn!("error disconnecting stream: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let server =
|
|
|
|
|
data.state.niri.zbus_conn.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
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
calloop::channel::Event::Closed => (),
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
let screen_cast = ScreenCast::new(backend.connectors(), to_niri);
|
|
|
|
|
|
2023-08-27 10:29:06 +04:00
|
|
|
let mut zbus_conn = None;
|
2023-09-03 10:28:00 +04:00
|
|
|
let mut inhibit_power_key_fd = None;
|
2023-08-27 07:37:36 +04:00
|
|
|
if std::env::var_os("NOTIFY_SOCKET").is_some() {
|
|
|
|
|
// We're starting as a systemd service. Export our variables and tell systemd we're
|
|
|
|
|
// ready.
|
|
|
|
|
let rv = Command::new("/bin/sh")
|
|
|
|
|
.args([
|
|
|
|
|
"-c",
|
|
|
|
|
"systemctl --user import-environment WAYLAND_DISPLAY && \
|
|
|
|
|
hash dbus-update-activation-environment 2>/dev/null && \
|
|
|
|
|
dbus-update-activation-environment WAYLAND_DISPLAY",
|
|
|
|
|
])
|
|
|
|
|
.spawn();
|
2023-08-27 11:26:47 +04:00
|
|
|
// Wait for the import process to complete, otherwise services will start too fast
|
|
|
|
|
// without environment variables available.
|
|
|
|
|
match rv {
|
|
|
|
|
Ok(mut child) => match child.wait() {
|
|
|
|
|
Ok(status) => {
|
|
|
|
|
if !status.success() {
|
|
|
|
|
warn!("import environment shell exited with {status}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
warn!("error waiting for import environment shell: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
Err(err) => {
|
|
|
|
|
warn!("error spawning shell to import environment into systemd: {err:?}");
|
|
|
|
|
}
|
2023-08-27 07:37:36 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-27 10:29:06 +04:00
|
|
|
// Set up zbus, make sure it happens before anything might want it.
|
2023-09-19 16:46:13 +04:00
|
|
|
let conn = zbus::blocking::ConnectionBuilder::session()
|
2023-08-27 10:29:06 +04:00
|
|
|
.unwrap()
|
|
|
|
|
.name("org.gnome.Mutter.ServiceChannel")
|
|
|
|
|
.unwrap()
|
|
|
|
|
.serve_at(
|
|
|
|
|
"/org/gnome/Mutter/ServiceChannel",
|
|
|
|
|
ServiceChannel::new(display_handle.clone()),
|
|
|
|
|
)
|
2023-09-19 16:46:13 +04:00
|
|
|
.unwrap()
|
|
|
|
|
.build()
|
2023-08-27 10:29:06 +04:00
|
|
|
.unwrap();
|
2023-09-08 17:54:02 +04:00
|
|
|
|
2023-09-19 16:46:13 +04:00
|
|
|
if pipewire.is_some() {
|
|
|
|
|
let server = conn.object_server();
|
|
|
|
|
server
|
|
|
|
|
.at("/org/gnome/Mutter/ScreenCast", screen_cast.clone())
|
|
|
|
|
.unwrap();
|
|
|
|
|
server
|
|
|
|
|
.at(
|
2023-09-08 17:54:02 +04:00
|
|
|
"/org/gnome/Mutter/DisplayConfig",
|
|
|
|
|
DisplayConfig::new(backend.connectors()),
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
2023-09-19 16:46:13 +04:00
|
|
|
|
|
|
|
|
let flags = RequestNameFlags::AllowReplacement
|
|
|
|
|
| RequestNameFlags::ReplaceExisting
|
|
|
|
|
| RequestNameFlags::DoNotQueue;
|
|
|
|
|
conn.request_name_with_flags("org.gnome.Mutter.ScreenCast", flags)
|
|
|
|
|
.unwrap();
|
|
|
|
|
conn.request_name_with_flags("org.gnome.Mutter.DisplayConfig", flags)
|
|
|
|
|
.unwrap();
|
2023-09-08 17:54:02 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-27 10:29:06 +04:00
|
|
|
zbus_conn = Some(conn);
|
|
|
|
|
|
2023-08-27 07:37:36 +04:00
|
|
|
// Notify systemd we're ready.
|
2023-08-27 10:33:58 +04:00
|
|
|
if let Err(err) = sd_notify::notify(true, &[NotifyState::Ready]) {
|
2023-08-27 07:37:36 +04:00
|
|
|
warn!("error notifying systemd: {err:?}");
|
|
|
|
|
};
|
2023-09-03 10:28:00 +04:00
|
|
|
|
|
|
|
|
// Inhibit power key handling so we can suspend on it.
|
|
|
|
|
let zbus_system_conn = zbus::blocking::ConnectionBuilder::system()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.build()
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
// logind-zbus has a wrong signature for this method, so do it manually.
|
|
|
|
|
// https://gitlab.com/flukejones/logind-zbus/-/merge_requests/5
|
|
|
|
|
let message = zbus_system_conn
|
|
|
|
|
.call_method(
|
|
|
|
|
Some("org.freedesktop.login1"),
|
|
|
|
|
"/org/freedesktop/login1",
|
|
|
|
|
Some("org.freedesktop.login1.Manager"),
|
|
|
|
|
"Inhibit",
|
|
|
|
|
&("handle-power-key", "niri", "Power key handling", "block"),
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
match message.body() {
|
|
|
|
|
Ok(fd) => {
|
|
|
|
|
inhibit_power_key_fd = Some(fd);
|
|
|
|
|
}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
warn!("error inhibiting power key: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-09-14 22:28:26 +04:00
|
|
|
} else if pipewire.is_some() && config_.debug.screen_cast_in_non_session_instances {
|
2023-09-19 16:46:13 +04:00
|
|
|
let conn = zbus::blocking::Connection::session().unwrap();
|
|
|
|
|
{
|
|
|
|
|
let server = conn.object_server();
|
|
|
|
|
server
|
|
|
|
|
.at("/org/gnome/Mutter/ScreenCast", screen_cast.clone())
|
|
|
|
|
.unwrap();
|
|
|
|
|
server
|
|
|
|
|
.at(
|
|
|
|
|
"/org/gnome/Mutter/DisplayConfig",
|
|
|
|
|
DisplayConfig::new(backend.connectors()),
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let flags = RequestNameFlags::AllowReplacement
|
|
|
|
|
| RequestNameFlags::ReplaceExisting
|
|
|
|
|
| RequestNameFlags::DoNotQueue;
|
|
|
|
|
conn.request_name_with_flags("org.gnome.Mutter.ScreenCast", flags)
|
|
|
|
|
.unwrap();
|
|
|
|
|
conn.request_name_with_flags("org.gnome.Mutter.DisplayConfig", flags)
|
2023-09-08 17:54:02 +04:00
|
|
|
.unwrap();
|
2023-09-19 16:46:13 +04:00
|
|
|
|
2023-09-08 17:54:02 +04:00
|
|
|
zbus_conn = Some(conn);
|
2023-08-27 07:37:36 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-09 11:03:38 +04:00
|
|
|
let display_source = Generic::new(
|
|
|
|
|
display.backend().poll_fd().as_raw_fd(),
|
|
|
|
|
Interest::READ,
|
|
|
|
|
Mode::Level,
|
|
|
|
|
);
|
|
|
|
|
event_loop
|
|
|
|
|
.insert_source(display_source, |_, _, data| {
|
2023-09-03 14:10:02 +04:00
|
|
|
data.display.dispatch_clients(&mut data.state).unwrap();
|
2023-08-09 11:03:38 +04:00
|
|
|
Ok(PostAction::Continue)
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
2023-09-14 22:28:26 +04:00
|
|
|
drop(config_);
|
2023-08-09 11:03:38 +04:00
|
|
|
Self {
|
2023-09-14 22:28:26 +04:00
|
|
|
config,
|
|
|
|
|
|
2023-08-09 11:03:38 +04:00
|
|
|
event_loop,
|
|
|
|
|
stop_signal,
|
|
|
|
|
display_handle,
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
monitor_set: MonitorSet::new(),
|
|
|
|
|
global_space: Space::default(),
|
|
|
|
|
output_state: HashMap::new(),
|
|
|
|
|
unmapped_windows: HashMap::new(),
|
2023-08-09 11:03:38 +04:00
|
|
|
|
|
|
|
|
compositor_state,
|
|
|
|
|
xdg_shell_state,
|
2023-08-15 12:49:26 +04:00
|
|
|
layer_shell_state,
|
2023-08-09 11:03:38 +04:00
|
|
|
shm_state,
|
|
|
|
|
output_manager_state,
|
|
|
|
|
seat_state,
|
2023-08-16 11:43:52 +04:00
|
|
|
tablet_state,
|
2023-09-02 15:09:07 +04:00
|
|
|
pointer_gestures_state,
|
2023-08-09 11:03:38 +04:00
|
|
|
data_device_state,
|
2023-08-11 08:22:34 +04:00
|
|
|
popups: PopupManager::default(),
|
2023-08-16 10:59:34 +04:00
|
|
|
presentation_state,
|
2023-08-09 11:03:38 +04:00
|
|
|
|
|
|
|
|
seat,
|
2023-08-15 17:17:42 +04:00
|
|
|
pointer_buffer: None,
|
2023-08-15 16:19:05 +04:00
|
|
|
cursor_image: CursorImageStatus::Default,
|
2023-08-15 16:40:54 +04:00
|
|
|
dnd_icon: None,
|
2023-08-27 10:29:06 +04:00
|
|
|
|
|
|
|
|
zbus_conn,
|
2023-09-03 10:28:00 +04:00
|
|
|
inhibit_power_key_fd,
|
2023-09-08 17:54:02 +04:00
|
|
|
screen_cast,
|
|
|
|
|
pipewire,
|
|
|
|
|
casts: vec![],
|
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-08-13 12:46:53 +04:00
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
self.global_space.map_output(&output, (x, 0));
|
|
|
|
|
self.monitor_set.add_output(output.clone());
|
|
|
|
|
|
|
|
|
|
let state = OutputState {
|
2023-09-03 14:10:02 +04:00
|
|
|
global: output.create_global::<State>(&self.display_handle),
|
2023-08-13 12:46:53 +04:00
|
|
|
queued_redraw: None,
|
2023-08-10 09:58:26 +04:00
|
|
|
waiting_for_vblank: false,
|
2023-08-14 15:53:24 +04:00
|
|
|
frame_clock: FrameClock::new(refresh_interval),
|
2023-08-13 12:46:53 +04:00
|
|
|
};
|
|
|
|
|
let rv = self.output_state.insert(output, state);
|
|
|
|
|
assert!(rv.is_none(), "output was already tracked");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn remove_output(&mut self, output: &Output) {
|
|
|
|
|
let mut state = self.output_state.remove(output).unwrap();
|
2023-09-03 15:15:55 +04:00
|
|
|
self.display_handle.remove_global::<State>(state.global);
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
if let Some(idle) = state.queued_redraw.take() {
|
|
|
|
|
idle.cancel();
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
self.monitor_set.remove_output(output);
|
|
|
|
|
self.global_space.unmap_output(output);
|
|
|
|
|
// FIXME: reposition outputs so they are adjacent.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn output_resized(&mut self, output: Output) {
|
2023-08-13 19:55:37 +04:00
|
|
|
self.monitor_set.update_output(&output);
|
2023-08-15 12:49:26 +04:00
|
|
|
layer_map_for_output(&output).arrange();
|
2023-08-13 12:46:53 +04:00
|
|
|
self.queue_redraw(output);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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-08-13 19:55:37 +04:00
|
|
|
let (window, _loc) = self.monitor_set.window_under(output, pos_within_output)?;
|
|
|
|
|
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) =
|
|
|
|
|
self.monitor_set.window_under(output, pos_within_output)?;
|
|
|
|
|
|
|
|
|
|
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> {
|
|
|
|
|
let active = self.monitor_set.active_output()?;
|
|
|
|
|
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.monitor_set.active_output()?;
|
|
|
|
|
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.monitor_set.active_output()?;
|
|
|
|
|
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.monitor_set.active_output()?;
|
|
|
|
|
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-08-15 12:49:26 +04:00
|
|
|
fn layer_surface_focus(&self) -> Option<WlSurface> {
|
|
|
|
|
let output = self.monitor_set.active_output()?;
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
if state.queued_redraw.is_some() || state.waiting_for_vblank {
|
2023-08-10 09:58:26 +04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-10 14:06:32 +04:00
|
|
|
// Timer::immediate() adds a millisecond of delay for some reason.
|
|
|
|
|
// This should be fixed in calloop v0.11: https://github.com/Smithay/calloop/issues/142
|
2023-08-13 12:46:53 +04:00
|
|
|
let idle = self.event_loop.insert_idle(move |data| {
|
2023-09-14 22:28:26 +04:00
|
|
|
data.state.niri.redraw(&mut data.state.backend, &output);
|
2023-08-10 14:06:32 +04:00
|
|
|
});
|
2023-08-13 12:46:53 +04:00
|
|
|
state.queued_redraw = Some(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-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-08-15 17:17:42 +04:00
|
|
|
let (default_buffer, default_hotspot) = self
|
|
|
|
|
.pointer_buffer
|
|
|
|
|
.get_or_insert_with(|| load_default_cursor(renderer));
|
|
|
|
|
let default_hotspot = default_hotspot.to_logical(1);
|
|
|
|
|
|
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 {
|
|
|
|
|
self.cursor_image = CursorImageStatus::Default;
|
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
|
|
|
};
|
|
|
|
|
let pointer_pos = (pointer_pos - hotspot.to_f64()).to_physical_precise_round(1.);
|
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![],
|
|
|
|
|
CursorImageStatus::Default => vec![OutputRenderElements::DefaultPointer(
|
2023-08-15 17:17:42 +04:00
|
|
|
TextureRenderElement::from_texture_buffer(
|
|
|
|
|
pointer_pos.to_f64(),
|
|
|
|
|
default_buffer,
|
|
|
|
|
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
|
|
|
)],
|
2023-09-11 19:24:09 +04:00
|
|
|
CursorImageStatus::Surface(surface) => render_elements_from_surface_tree(
|
|
|
|
|
renderer,
|
|
|
|
|
surface,
|
|
|
|
|
pointer_pos,
|
|
|
|
|
1.,
|
|
|
|
|
1.,
|
|
|
|
|
Kind::Cursor,
|
|
|
|
|
),
|
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,
|
|
|
|
|
1.,
|
|
|
|
|
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-08-27 17:44:22 +04:00
|
|
|
fn render(
|
|
|
|
|
&mut self,
|
|
|
|
|
renderer: &mut GlesRenderer,
|
|
|
|
|
output: &Output,
|
|
|
|
|
) -> Vec<OutputRenderElements<GlesRenderer>> {
|
|
|
|
|
let _span = tracy_client::span!("Niri::render");
|
2023-08-15 12:49:26 +04:00
|
|
|
|
|
|
|
|
// Get monitor elements.
|
2023-08-27 17:44:22 +04:00
|
|
|
let mon = self.monitor_set.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
|
|
|
// Get layer-shell elements.
|
|
|
|
|
let layer_map = layer_map_for_output(output);
|
|
|
|
|
let (lower, upper): (Vec<&LayerSurface>, Vec<&LayerSurface>) = layer_map
|
|
|
|
|
.layers()
|
|
|
|
|
.rev()
|
|
|
|
|
.partition(|s| matches!(s.layer(), Layer::Background | Layer::Bottom));
|
|
|
|
|
|
|
|
|
|
// The pointer goes on the top.
|
2023-08-15 16:19:05 +04:00
|
|
|
let mut elements = self.pointer_element(renderer, output);
|
2023-08-15 12:49:26 +04:00
|
|
|
|
|
|
|
|
// Then the upper layer-shell elements.
|
|
|
|
|
elements.extend(
|
|
|
|
|
upper
|
|
|
|
|
.into_iter()
|
|
|
|
|
.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(1.),
|
|
|
|
|
Scale::from(1.),
|
|
|
|
|
1.,
|
|
|
|
|
)
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(OutputRenderElements::Wayland)
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Then the regular monitor elements.
|
|
|
|
|
elements.extend(monitor_elements.into_iter().map(OutputRenderElements::from));
|
|
|
|
|
|
|
|
|
|
// Then the lower layer-shell elements.
|
|
|
|
|
elements.extend(
|
|
|
|
|
lower
|
|
|
|
|
.into_iter()
|
|
|
|
|
.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(1.),
|
|
|
|
|
Scale::from(1.),
|
|
|
|
|
1.,
|
|
|
|
|
)
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(OutputRenderElements::Wayland)
|
|
|
|
|
}),
|
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");
|
|
|
|
|
|
|
|
|
|
let state = self.output_state.get_mut(output).unwrap();
|
|
|
|
|
let presentation_time = state.frame_clock.next_presentation_time();
|
|
|
|
|
|
|
|
|
|
assert!(state.queued_redraw.take().is_some());
|
|
|
|
|
assert!(!state.waiting_for_vblank);
|
|
|
|
|
|
|
|
|
|
// Advance the animations.
|
|
|
|
|
let mon = self.monitor_set.monitor_for_output_mut(output).unwrap();
|
|
|
|
|
mon.advance_animations(presentation_time);
|
|
|
|
|
|
|
|
|
|
// Render the elements.
|
|
|
|
|
let elements = self.render(backend.renderer(), output);
|
2023-08-16 10:59:34 +04:00
|
|
|
|
2023-08-15 12:49:26 +04:00
|
|
|
// Hand it over to the backend.
|
2023-09-14 22:28:26 +04:00
|
|
|
let dmabuf_feedback = backend.render(self, output, &elements);
|
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.
|
|
|
|
|
self.send_frame_callbacks(output);
|
2023-09-08 17:54:02 +04:00
|
|
|
|
|
|
|
|
// Render and send to PipeWire screencast streams.
|
|
|
|
|
self.send_for_screen_cast(backend, output, &elements, presentation_time);
|
2023-08-27 17:44:22 +04:00
|
|
|
}
|
|
|
|
|
|
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");
|
|
|
|
|
|
|
|
|
|
self.monitor_set.send_dmabuf_feedback(output, feedback);
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
|_, _| Some(output.clone()),
|
|
|
|
|
|_, _| feedback,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let CursorImageStatus::Surface(surface) = &self.cursor_image {
|
|
|
|
|
send_dmabuf_feedback_surface_tree(
|
|
|
|
|
surface,
|
|
|
|
|
output,
|
|
|
|
|
|_, _| Some(output.clone()),
|
|
|
|
|
|_, _| feedback,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-27 17:44:22 +04:00
|
|
|
fn send_frame_callbacks(&self, output: &Output) {
|
|
|
|
|
let _span = tracy_client::span!("Niri::send_frame_callbacks");
|
|
|
|
|
|
2023-09-04 14:27:16 +04:00
|
|
|
let frame_callback_time = get_monotonic_time();
|
2023-08-15 12:49:26 +04:00
|
|
|
self.monitor_set.send_frame(output, frame_callback_time);
|
|
|
|
|
|
2023-08-16 10:59:34 +04:00
|
|
|
for surface in layer_map_for_output(output).layers() {
|
2023-08-15 12:49:26 +04:00
|
|
|
surface.send_frame(output, frame_callback_time, None, |_, _| {
|
|
|
|
|
Some(output.clone())
|
|
|
|
|
});
|
|
|
|
|
}
|
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, |_, _| {
|
|
|
|
|
Some(output.clone())
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let CursorImageStatus::Surface(surface) = &self.cursor_image {
|
|
|
|
|
send_frames_surface_tree(surface, output, frame_callback_time, None, |_, _| {
|
|
|
|
|
Some(output.clone())
|
|
|
|
|
});
|
|
|
|
|
}
|
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,
|
|
|
|
|
|_, _| Some(output.clone()),
|
|
|
|
|
|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,
|
|
|
|
|
|_, _| Some(output.clone()),
|
|
|
|
|
|surface, _| {
|
|
|
|
|
surface_presentation_feedback_flags_from_states(surface, render_element_states)
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for win in self.monitor_set.windows_for_output(output) {
|
|
|
|
|
win.take_presentation_feedback(
|
|
|
|
|
&mut feedback,
|
|
|
|
|
|_, _| Some(output.clone()),
|
|
|
|
|
|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,
|
|
|
|
|
|_, _| Some(output.clone()),
|
|
|
|
|
|surface, _| {
|
|
|
|
|
surface_presentation_feedback_flags_from_states(surface, render_element_states)
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
feedback
|
|
|
|
|
}
|
2023-08-27 19:34:37 +04:00
|
|
|
|
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-08 17:54:02 +04:00
|
|
|
backend: &mut Backend,
|
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-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
|
|
|
|
|
render_to_dmabuf(backend.renderer(), dmabuf, size, elements).unwrap();
|
|
|
|
|
|
|
|
|
|
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-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;
|
|
|
|
|
let elements = self.render(renderer, output);
|
2023-08-27 19:34:37 +04:00
|
|
|
|
2023-09-08 17:54:02 +04:00
|
|
|
let mapping = render_and_download(renderer, size, &elements).context("error rendering")?;
|
2023-08-27 19:34:37 +04:00
|
|
|
let copy = renderer
|
|
|
|
|
.map_texture(&mapping)
|
|
|
|
|
.context("error mapping texture")?;
|
|
|
|
|
let pixels = copy.to_vec();
|
|
|
|
|
|
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:?}");
|
|
|
|
|
|
|
|
|
|
thread::spawn(move || {
|
|
|
|
|
if let Err(err) = image::save_buffer(
|
|
|
|
|
path,
|
|
|
|
|
&pixels,
|
|
|
|
|
size.w as u32,
|
|
|
|
|
size.h as u32,
|
|
|
|
|
image::ColorType::Rgba8,
|
|
|
|
|
) {
|
|
|
|
|
warn!("error saving screenshot image: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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>,
|
|
|
|
|
elements: &[OutputRenderElements<GlesRenderer>],
|
|
|
|
|
) -> 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")?;
|
|
|
|
|
|
|
|
|
|
frame
|
|
|
|
|
.clear([0.1, 0.1, 0.1, 1.], &[output_rect])
|
|
|
|
|
.context("error clearing")?;
|
|
|
|
|
|
|
|
|
|
for element in elements.iter().rev() {
|
|
|
|
|
let src = element.src();
|
|
|
|
|
let dst = element.geometry(Scale::from(1.));
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn render_to_dmabuf(
|
|
|
|
|
renderer: &mut GlesRenderer,
|
|
|
|
|
dmabuf: Dmabuf,
|
|
|
|
|
size: Size<i32, Physical>,
|
|
|
|
|
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")?;
|
|
|
|
|
|
|
|
|
|
frame
|
|
|
|
|
.clear([0.1, 0.1, 0.1, 1.], &[output_rect])
|
|
|
|
|
.context("error clearing")?;
|
|
|
|
|
|
|
|
|
|
for element in elements.iter().rev() {
|
|
|
|
|
let src = element.src();
|
|
|
|
|
let dst = element.geometry(Scale::from(1.));
|
|
|
|
|
element
|
|
|
|
|
.draw(&mut frame, src, dst, &[output_rect])
|
|
|
|
|
.context("error drawing element")?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let _sync_point = frame.finish().context("error finishing frame")?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|