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