aboutsummaryrefslogtreecommitdiff
path: root/src/dbus
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2023-09-08 17:54:02 +0400
committerIvan Molodetskikh <yalterz@gmail.com>2023-09-08 23:53:56 +0400
commitd52ca23caa4345ddf768da5af24f47eae6fd4738 (patch)
treec0e3f3f4c99b0dfd5bd739e349890081cba2187e /src/dbus
parentbd0ecf917489a84efa51bb1272ca27039256fe21 (diff)
downloadniri-d52ca23caa4345ddf768da5af24f47eae6fd4738.tar.gz
niri-d52ca23caa4345ddf768da5af24f47eae6fd4738.tar.bz2
niri-d52ca23caa4345ddf768da5af24f47eae6fd4738.zip
Add initial monitor screencast portal impl
DmaBuf monitor screencasting through xdg-dekstop-portal-gnome! Somewhat limited currently, e.g. the cursor is always embedded. But gets most of the job done.
Diffstat (limited to 'src/dbus')
-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
3 files changed, 346 insertions, 0 deletions
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:?}");
+ }
+ }
+}