libcamera/
camera_manager.rs

1use std::{
2    ffi::{CStr, CString},
3    io,
4    marker::PhantomData,
5    ptr::NonNull,
6    sync::{mpsc, Arc, Mutex},
7};
8
9use libcamera_sys::*;
10
11use crate::{camera::Camera, logging::LoggingLevel, utils::handle_result};
12
13struct ManagerCallbacks {
14    added: Option<Box<dyn FnMut(Camera<'static>) + Send>>,
15    removed: Option<Box<dyn FnMut(Camera<'static>) + Send>>,
16    hotplug_tx: Option<mpsc::Sender<HotplugEvent>>,
17}
18
19/// Hotplug event propagated via channel helper.
20#[derive(Debug, Clone)]
21pub enum HotplugEvent {
22    Added(String),
23    Removed(String),
24}
25
26/// Camera manager used to enumerate available cameras in the system.
27pub struct CameraManager {
28    ptr: NonNull<libcamera_camera_manager_t>,
29    callbacks: Box<ManagerCallbacks>,
30    added_handle: *mut libcamera_callback_handle_t,
31    removed_handle: *mut libcamera_callback_handle_t,
32    started: bool,
33    tracker: Arc<CameraTracker>,
34}
35
36impl CameraManager {
37    /// Initializes `libcamera`, starts the manager and creates [Self].
38    pub fn new() -> io::Result<Self> {
39        let mut mgr = Self::new_unstarted()?;
40        mgr.start()?;
41        Ok(mgr)
42    }
43
44    /// Create a `CameraManager` without starting it.
45    ///
46    /// Call [Self::start] before using it to enumerate cameras.
47    pub fn new_unstarted() -> io::Result<Self> {
48        let ptr = NonNull::new(unsafe { libcamera_camera_manager_create() }).unwrap();
49        Ok(CameraManager {
50            ptr,
51            callbacks: Box::new(ManagerCallbacks {
52                added: None,
53                removed: None,
54                hotplug_tx: None,
55            }),
56            added_handle: core::ptr::null_mut(),
57            removed_handle: core::ptr::null_mut(),
58            started: false,
59            tracker: Arc::new(CameraTracker::default()),
60        })
61    }
62
63    /// Start the camera manager if it is not already running.
64    pub fn start(&mut self) -> io::Result<()> {
65        if self.started {
66            return Ok(());
67        }
68        let ret = unsafe { libcamera_camera_manager_start(self.ptr.as_ptr()) };
69        handle_result(ret)?;
70        self.started = true;
71        Ok(())
72    }
73
74    /// Stop the camera manager. Safe to call multiple times.
75    pub fn stop(&mut self) -> io::Result<()> {
76        if !self.started {
77            return Ok(());
78        }
79        unsafe { libcamera_camera_manager_stop(self.ptr.as_ptr()) };
80        self.started = false;
81        Ok(())
82    }
83
84    /// Attempt to stop only if no tracked cameras are still alive.
85    pub fn try_stop(&mut self) -> io::Result<()> {
86        if self.tracker.has_live_handles() {
87            return Err(io::Error::other(
88                "cannot stop CameraManager while cameras are still alive",
89            ));
90        }
91        self.stop()
92    }
93
94    /// Restart the manager by stopping (if safe) and starting again.
95    pub fn restart(&mut self) -> io::Result<()> {
96        self.try_stop()?;
97        self.start()
98    }
99
100    /// Returns true if the manager has been started.
101    pub fn is_started(&self) -> bool {
102        self.started
103    }
104
105    /// Returns version string of the linked libcamera.
106    pub fn version(&self) -> &str {
107        unsafe { CStr::from_ptr(libcamera_version_string()) }.to_str().unwrap()
108    }
109
110    /// Enumerates cameras within the system.
111    pub fn cameras<'a>(&self) -> CameraList<'a> {
112        unsafe {
113            CameraList::from_ptr(
114                NonNull::new(libcamera_camera_manager_cameras(self.ptr.as_ptr())).unwrap(),
115                Some(self.tracker.clone()),
116            )
117        }
118    }
119
120    /// Returns a camera by id if present.
121    pub fn get<'a>(&self, id: &str) -> Option<Camera<'a>> {
122        let id_cstr = CString::new(id).ok()?;
123        let cam_ptr = unsafe { libcamera_camera_manager_get_id(self.ptr.as_ptr(), id_cstr.as_ptr()) };
124        NonNull::new(cam_ptr).map(|p| unsafe { Camera::from_ptr_tracked(p, Some(self.tracker.clone())) })
125    }
126
127    /// Set the log level.
128    ///
129    /// # Parameters
130    ///
131    /// * `category` - Free-form category string, a list of those can be seen by running `grep 'LOG_DEFINE_CATEGORY('
132    ///   -R` on the `libcamera` source code
133    /// * `level` - Maximum log importance level to show, anything more less important than that will be hidden.
134    pub fn log_set_level(&self, category: &str, level: LoggingLevel) {
135        let category = CString::new(category).expect("category contains null byte");
136        let level: &CStr = level.into();
137        unsafe {
138            libcamera_log_set_level(category.as_ptr(), level.as_ptr());
139        }
140    }
141
142    /// Register a callback for camera-added events.
143    ///
144    /// # Warning
145    /// The callback is invoked on libcamera's internal thread. Do not block in the callback; send work to another
146    /// thread/channel if needed.
147    pub fn on_camera_added(&mut self, cb: impl FnMut(Camera<'static>) + Send + 'static) {
148        self.callbacks.added = Some(Box::new(cb));
149        if self.added_handle.is_null() {
150            let data = self.callbacks.as_mut() as *mut _ as *mut _;
151            self.added_handle = unsafe {
152                libcamera_camera_manager_camera_added_connect(self.ptr.as_ptr(), Some(camera_added_cb), data)
153            };
154        }
155    }
156
157    /// Register a callback for camera-removed events.
158    ///
159    /// # Warning
160    /// The callback is invoked on libcamera's internal thread. Do not block in the callback; send work to another
161    /// thread/channel if needed.
162    pub fn on_camera_removed(&mut self, cb: impl FnMut(Camera<'static>) + Send + 'static) {
163        self.callbacks.removed = Some(Box::new(cb));
164        if self.removed_handle.is_null() {
165            let data = self.callbacks.as_mut() as *mut _ as *mut _;
166            self.removed_handle = unsafe {
167                libcamera_camera_manager_camera_removed_connect(self.ptr.as_ptr(), Some(camera_removed_cb), data)
168            };
169        }
170    }
171
172    /// Subscribe to hotplug events via a channel.
173    ///
174    /// The returned `Receiver` yields `HotplugEvent` values. Internally this hooks into the libcamera hotplug signals
175    /// and forwards them; it uses the same callbacks as `on_camera_added/removed`, so do not mix different senders
176    /// without care.
177    pub fn subscribe_hotplug_events(&mut self) -> mpsc::Receiver<HotplugEvent> {
178        let (tx, rx) = mpsc::channel();
179        self.callbacks.hotplug_tx = Some(tx);
180        // Ensure signals are connected
181        if self.added_handle.is_null() {
182            let data = self.callbacks.as_mut() as *mut _ as *mut _;
183            self.added_handle = unsafe {
184                libcamera_camera_manager_camera_added_connect(self.ptr.as_ptr(), Some(camera_added_cb), data)
185            };
186        }
187        if self.removed_handle.is_null() {
188            let data = self.callbacks.as_mut() as *mut _ as *mut _;
189            self.removed_handle = unsafe {
190                libcamera_camera_manager_camera_removed_connect(self.ptr.as_ptr(), Some(camera_removed_cb), data)
191            };
192        }
193        rx
194    }
195}
196
197impl Drop for CameraManager {
198    fn drop(&mut self) {
199        if self.started {
200            unsafe { libcamera_camera_manager_stop(self.ptr.as_ptr()) };
201            self.started = false;
202        }
203        unsafe {
204            if !self.added_handle.is_null() {
205                libcamera_camera_manager_camera_signal_disconnect(self.ptr.as_ptr(), self.added_handle);
206            }
207            if !self.removed_handle.is_null() {
208                libcamera_camera_manager_camera_signal_disconnect(self.ptr.as_ptr(), self.removed_handle);
209            }
210            libcamera_camera_manager_destroy(self.ptr.as_ptr());
211        }
212    }
213}
214
215#[derive(Default)]
216pub(crate) struct CameraTracker {
217    handles: Mutex<Vec<std::sync::Weak<()>>>,
218}
219
220impl CameraTracker {
221    pub(crate) fn track(&self) -> std::sync::Arc<()> {
222        let token = std::sync::Arc::new(());
223        self.handles.lock().unwrap().push(std::sync::Arc::downgrade(&token));
224        token
225    }
226
227    fn has_live_handles(&self) -> bool {
228        self.handles.lock().unwrap().iter().any(|w| w.upgrade().is_some())
229    }
230}
231
232pub struct CameraList<'d> {
233    ptr: NonNull<libcamera_camera_list_t>,
234    _phantom: PhantomData<&'d ()>,
235    tracker: Option<Arc<CameraTracker>>,
236}
237
238impl<'d> CameraList<'d> {
239    pub(crate) unsafe fn from_ptr(ptr: NonNull<libcamera_camera_list_t>, tracker: Option<Arc<CameraTracker>>) -> Self {
240        Self {
241            ptr,
242            _phantom: Default::default(),
243            tracker,
244        }
245    }
246
247    /// Number of cameras
248    pub fn len(&self) -> usize {
249        unsafe { libcamera_camera_list_size(self.ptr.as_ptr()) }
250    }
251
252    /// Returns `true` if there are no cameras available
253    pub fn is_empty(&self) -> bool {
254        self.len() == 0
255    }
256
257    /// Returns camera at a given index.
258    ///
259    /// Returns [None] if index is out of range of available cameras.
260    pub fn get(&self, index: usize) -> Option<Camera<'d>> {
261        let cam_ptr = unsafe { libcamera_camera_list_get(self.ptr.as_ptr(), index as _) };
262        let tracker = self.tracker.clone();
263        NonNull::new(cam_ptr).map(|p| unsafe { Camera::from_ptr_tracked(p, tracker) })
264    }
265
266    /// Returns an iterator over the cameras in the list.
267    pub fn iter(&'d self) -> CameraListIter<'d> {
268        CameraListIter { list: self, index: 0 }
269    }
270}
271
272impl Drop for CameraList<'_> {
273    fn drop(&mut self) {
274        unsafe {
275            libcamera_camera_list_destroy(self.ptr.as_ptr());
276        }
277    }
278}
279
280pub struct CameraListIter<'d> {
281    list: &'d CameraList<'d>,
282    index: usize,
283}
284
285impl<'d> Iterator for CameraListIter<'d> {
286    type Item = Camera<'d>;
287
288    fn next(&mut self) -> Option<Self::Item> {
289        if self.index < self.list.len() {
290            let camera = self.list.get(self.index);
291            self.index += 1;
292            camera
293        } else {
294            None
295        }
296    }
297
298    fn size_hint(&self) -> (usize, Option<usize>) {
299        let len = self.list.len().saturating_sub(self.index);
300        (len, Some(len))
301    }
302}
303
304impl<'d> ExactSizeIterator for CameraListIter<'d> {}
305
306unsafe extern "C" fn camera_added_cb(data: *mut core::ffi::c_void, cam: *mut libcamera_camera_t) {
307    if data.is_null() || cam.is_null() {
308        return;
309    }
310    // Safety: called from libcamera thread, user must ensure callbacks are Send-safe.
311    let state = &mut *(data as *mut ManagerCallbacks);
312    if let Some(ptr) = NonNull::new(cam) {
313        // Clone shared_ptr once to avoid double-drop when used by multiple consumers.
314        let cam_copy = unsafe { libcamera_camera_copy(ptr.as_ptr()) };
315        if let Some(copy_ptr) = NonNull::new(cam_copy) {
316            let cam = unsafe { Camera::from_ptr(copy_ptr) };
317            let cam_id = cam.id().to_string();
318            if let Some(cb) = state.added.as_mut() {
319                cb(cam);
320            } else {
321                drop(cam);
322            }
323            if let Some(tx) = state.hotplug_tx.as_ref() {
324                let _ = tx.send(HotplugEvent::Added(cam_id));
325            }
326        }
327    }
328}
329
330unsafe extern "C" fn camera_removed_cb(data: *mut core::ffi::c_void, cam: *mut libcamera_camera_t) {
331    if data.is_null() || cam.is_null() {
332        return;
333    }
334    let state = &mut *(data as *mut ManagerCallbacks);
335    if let Some(ptr) = NonNull::new(cam) {
336        let cam_copy = unsafe { libcamera_camera_copy(ptr.as_ptr()) };
337        if let Some(copy_ptr) = NonNull::new(cam_copy) {
338            let cam = unsafe { Camera::from_ptr(copy_ptr) };
339            let cam_id = cam.id().to_string();
340            if let Some(cb) = state.removed.as_mut() {
341                cb(cam);
342            } else {
343                drop(cam);
344            }
345            if let Some(tx) = state.hotplug_tx.as_ref() {
346                let _ = tx.send(HotplugEvent::Removed(cam_id));
347            }
348        }
349    }
350}