use crate::{
abilities::Abilities,
file::{CameraFile, CameraFilePath},
filesys::{CameraFS, StorageInfo},
helper::{camera_text_to_str, chars_to_cow, uninit},
port::PortInfo,
try_gp_internal,
widget::{Widget, WidgetType},
Context, InnerPtr, Result,
};
use std::{borrow::Cow, ffi, marker::PhantomData, os::raw::c_char, time::Duration};
#[derive(Debug)]
pub enum CameraEvent {
Unknown(String),
Timeout,
NewFile(CameraFilePath),
FileChanged(CameraFilePath),
NewFolder(CameraFilePath),
CaptureComplete,
}
pub struct Camera<'a> {
pub(crate) camera: *mut libgphoto2_sys::Camera,
pub(crate) context: &'a Context<'a>,
_phantom: PhantomData<&'a ffi::c_void>,
}
impl Drop for Camera<'_> {
fn drop(&mut self) {
unsafe {
libgphoto2_sys::gp_camera_unref(self.camera);
}
}
}
impl<'a> InnerPtr<'a, libgphoto2_sys::Camera> for Camera<'a> {
unsafe fn inner_mut_ptr(&'a self) -> &'a *mut libgphoto2_sys::Camera {
&self.camera
}
}
impl<'a> Camera<'a> {
pub(crate) fn new(camera: *mut libgphoto2_sys::Camera, context: &'a Context) -> Self {
Self { camera, context, _phantom: PhantomData }
}
pub fn capture_image(&self) -> Result<CameraFilePath> {
let mut file_path_ptr = unsafe { uninit() };
try_gp_internal!(libgphoto2_sys::gp_camera_capture(
self.camera,
libgphoto2_sys::CameraCaptureType::GP_CAPTURE_IMAGE,
&mut file_path_ptr,
self.context.inner
))?;
Ok(file_path_ptr.into())
}
pub fn capture_preview(&self) -> Result<CameraFile> {
let camera_file = CameraFile::new()?;
try_gp_internal!(libgphoto2_sys::gp_camera_capture_preview(
self.camera,
camera_file.inner,
self.context.inner
))?;
Ok(camera_file)
}
pub fn abilities(&self) -> Result<Abilities> {
let mut abilities = unsafe { uninit() };
try_gp_internal!(libgphoto2_sys::gp_camera_get_abilities(self.camera, &mut abilities))?;
Ok(abilities.into())
}
pub fn summary(&self) -> Result<Cow<str>> {
let mut summary = unsafe { uninit() };
try_gp_internal!(libgphoto2_sys::gp_camera_get_summary(
self.camera,
&mut summary,
self.context.inner
))?;
Ok(camera_text_to_str(summary))
}
pub fn about(&self) -> Result<Cow<str>> {
let mut about = unsafe { uninit() };
try_gp_internal!(libgphoto2_sys::gp_camera_get_about(
self.camera,
&mut about,
self.context.inner
))?;
Ok(camera_text_to_str(about))
}
pub fn manual(&self) -> Result<Cow<str>> {
let mut manual = unsafe { uninit() };
try_gp_internal!(libgphoto2_sys::gp_camera_get_manual(
self.camera,
&mut manual,
self.context.inner
))?;
Ok(camera_text_to_str(manual))
}
pub fn storages(&self) -> Result<Vec<StorageInfo>> {
let mut storages_ptr = unsafe { uninit() };
let mut storages_len = unsafe { uninit() };
try_gp_internal!(libgphoto2_sys::gp_camera_get_storageinfo(
self.camera,
&mut storages_ptr,
&mut storages_len,
self.context.inner
))?;
let storages = unsafe {
std::slice::from_raw_parts(
storages_ptr.cast::<StorageInfo>(),
storages_len as usize,
)
};
let result = storages.to_vec();
unsafe {
libc::free(storages_ptr.cast());
}
Ok(result)
}
pub fn fs(&'a self) -> CameraFS<'a> {
CameraFS::new(self)
}
pub fn wait_event(&self, timeout: Duration) -> Result<CameraEvent> {
use libgphoto2_sys::CameraEventType;
let duration_milliseconds = timeout.as_millis();
let mut event_type = unsafe { uninit() };
let mut event_data = unsafe { uninit() };
try_gp_internal!(libgphoto2_sys::gp_camera_wait_for_event(
self.camera,
duration_milliseconds as i32,
&mut event_type,
&mut event_data,
self.context.inner
))?;
Ok(match event_type {
CameraEventType::GP_EVENT_UNKNOWN => {
let data = chars_to_cow(event_data as *const c_char);
CameraEvent::Unknown(data.to_string())
}
CameraEventType::GP_EVENT_TIMEOUT => CameraEvent::Timeout,
CameraEventType::GP_EVENT_FILE_ADDED => {
let file = event_data as *const libgphoto2_sys::CameraFilePath;
CameraEvent::NewFile(CameraFilePath { inner: unsafe { *file } })
}
CameraEventType::GP_EVENT_FOLDER_ADDED => {
let folder = event_data as *const libgphoto2_sys::CameraFilePath;
CameraEvent::NewFolder(CameraFilePath { inner: unsafe { *folder } })
}
CameraEventType::GP_EVENT_FILE_CHANGED => {
let changed_file = event_data as *const libgphoto2_sys::CameraFilePath;
CameraEvent::FileChanged(CameraFilePath { inner: unsafe { *changed_file } })
}
CameraEventType::GP_EVENT_CAPTURE_COMPLETE => CameraEvent::CaptureComplete,
})
}
pub fn port_info(&self) -> Result<PortInfo> {
let mut port_info = unsafe { uninit() };
try_gp_internal!(libgphoto2_sys::gp_camera_get_port_info(self.camera, &mut port_info))?;
Ok(PortInfo { inner: port_info })
}
pub fn config(&self) -> Result<Widget<'a>> {
let mut root_widget = unsafe { uninit() };
try_gp_internal!(libgphoto2_sys::gp_camera_get_config(
self.camera,
&mut root_widget,
self.context.inner
))?;
Ok(Widget::new(root_widget))
}
pub fn config_key(&self, key: &str) -> Result<Widget<'a>> {
let mut widget = unsafe { uninit() };
let key = ffi::CString::new(key)?;
try_gp_internal!(libgphoto2_sys::gp_camera_get_single_config(
self.camera,
key.as_ptr() as *const c_char,
&mut widget,
self.context.inner
))?;
Ok(Widget::new(widget))
}
pub fn set_all_config(&self, config: &Widget) -> Result<()> {
if !matches!(config.widget_type()?, WidgetType::Window | WidgetType::Section) {
Err("Full config object must be of type Window or section")?;
}
try_gp_internal!(libgphoto2_sys::gp_camera_set_config(
self.camera,
config.inner,
self.context.inner
))?;
Ok(())
}
pub fn set_config(&self, config: &Widget) -> Result<()> {
let name = ffi::CString::new(&config.name()?[..])?;
try_gp_internal!(libgphoto2_sys::gp_camera_set_single_config(
self.camera,
name.as_ptr() as *const c_char,
config.inner,
self.context.inner
))?;
Ok(())
}
}