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, StreamToken};
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();
40 let mut create_opts: HashMap<&str, Value> = HashMap::new();
41 create_opts.insert(
42 "remote-desktop-session-id",
43 Value::from(self.state.rd_session_id()),
44 );
45 let reply = conn
46 .call_method(
47 Some("org.gnome.Mutter.ScreenCast"),
48 "/org/gnome/Mutter/ScreenCast",
49 Some("org.gnome.Mutter.ScreenCast"),
50 "CreateSession",
51 &(create_opts,),
52 )
53 .await
54 .map_err(|e| Error::screenshot_with("CreateSession", e))?;
55 let session_path: OwnedObjectPath = reply
56 .body()
57 .deserialize()
58 .map_err(|e| Error::screenshot_with("parse session path", e))?;
59
60 let reply = conn
62 .call_method(
63 Some("org.gnome.Mutter.ScreenCast"),
64 session_path.as_str(),
65 Some("org.gnome.Mutter.ScreenCast.Session"),
66 "RecordMonitor",
67 &("", empty_opts),
68 )
69 .await
70 .map_err(|e| Error::screenshot_with("RecordMonitor", e))?;
71 let stream_path: OwnedObjectPath = reply
72 .body()
73 .deserialize()
74 .map_err(|e| Error::screenshot_with("parse stream path", e))?;
75
76 let stream_proxy: zbus::Proxy<'_> = zbus::proxy::Builder::new(conn)
80 .destination("org.gnome.Mutter.ScreenCast")
81 .map_err(|e| Error::screenshot_with("proxy destination", e))?
82 .path(stream_path.as_str())
83 .map_err(|e| Error::screenshot_with("proxy path", e))?
84 .interface("org.gnome.Mutter.ScreenCast.Stream")
85 .map_err(|e| Error::screenshot_with("proxy interface", e))?
86 .build()
87 .await
88 .map_err(|e| Error::screenshot_with("build stream proxy", e))?;
89
90 let mut signal_stream = stream_proxy
91 .receive_signal("PipeWireStreamAdded")
92 .await
93 .map_err(|e| Error::screenshot_with("receive_signal", e))?;
94
95 let should_start_rd = {
105 let mut guard = self.state.rd_started_lock()?;
106 if *guard {
107 false
108 } else {
109 *guard = true;
110 true
111 }
112 };
113 if should_start_rd {
114 conn.call_method(
115 Some("org.gnome.Mutter.RemoteDesktop"),
116 self.state.rd_session_path(),
117 Some("org.gnome.Mutter.RemoteDesktop.Session"),
118 "Start",
119 &(),
120 )
121 .await
122 .map_err(|e| Error::screenshot_with("RemoteDesktop Start", e))?;
123 } else {
124 conn.call_method(
125 Some("org.gnome.Mutter.ScreenCast"),
126 session_path.as_str(),
127 Some("org.gnome.Mutter.ScreenCast.Session"),
128 "Start",
129 &(),
130 )
131 .await
132 .map_err(|e| Error::screenshot_with("Start", e))?;
133 }
134
135 let node_id: u32 = tokio::time::timeout(std::time::Duration::from_secs(5), async {
137 let signal = signal_stream
138 .next()
139 .await
140 .ok_or_else(|| Error::screenshot("signal stream ended"))?;
141 signal
142 .body()
143 .deserialize::<u32>()
144 .map_err(|e| Error::screenshot_with("parse node_id", e))
145 })
146 .await
147 .map_err(|_| Error::screenshot("timeout waiting for PipeWireStreamAdded"))??;
148
149 tracing::debug!(node_id, "got PipeWire node id for screenshot");
150
151 *self.state.active_stream_path_lock()? = Some(stream_path.to_string());
154
155 Ok(PipeWireStream {
156 node_id,
157 token: StreamToken::new(session_path),
163 })
164 }
165
166 async fn stop_stream(&self, stream: PipeWireStream) -> Result<()> {
167 let session_path = stream.token.downcast::<OwnedObjectPath>()?;
168 let _ = self
169 .state
170 .conn()
171 .call_method(
172 Some("org.gnome.Mutter.ScreenCast"),
173 session_path.as_str(),
174 Some("org.gnome.Mutter.ScreenCast.Session"),
175 "Stop",
176 &(),
177 )
178 .await;
179 *self.state.active_stream_path_lock()? = None;
180 Ok(())
181 }
182
183 fn pipewire_socket(&self) -> PathBuf {
184 self.state.runtime_dir().join("pipewire-0")
185 }
186}