1use std::{collections::HashMap, os::fd::OwnedFd};
35
36#[cfg(feature = "pipewire")]
37use pipewire::{context::Context, main_loop::MainLoop};
38use zbus::zvariant::{self, SerializeDict, Type, Value};
39
40use super::{HandleToken, Request};
41use crate::{proxy::Proxy, Error};
42
43#[derive(SerializeDict, Type, Debug, Default)]
44#[zvariant(signature = "dict")]
45struct CameraAccessOptions {
46 handle_token: HandleToken,
47}
48
49#[derive(Debug)]
54#[doc(alias = "org.freedesktop.portal.Camera")]
55pub struct Camera<'a>(Proxy<'a>);
56
57impl<'a> Camera<'a> {
58 pub async fn new() -> Result<Camera<'a>, Error> {
60 let proxy = Proxy::new_desktop("org.freedesktop.portal.Camera").await?;
61 Ok(Self(proxy))
62 }
63
64 #[doc(alias = "AccessCamera")]
70 #[doc(alias = "xdp_portal_access_camera")]
71 pub async fn request_access(&self) -> Result<Request<()>, Error> {
72 let options = CameraAccessOptions::default();
73 self.0
74 .empty_request(&options.handle_token, "AccessCamera", &options)
75 .await
76 }
77
78 #[doc(alias = "OpenPipeWireRemote")]
89 #[doc(alias = "xdp_portal_open_pipewire_remote_for_camera")]
90 pub async fn open_pipe_wire_remote(&self) -> Result<OwnedFd, Error> {
91 let options: HashMap<&str, Value<'_>> = HashMap::new();
94 let fd = self
95 .0
96 .call::<zvariant::OwnedFd>("OpenPipeWireRemote", &options)
97 .await?;
98 Ok(fd.into())
99 }
100
101 #[doc(alias = "IsCameraPresent")]
107 #[doc(alias = "xdp_portal_is_camera_present")]
108 pub async fn is_present(&self) -> Result<bool, Error> {
109 self.0.property("IsCameraPresent").await
110 }
111}
112
113impl<'a> std::ops::Deref for Camera<'a> {
114 type Target = zbus::Proxy<'a>;
115
116 fn deref(&self) -> &Self::Target {
117 &self.0
118 }
119}
120
121#[cfg(feature = "pipewire")]
122#[derive(Debug)]
124pub struct Stream {
125 node_id: u32,
126 properties: HashMap<String, String>,
127}
128
129#[cfg(feature = "pipewire")]
130impl Stream {
131 pub fn node_id(&self) -> u32 {
133 self.node_id
134 }
135
136 pub fn properties(&self) -> HashMap<String, String> {
138 self.properties.clone()
139 }
140}
141
142#[cfg(feature = "pipewire")]
143fn pipewire_streams_inner<F: Fn(Stream) + Clone + 'static, G: FnOnce() + Clone + 'static>(
144 fd: OwnedFd,
145 callback: F,
146 done_callback: G,
147) -> Result<(), pipewire::Error> {
148 let mainloop = MainLoop::new(None)?;
149 let context = Context::new(&mainloop)?;
150 let core = context.connect_fd(fd, None)?;
151 let registry = core.get_registry()?;
152
153 let pending = core.sync(0).expect("sync failed");
154
155 let loop_clone = mainloop.clone();
156 let _listener_reg = registry
157 .add_listener_local()
158 .global(move |global| {
159 if let Some(props) = &global.props {
160 if props.get("media.role") == Some("Camera") {
161 #[cfg(feature = "tracing")]
162 tracing::info!("found camera: {:#?}", props);
163
164 let mut properties = HashMap::new();
165 for (key, value) in props.iter() {
166 properties.insert(key.to_string(), value.to_string());
167 }
168 let node_id = global.id;
169
170 let stream = Stream {
171 node_id,
172 properties,
173 };
174 callback.clone()(stream);
175 }
176 }
177 })
178 .register();
179 let _listener_core = core
180 .add_listener_local()
181 .done(move |id, seq| {
182 if id == pipewire::core::PW_ID_CORE && seq == pending {
183 loop_clone.quit();
184 done_callback.clone()();
185 }
186 })
187 .register();
188
189 mainloop.run();
190
191 Ok(())
192}
193
194#[cfg(feature = "pipewire")]
207#[cfg_attr(docsrs, doc(cfg(feature = "pipewire")))]
208pub async fn pipewire_streams(fd: OwnedFd) -> Result<Vec<Stream>, pipewire::Error> {
209 let (sender, receiver) = futures_channel::oneshot::channel();
210 let (streams_sender, mut streams_receiver) = futures_channel::mpsc::unbounded();
211
212 let sender = std::sync::Arc::new(std::sync::Mutex::new(Some(sender)));
213 let streams_sender = std::sync::Arc::new(std::sync::Mutex::new(streams_sender));
214
215 std::thread::spawn(move || {
216 let inner_sender = sender.clone();
217
218 if let Err(err) = pipewire_streams_inner(
219 fd,
220 move |stream| {
221 let inner_streams_sender = streams_sender.clone();
222 if let Ok(mut sender) = inner_streams_sender.lock() {
223 let _result = sender.start_send(stream);
224 };
225 },
226 move || {
227 if let Ok(mut guard) = inner_sender.lock() {
228 if let Some(inner_sender) = guard.take() {
229 let _result = inner_sender.send(Ok(()));
230 }
231 }
232 },
233 ) {
234 #[cfg(feature = "tracing")]
235 tracing::error!("Failed to get pipewire streams {:#?}", err);
236 let mut guard = sender.lock().unwrap();
237 if let Some(sender) = guard.take() {
238 let _ = sender.send(Err(err));
239 }
240 }
241 });
242
243 receiver.await.unwrap()?;
244
245 let mut streams = vec![];
246 while let Ok(Some(stream)) = streams_receiver.try_next() {
247 streams.push(stream);
248 }
249
250 Ok(streams)
251}
252
253#[cfg(not(feature = "pipewire"))]
254#[cfg_attr(docsrs, doc(cfg(not(feature = "pipewire"))))]
255pub async fn request() -> Result<Option<OwnedFd>, Error> {
258 let proxy = Camera::new().await?;
259 proxy.request_access().await?;
260 if proxy.is_present().await? {
261 Ok(Some(proxy.open_pipe_wire_remote().await?))
262 } else {
263 Ok(None)
264 }
265}
266
267#[cfg(feature = "pipewire")]
268#[cfg_attr(docsrs, doc(cfg(feature = "pipewire")))]
269pub async fn request() -> Result<Option<(OwnedFd, Vec<Stream>)>, Error> {
272 let proxy = Camera::new().await?;
273 proxy.request_access().await?;
274 if proxy.is_present().await? {
275 let fd = proxy.open_pipe_wire_remote().await?;
276 let streams = pipewire_streams(fd.try_clone()?).await?;
277 Ok(Some((fd, streams)))
278 } else {
279 Ok(None)
280 }
281}