waydriver_capture_mutter/
lib.rs1use std::collections::HashMap;
9use std::path::PathBuf;
10use std::sync::Arc;
11
12use async_trait::async_trait;
13use futures_util::StreamExt;
14use zbus::zvariant::{OwnedObjectPath, Value};
15
16use waydriver::{CaptureBackend, Error, PipeWireStream, Result};
17use waydriver_compositor_mutter::MutterState;
18
19pub struct MutterCapture {
21 state: Arc<MutterState>,
22}
23
24impl MutterCapture {
25 pub fn new(state: Arc<MutterState>) -> Self {
27 Self { state }
28 }
29}
30
31#[async_trait]
32impl CaptureBackend for MutterCapture {
33 async fn start_stream(&self) -> Result<PipeWireStream> {
34 let conn = &self.state.conn;
35
36 let empty_opts: HashMap<&str, Value> = HashMap::new();
38 let reply = conn
39 .call_method(
40 Some("org.gnome.Mutter.ScreenCast"),
41 "/org/gnome/Mutter/ScreenCast",
42 Some("org.gnome.Mutter.ScreenCast"),
43 "CreateSession",
44 &(empty_opts.clone(),),
45 )
46 .await
47 .map_err(|e| Error::Screenshot(format!("CreateSession: {e}")))?;
48 let session_path: OwnedObjectPath = reply
49 .body()
50 .deserialize()
51 .map_err(|e| Error::Screenshot(format!("parse session path: {e}")))?;
52
53 let reply = conn
55 .call_method(
56 Some("org.gnome.Mutter.ScreenCast"),
57 session_path.as_str(),
58 Some("org.gnome.Mutter.ScreenCast.Session"),
59 "RecordMonitor",
60 &("", empty_opts),
61 )
62 .await
63 .map_err(|e| Error::Screenshot(format!("RecordMonitor: {e}")))?;
64 let stream_path: OwnedObjectPath = reply
65 .body()
66 .deserialize()
67 .map_err(|e| Error::Screenshot(format!("parse stream path: {e}")))?;
68
69 let stream_proxy: zbus::Proxy<'_> = zbus::proxy::Builder::new(conn)
73 .destination("org.gnome.Mutter.ScreenCast")
74 .map_err(|e| Error::Screenshot(format!("proxy destination: {e}")))?
75 .path(stream_path.as_str())
76 .map_err(|e| Error::Screenshot(format!("proxy path: {e}")))?
77 .interface("org.gnome.Mutter.ScreenCast.Stream")
78 .map_err(|e| Error::Screenshot(format!("proxy interface: {e}")))?
79 .build()
80 .await
81 .map_err(|e| Error::Screenshot(format!("build stream proxy: {e}")))?;
82
83 let mut signal_stream = stream_proxy
84 .receive_signal("PipeWireStreamAdded")
85 .await
86 .map_err(|e| Error::Screenshot(format!("receive_signal: {e}")))?;
87
88 conn.call_method(
90 Some("org.gnome.Mutter.ScreenCast"),
91 session_path.as_str(),
92 Some("org.gnome.Mutter.ScreenCast.Session"),
93 "Start",
94 &(),
95 )
96 .await
97 .map_err(|e| Error::Screenshot(format!("Start: {e}")))?;
98
99 let node_id: u32 = tokio::time::timeout(std::time::Duration::from_secs(5), async {
101 let signal = signal_stream
102 .next()
103 .await
104 .ok_or_else(|| Error::Screenshot("signal stream ended".to_string()))?;
105 signal
106 .body()
107 .deserialize::<u32>()
108 .map_err(|e| Error::Screenshot(format!("parse node_id: {e}")))
109 })
110 .await
111 .map_err(|_| Error::Screenshot("timeout waiting for PipeWireStreamAdded".to_string()))??;
112
113 tracing::debug!(node_id, "got PipeWire node id for screenshot");
114
115 Ok(PipeWireStream {
116 node_id,
117 token: Box::new(session_path),
118 })
119 }
120
121 async fn stop_stream(&self, stream: PipeWireStream) -> Result<()> {
122 let session_path = stream.token.downcast::<OwnedObjectPath>().map_err(|_| {
123 Error::Screenshot("stop_stream: token was not an OwnedObjectPath".into())
124 })?;
125 let _ = self
126 .state
127 .conn
128 .call_method(
129 Some("org.gnome.Mutter.ScreenCast"),
130 session_path.as_str(),
131 Some("org.gnome.Mutter.ScreenCast.Session"),
132 "Stop",
133 &(),
134 )
135 .await;
136 Ok(())
137 }
138
139 fn pipewire_socket(&self) -> PathBuf {
140 self.state.runtime_dir.join("pipewire-0")
141 }
142}