aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/mod.rs19
-rw-r--r--src/backend/tty.rs24
-rw-r--r--src/backend/winit.rs13
-rw-r--r--src/config.rs4
-rw-r--r--src/dbus/mod.rs2
-rw-r--r--src/dbus/mutter_display_config.rs88
-rw-r--r--src/dbus/mutter_screen_cast.rs256
-rw-r--r--src/main.rs1
-rw-r--r--src/niri.rs329
-rw-r--r--src/pw_utils.rs382
10 files changed, 1069 insertions, 49 deletions
diff --git a/src/backend/mod.rs b/src/backend/mod.rs
index 8a59e6f3..8473ccc2 100644
--- a/src/backend/mod.rs
+++ b/src/backend/mod.rs
@@ -1,3 +1,8 @@
+use std::collections::HashMap;
+use std::sync::{Arc, Mutex};
+
+use smithay::backend::allocator::gbm::GbmDevice;
+use smithay::backend::drm::DrmDeviceFd;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::output::Output;
use smithay::wayland::dmabuf::DmabufFeedback;
@@ -79,6 +84,20 @@ impl Backend {
}
}
+ pub fn connectors(&self) -> Arc<Mutex<HashMap<String, Output>>> {
+ match self {
+ Backend::Tty(tty) => tty.connectors(),
+ Backend::Winit(winit) => winit.connectors(),
+ }
+ }
+
+ pub fn gbm_device(&self) -> Option<GbmDevice<DrmDeviceFd>> {
+ match self {
+ Backend::Tty(tty) => tty.gbm_device(),
+ Backend::Winit(_) => None,
+ }
+ }
+
pub fn tty(&mut self) -> &mut Tty {
if let Self::Tty(v) = self {
v
diff --git a/src/backend/tty.rs b/src/backend/tty.rs
index a3298655..343da54e 100644
--- a/src/backend/tty.rs
+++ b/src/backend/tty.rs
@@ -1,6 +1,7 @@
use std::collections::{HashMap, HashSet};
use std::os::fd::FromRawFd;
use std::path::{Path, PathBuf};
+use std::sync::{Mutex, Arc};
use std::time::Duration;
use anyhow::{anyhow, Context};
@@ -44,6 +45,7 @@ pub struct Tty {
udev_dispatcher: Dispatcher<'static, UdevBackend, LoopData>,
primary_gpu_path: PathBuf,
output_device: Option<OutputDevice>,
+ connectors: Arc<Mutex<HashMap<String, Output>>>,
}
type GbmDrmCompositor = DrmCompositor<
@@ -236,6 +238,7 @@ impl Tty {
udev_dispatcher,
primary_gpu_path,
output_device: None,
+ connectors: Arc::new(Mutex::new(HashMap::new())),
}
}
@@ -549,6 +552,11 @@ impl Tty {
tracy_client::internal::create_frame_name(format!("vblank on {output_name}\0").leak())
};
+ self.connectors
+ .lock()
+ .unwrap()
+ .insert(output_name.clone(), output.clone());
+
let surface = Surface {
name: output_name,
compositor,
@@ -574,10 +582,10 @@ impl Tty {
debug!("disconnecting connector: {connector:?}");
let device = self.output_device.as_mut().unwrap();
- if device.surfaces.remove(&crtc).is_none() {
- debug!("crts wasn't enabled");
+ let Some(surface) = device.surfaces.remove(&crtc) else {
+ debug!("crtc wasn't enabled");
return;
- }
+ };
let output = niri
.global_space
@@ -590,6 +598,8 @@ impl Tty {
.clone();
niri.remove_output(&output);
+
+ self.connectors.lock().unwrap().remove(&surface.name);
}
pub fn seat_name(&self) -> String {
@@ -680,6 +690,14 @@ impl Tty {
pub fn dmabuf_state(&mut self) -> &mut DmabufState {
&mut self.output_device.as_mut().unwrap().dmabuf_state
}
+
+ pub fn connectors(&self) -> Arc<Mutex<HashMap<String, Output>>> {
+ self.connectors.clone()
+ }
+
+ pub fn gbm_device(&self) -> Option<GbmDevice<DrmDeviceFd>> {
+ self.output_device.as_ref().map(|d| d.gbm.clone())
+ }
}
fn refresh_interval(mode: DrmMode) -> Duration {
diff --git a/src/backend/winit.rs b/src/backend/winit.rs
index be8ec29a..e7a50ac7 100644
--- a/src/backend/winit.rs
+++ b/src/backend/winit.rs
@@ -1,3 +1,5 @@
+use std::collections::HashMap;
+use std::sync::{Arc, Mutex};
use std::time::Duration;
use smithay::backend::renderer::damage::OutputDamageTracker;
@@ -21,6 +23,7 @@ pub struct Winit {
output: Output,
backend: WinitGraphicsBackend<GlesRenderer>,
damage_tracker: OutputDamageTracker,
+ connectors: Arc<Mutex<HashMap<String, Output>>>,
}
impl Winit {
@@ -53,6 +56,11 @@ impl Winit {
);
output.set_preferred(mode);
+ let connectors = Arc::new(Mutex::new(HashMap::from([(
+ "winit".to_owned(),
+ output.clone(),
+ )])));
+
let damage_tracker = OutputDamageTracker::from_output(&output);
let timer = Timer::immediate();
@@ -99,6 +107,7 @@ impl Winit {
output,
backend,
damage_tracker,
+ connectors,
}
}
@@ -162,4 +171,8 @@ impl Winit {
let renderer = self.backend.renderer();
renderer.set_debug_flags(renderer.debug_flags() ^ DebugFlags::TINT);
}
+
+ pub fn connectors(&self) -> Arc<Mutex<HashMap<String, Output>>> {
+ self.connectors.clone()
+ }
}
diff --git a/src/config.rs b/src/config.rs
index 9d03275e..b624f88e 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -128,12 +128,15 @@ pub enum Action {
pub struct DebugConfig {
#[knuffel(child, unwrap(argument), default = 1.)]
pub animation_slowdown: f64,
+ #[knuffel(child)]
+ pub screen_cast_in_non_session_instances: bool,
}
impl Default for DebugConfig {
fn default() -> Self {
Self {
animation_slowdown: 1.,
+ screen_cast_in_non_session_instances: false,
}
}
}
@@ -308,6 +311,7 @@ mod tests {
]),
debug: DebugConfig {
animation_slowdown: 2.,
+ ..Default::default()
},
},
);
diff --git a/src/dbus/mod.rs b/src/dbus/mod.rs
index 01b4c391..84a8bc30 100644
--- a/src/dbus/mod.rs
+++ b/src/dbus/mod.rs
@@ -1 +1,3 @@
+pub mod mutter_display_config;
+pub mod mutter_screen_cast;
pub mod mutter_service_channel;
diff --git a/src/dbus/mutter_display_config.rs b/src/dbus/mutter_display_config.rs
new file mode 100644
index 00000000..1807f01d
--- /dev/null
+++ b/src/dbus/mutter_display_config.rs
@@ -0,0 +1,88 @@
+use std::collections::HashMap;
+use std::sync::{Arc, Mutex};
+
+use serde::Serialize;
+use smithay::output::Output;
+use zbus::zvariant::{OwnedValue, Type};
+use zbus::{dbus_interface, fdo};
+
+pub struct DisplayConfig {
+ connectors: Arc<Mutex<HashMap<String, Output>>>,
+}
+
+#[derive(Serialize, Type)]
+pub struct Monitor {
+ names: (String, String, String, String),
+ modes: Vec<Mode>,
+ properties: HashMap<String, OwnedValue>,
+}
+
+#[derive(Serialize, Type)]
+pub struct Mode {
+ id: String,
+ width: i32,
+ height: i32,
+ refresh_rate: f64,
+ preferred_scale: f64,
+ supported_scales: Vec<f64>,
+ properties: HashMap<String, OwnedValue>,
+}
+
+#[derive(Serialize, Type)]
+pub struct LogicalMonitor {
+ x: i32,
+ y: i32,
+ scale: f64,
+ transform: u32,
+ is_primary: bool,
+ monitors: Vec<(String, String, String, String)>,
+ properties: HashMap<String, OwnedValue>,
+}
+
+#[dbus_interface(name = "org.gnome.Mutter.DisplayConfig")]
+impl DisplayConfig {
+ async fn get_current_state(
+ &self,
+ ) -> fdo::Result<(
+ u32,
+ Vec<Monitor>,
+ Vec<LogicalMonitor>,
+ HashMap<String, OwnedValue>,
+ )> {
+ // Construct the DBus response.
+ let monitors: Vec<Monitor> = self
+ .connectors
+ .lock()
+ .unwrap()
+ .keys()
+ .map(|c| Monitor {
+ names: (c.clone(), String::new(), String::new(), String::new()),
+ modes: vec![],
+ properties: HashMap::new(),
+ })
+ .collect();
+
+ let logical_monitors = monitors
+ .iter()
+ .map(|m| LogicalMonitor {
+ x: 0,
+ y: 0,
+ scale: 1.,
+ transform: 0,
+ is_primary: false,
+ monitors: vec![m.names.clone()],
+ properties: HashMap::new(),
+ })
+ .collect();
+
+ Ok((0, monitors, logical_monitors, HashMap::new()))
+ }
+
+ // FIXME: monitors-changed signal.
+}
+
+impl DisplayConfig {
+ pub fn new(connectors: Arc<Mutex<HashMap<String, Output>>>) -> Self {
+ Self { connectors }
+ }
+}
diff --git a/src/dbus/mutter_screen_cast.rs b/src/dbus/mutter_screen_cast.rs
new file mode 100644
index 00000000..320319cc
--- /dev/null
+++ b/src/dbus/mutter_screen_cast.rs
@@ -0,0 +1,256 @@
+use std::collections::HashMap;
+use std::mem;
+use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
+use std::sync::{Arc, Mutex};
+
+use serde::Deserialize;
+use smithay::output::Output;
+use smithay::reexports::calloop;
+use zbus::zvariant::{DeserializeDict, OwnedObjectPath, Type, Value};
+use zbus::{dbus_interface, fdo, InterfaceRef, ObjectServer, SignalContext};
+
+#[derive(Clone)]
+pub struct ScreenCast {
+ connectors: Arc<Mutex<HashMap<String, Output>>>,
+ to_niri: calloop::channel::Sender<ToNiriMsg>,
+ sessions: Arc<Mutex<Vec<(Session, InterfaceRef<Session>)>>>,
+}
+
+#[derive(Clone)]
+pub struct Session {
+ id: usize,
+ connectors: Arc<Mutex<HashMap<String, Output>>>,
+ to_niri: calloop::channel::Sender<ToNiriMsg>,
+ streams: Arc<Mutex<Vec<(Stream, InterfaceRef<Stream>)>>>,
+}
+
+#[derive(Debug, Default, Deserialize, Type, Clone, Copy)]
+pub enum CursorMode {
+ #[default]
+ Hidden = 0,
+ Embedded = 1,
+ Metadata = 2,
+}
+
+#[derive(Debug, DeserializeDict, Type)]
+#[zvariant(signature = "dict")]
+struct RecordMonitorProperties {
+ #[zvariant(rename = "cursor-mode")]
+ cursor_mode: Option<CursorMode>,
+ #[zvariant(rename = "is-recording")]
+ _is_recording: Option<bool>,
+}
+
+#[derive(Clone)]
+pub struct Stream {
+ output: Output,
+ cursor_mode: CursorMode,
+ was_started: Arc<AtomicBool>,
+ to_niri: calloop::channel::Sender<ToNiriMsg>,
+}
+
+pub enum ToNiriMsg {
+ StartCast {
+ session_id: usize,
+ output: Output,
+ cursor_mode: CursorMode,
+ signal_ctx: SignalContext<'static>,
+ },
+ StopCast {
+ session_id: usize,
+ },
+}
+
+#[dbus_interface(name = "org.gnome.Mutter.ScreenCast")]
+impl ScreenCast {
+ async fn create_session(
+ &self,
+ #[zbus(object_server)] server: &ObjectServer,
+ properties: HashMap<&str, Value<'_>>,
+ ) -> fdo::Result<OwnedObjectPath> {
+ if properties.contains_key("remote-desktop-session-id") {
+ return Err(fdo::Error::Failed(
+ "there are no remote desktop sessions".to_owned(),
+ ));
+ }
+
+ static NUMBER: AtomicUsize = AtomicUsize::new(0);
+ let session_id = NUMBER.fetch_add(1, Ordering::SeqCst);
+ let path = format!("/org/gnome/Mutter/ScreenCast/Session/u{}", session_id);
+ let path = OwnedObjectPath::try_from(path).unwrap();
+
+ let session = Session::new(session_id, self.connectors.clone(), self.to_niri.clone());
+ match server.at(&path, session.clone()).await {
+ Ok(true) => {
+ let iface = server.interface(&path).await.unwrap();
+ self.sessions.lock().unwrap().push((session, iface));
+ }
+ Ok(false) => return Err(fdo::Error::Failed("session path already exists".to_owned())),
+ Err(err) => {
+ return Err(fdo::Error::Failed(format!(
+ "error creating session object: {err:?}"
+ )))
+ }
+ }
+
+ Ok(path)
+ }
+
+ #[dbus_interface(property)]
+ async fn version(&self) -> i32 {
+ 4
+ }
+}
+
+#[dbus_interface(name = "org.gnome.Mutter.ScreenCast.Session")]
+impl Session {
+ async fn start(&self) {
+ debug!("start");
+
+ for (stream, iface) in &*self.streams.lock().unwrap() {
+ stream.start(self.id, iface.signal_context().clone());
+ }
+ }
+
+ pub async fn stop(
+ &self,
+ #[zbus(object_server)] server: &ObjectServer,
+ #[zbus(signal_context)] ctxt: SignalContext<'_>,
+ ) {
+ debug!("stop");
+
+ Session::closed(&ctxt).await.unwrap();
+
+ if let Err(err) = self.to_niri.send(ToNiriMsg::StopCast {
+ session_id: self.id,
+ }) {
+ warn!("error sending StopCast to niri: {err:?}");
+ }
+
+ let streams = mem::take(&mut *self.streams.lock().unwrap());
+ for (_, iface) in streams.iter() {
+ server
+ .remove::<Stream, _>(iface.signal_context().path())
+ .await
+ .unwrap();
+ }
+
+ server.remove::<Session, _>(ctxt.path()).await.unwrap();
+ }
+
+ async fn record_monitor(
+ &mut self,
+ #[zbus(object_server)] server: &ObjectServer,
+ connector: &str,
+ properties: RecordMonitorProperties,
+ ) -> fdo::Result<OwnedObjectPath> {
+ debug!(connector, ?properties, "record_monitor");
+
+ let Some(output) = self.connectors.lock().unwrap().get(connector).cloned() else {
+ return Err(fdo::Error::Failed("no such monitor".to_owned()));
+ };
+
+ static NUMBER: AtomicUsize = AtomicUsize::new(0);
+ let path = format!(
+ "/org/gnome/Mutter/ScreenCast/Stream/u{}",
+ NUMBER.fetch_add(1, Ordering::SeqCst)
+ );
+ let path = OwnedObjectPath::try_from(path).unwrap();
+
+ let cursor_mode = properties.cursor_mode.unwrap_or_default();
+
+ let stream = Stream::new(output, cursor_mode, self.to_niri.clone());
+ match server.at(&path, stream.clone()).await {
+ Ok(true) => {
+ let iface = server.interface(&path).await.unwrap();
+ self.streams.lock().unwrap().push((stream, iface));
+ }
+ Ok(false) => return Err(fdo::Error::Failed("stream path already exists".to_owned())),
+ Err(err) => {
+ return Err(fdo::Error::Failed(format!(
+ "error creating stream object: {err:?}"
+ )))
+ }
+ }
+
+ Ok(path)
+ }
+
+ #[dbus_interface(signal)]
+ async fn closed(ctxt: &SignalContext<'_>) -> zbus::Result<()>;
+}
+
+#[dbus_interface(name = "org.gnome.Mutter.ScreenCast.Stream")]
+impl Stream {
+ #[dbus_interface(signal)]
+ pub async fn pipe_wire_stream_added(ctxt: &SignalContext<'_>, node_id: u32)
+ -> zbus::Result<()>;
+}
+
+impl ScreenCast {
+ pub fn new(
+ connectors: Arc<Mutex<HashMap<String, Output>>>,
+ to_niri: calloop::channel::Sender<ToNiriMsg>,
+ ) -> Self {
+ Self {
+ connectors,
+ to_niri,
+ sessions: Arc::new(Mutex::new(vec![])),
+ }
+ }
+}
+
+impl Session {
+ pub fn new(
+ id: usize,
+ connectors: Arc<Mutex<HashMap<String, Output>>>,
+ to_niri: calloop::channel::Sender<ToNiriMsg>,
+ ) -> Self {
+ Self {
+ id,
+ connectors,
+ streams: Arc::new(Mutex::new(vec![])),
+ to_niri,
+ }
+ }
+}
+
+impl Drop for Session {
+ fn drop(&mut self) {
+ let _ = self.to_niri.send(ToNiriMsg::StopCast {
+ session_id: self.id,
+ });
+ }
+}
+
+impl Stream {
+ pub fn new(
+ output: Output,
+ cursor_mode: CursorMode,
+ to_niri: calloop::channel::Sender<ToNiriMsg>,
+ ) -> Self {
+ Self {
+ output,
+ cursor_mode,
+ was_started: Arc::new(AtomicBool::new(false)),
+ to_niri,
+ }
+ }
+
+ fn start(&self, session_id: usize, ctxt: SignalContext<'static>) {
+ if self.was_started.load(Ordering::SeqCst) {
+ return;
+ }
+
+ let msg = ToNiriMsg::StartCast {
+ session_id,
+ output: self.output.clone(),
+ cursor_mode: self.cursor_mode,
+ signal_ctx: ctxt,
+ };
+
+ if let Err(err) = self.to_niri.send(msg) {
+ warn!("error sending StartCast to niri: {err:?}");
+ }
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 81422065..6c5e9ab0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -10,6 +10,7 @@ mod handlers;
mod input;
mod layout;
mod niri;
+mod pw_utils;
mod utils;
use std::env;
diff --git a/src/niri.rs b/src/niri.rs
index b4302a4c..a48879e9 100644
--- a/src/niri.rs
+++ b/src/niri.rs
@@ -8,6 +8,7 @@ use std::{env, thread};
use anyhow::Context;
use directories::UserDirs;
use sd_notify::NotifyState;
+use smithay::backend::allocator::dmabuf::Dmabuf;
use smithay::backend::allocator::Fourcc;
use smithay::backend::renderer::element::surface::{
render_elements_from_surface_tree, WaylandSurfaceRenderElement,
@@ -16,7 +17,7 @@ use smithay::backend::renderer::element::texture::{TextureBuffer, TextureRenderE
use smithay::backend::renderer::element::{
render_elements, AsRenderElements, Element, RenderElement, RenderElementStates,
};
-use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
+use smithay::backend::renderer::gles::{GlesMapping, GlesRenderer, GlesTexture};
use smithay::backend::renderer::{Bind, ExportMem, Frame, ImportAll, Offscreen, Renderer};
use smithay::desktop::utils::{
send_dmabuf_feedback_surface_tree, send_frames_surface_tree,
@@ -31,7 +32,7 @@ use smithay::input::pointer::{CursorImageAttributes, CursorImageStatus, MotionEv
use smithay::input::{Seat, SeatState};
use smithay::output::Output;
use smithay::reexports::calloop::generic::Generic;
-use smithay::reexports::calloop::{Idle, Interest, LoopHandle, LoopSignal, Mode, PostAction};
+use smithay::reexports::calloop::{self, Idle, Interest, LoopHandle, LoopSignal, Mode, PostAction};
use smithay::reexports::nix::libc::CLOCK_MONOTONIC;
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::WmCapabilities;
use smithay::reexports::wayland_server::backend::{
@@ -40,7 +41,7 @@ use smithay::reexports::wayland_server::backend::{
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::reexports::wayland_server::{Display, DisplayHandle};
use smithay::utils::{
- IsAlive, Logical, Physical, Point, Rectangle, Scale, Transform, SERIAL_COUNTER,
+ IsAlive, Logical, Physical, Point, Rectangle, Scale, Size, Transform, SERIAL_COUNTER,
};
use smithay::wayland::compositor::{with_states, CompositorClientState, CompositorState};
use smithay::wayland::data_device::DataDeviceState;
@@ -57,9 +58,12 @@ use time::OffsetDateTime;
use crate::backend::{Backend, Tty, Winit};
use crate::config::Config;
+use crate::dbus::mutter_display_config::DisplayConfig;
+use crate::dbus::mutter_screen_cast::{self, ScreenCast, ToNiriMsg};
use crate::dbus::mutter_service_channel::ServiceChannel;
use crate::frame_clock::FrameClock;
use crate::layout::{MonitorRenderElement, MonitorSet};
+use crate::pw_utils::{Cast, PipeWire};
use crate::utils::{center, get_monotonic_time, load_default_cursor};
use crate::LoopData;
@@ -102,6 +106,11 @@ pub struct Niri {
pub zbus_conn: Option<zbus::blocking::Connection>,
pub inhibit_power_key_fd: Option<zbus::zvariant::OwnedFd>,
+ pub screen_cast: ScreenCast,
+
+ // Casts are dropped before PipeWire to prevent a double-free (yay).
+ pub casts: Vec<Cast>,
+ pub pipewire: Option<PipeWire>,
}
pub struct OutputState {
@@ -137,13 +146,7 @@ impl State {
Backend::Tty(Tty::new(event_loop.clone()))
};
- let mut niri = Niri::new(
- &config,
- event_loop,
- stop_signal,
- display,
- backend.seat_name(),
- );
+ let mut niri = Niri::new(&config, event_loop, stop_signal, display, &backend);
backend.init(&mut niri);
Self {
@@ -195,7 +198,7 @@ impl Niri {
event_loop: LoopHandle<'static, LoopData>,
stop_signal: LoopSignal,
display: &mut Display<State>,
- seat_name: String,
+ backend: &Backend,
) -> Self {
let display_handle = display.handle();
@@ -215,7 +218,7 @@ impl Niri {
let presentation_state =
PresentationState::new::<State>(&display_handle, CLOCK_MONOTONIC as u32);
- let mut seat: Seat<State> = seat_state.new_wl_seat(&display_handle, seat_name);
+ let mut seat: Seat<State> = seat_state.new_wl_seat(&display_handle, backend.seat_name());
let xkb = XkbConfig {
rules: &config.input.keyboard.xkb.rules,
model: &config.input.keyboard.xkb.model,
@@ -246,6 +249,102 @@ impl Niri {
socket_name.to_string_lossy()
);
+ 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);
+
let mut zbus_conn = None;
let mut inhibit_power_key_fd = None;
if std::env::var_os("NOTIFY_SOCKET").is_some() {
@@ -278,7 +377,7 @@ impl Niri {
}
// Set up zbus, make sure it happens before anything might want it.
- let conn = zbus::blocking::ConnectionBuilder::session()
+ let mut conn = zbus::blocking::ConnectionBuilder::session()
.unwrap()
.name("org.gnome.Mutter.ServiceChannel")
.unwrap()
@@ -286,9 +385,24 @@ impl Niri {
"/org/gnome/Mutter/ServiceChannel",
ServiceChannel::new(display_handle.clone()),
)
- .unwrap()
- .build()
.unwrap();
+
+ if pipewire.is_some() && !config.debug.screen_cast_in_non_session_instances {
+ conn = conn
+ .name("org.gnome.Mutter.ScreenCast")
+ .unwrap()
+ .serve_at("/org/gnome/Mutter/ScreenCast", screen_cast.clone())
+ .unwrap()
+ .name("org.gnome.Mutter.DisplayConfig")
+ .unwrap()
+ .serve_at(
+ "/org/gnome/Mutter/DisplayConfig",
+ DisplayConfig::new(backend.connectors()),
+ )
+ .unwrap();
+ }
+
+ let conn = conn.build().unwrap();
zbus_conn = Some(conn);
// Notify systemd we're ready.
@@ -321,6 +435,23 @@ impl Niri {
warn!("error inhibiting power key: {err:?}");
}
}
+ } else if pipewire.is_some() && config.debug.screen_cast_in_non_session_instances {
+ let conn = zbus::blocking::ConnectionBuilder::session()
+ .unwrap()
+ .name("org.gnome.Mutter.ScreenCast")
+ .unwrap()
+ .serve_at("/org/gnome/Mutter/ScreenCast", screen_cast.clone())
+ .unwrap()
+ .name("org.gnome.Mutter.DisplayConfig")
+ .unwrap()
+ .serve_at(
+ "/org/gnome/Mutter/DisplayConfig",
+ DisplayConfig::new(backend.connectors()),
+ )
+ .unwrap()
+ .build()
+ .unwrap();
+ zbus_conn = Some(conn);
}
let display_source = Generic::new(
@@ -364,6 +495,9 @@ impl Niri {
zbus_conn,
inhibit_power_key_fd,
+ screen_cast,
+ pipewire,
+ casts: vec![],
}
}
@@ -720,6 +854,9 @@ impl Niri {
// Send the frame callbacks.
self.send_frame_callbacks(output);
+
+ // Render and send to PipeWire screencast streams.
+ self.send_for_screen_cast(backend, output, &elements, presentation_time);
}
fn send_dmabuf_feedbacks(&self, output: &Output, feedback: &DmabufFeedback) {
@@ -827,47 +964,74 @@ impl Niri {
feedback
}
- pub fn screenshot(
+ fn send_for_screen_cast(
&mut self,
- renderer: &mut GlesRenderer,
+ backend: &mut Backend,
output: &Output,
- ) -> anyhow::Result<()> {
- let _span = tracy_client::span!("Niri::screenshot");
+ elements: &[OutputRenderElements<GlesRenderer>],
+ presentation_time: Duration,
+ ) {
+ let _span = tracy_client::span!("Niri::send_for_screen_cast");
let size = output.current_mode().unwrap().size;
- 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")?;
+ for cast in &mut self.casts {
+ if !cast.is_active.get() {
+ continue;
+ }
- let elements = self.render(renderer, output);
+ 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;
+ }
+ };
- 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.into_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 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;
}
+ }
- let sync_point = frame.finish().context("error finishing frame")?;
- sync_point.wait();
+ 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);
- let mapping = renderer
- .copy_framebuffer(Rectangle::from_loc_and_size((0, 0), buffer_size), fourcc)
- .context("error copying framebuffer")?;
+ let mapping = render_and_download(renderer, size, &elements).context("error rendering")?;
let copy = renderer
.map_texture(&mapping)
.context("error mapping texture")?;
@@ -928,3 +1092,76 @@ impl ClientData for ClientState {
fn initialized(&self, _client_id: ClientId) {}
fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {}
}
+
+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")?;