From 8699f7bf4f303e4efa01af67999865f97ab9d750 Mon Sep 17 00:00:00 2001 From: Erica Z Date: Sun, 3 Nov 2024 10:06:32 +0100 Subject: [PATCH] split up mpv into modules --- src/mpv.rs | 275 +--------------------------------------------- src/mpv/error.rs | 36 ++++++ src/mpv/format.rs | 67 +++++++++++ src/mpv/handle.rs | 165 ++++++++++++++++++++++++++++ 4 files changed, 274 insertions(+), 269 deletions(-) create mode 100644 src/mpv/error.rs create mode 100644 src/mpv/format.rs create mode 100644 src/mpv/handle.rs diff --git a/src/mpv.rs b/src/mpv.rs index 2d95500..6a190d4 100644 --- a/src/mpv.rs +++ b/src/mpv.rs @@ -1,273 +1,10 @@ mod ffi; -use std::ffi::{c_char, c_int, c_void, CStr, CString}; +mod error; +pub use error::Error; -#[link(name = "mpv")] -extern "C" {} +mod format; +pub use format::FormatValue; -pub struct Error(c_int); - -impl Error { - fn from_return_code(rc: c_int) -> Result<(), Self> { - if rc == 0 { - Ok(()) - } else { - Err(Self(rc)) - } - } -} - -use std::fmt; - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - fmt::Display::fmt( - unsafe { CStr::from_ptr(ffi::mpv_error_string(self.0 as c_int)) } - .to_str() - .unwrap(), - f, - ) - } -} - -impl fmt::Debug for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - f.debug_tuple("Error") - .field( - &unsafe { CStr::from_ptr(ffi::mpv_error_string(self.0 as c_int)) } - .to_str() - .unwrap(), - ) - .finish() - } -} - -impl std::error::Error for Error {} - -use event_listener::{Event, EventListener, IntoNotification}; -use std::cell::RefCell; -use std::pin::Pin; -use std::ptr::NonNull; - -pub struct Handle { - inner: NonNull, - wakeup: Pin>, // the wakeup callback holds a pointer to this - wait_event_cell: RefCell<()>, -} - -// The client API is generally fully thread-safe, unless otherwise noted. -//unsafe impl Send for Handle {} -//unsafe impl Sync for Handle {} -// ........but we'd rather keep this in a single thread to play better with gtk - -impl Default for Handle { - fn default() -> Self { - Self::new() - } -} - -impl fmt::Debug for Handle { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - f.debug_tuple("Handle").field(&self.client_name()).finish() - } -} - -impl Handle { - pub fn new() -> Self { - let inner = - NonNull::new(unsafe { ffi::mpv_create() }).expect("could not create mpv handle"); - - let wakeup = Box::pin(Event::new()); - - extern "C" fn wakeup_callback(d: *mut c_void) { - let d = d as *const Event; - let wakeup = unsafe { &*d }; - wakeup.notify(1_u32.relaxed()); // mpv has its own synchronization, trust it - } - unsafe { - ffi::mpv_set_wakeup_callback( - inner.as_ptr(), - Some(wakeup_callback), - std::ptr::from_ref::(&wakeup) as *mut c_void, - ); - } - - // set up verbose logging for now - Error::from_return_code(unsafe { - ffi::mpv_request_log_messages(inner.as_ptr(), c"v".as_ptr()) - }) - .unwrap(); - - // TODO: maybe we need to set something before initialization, but idk - // also wed need a builder, since "Note that you should avoid doing concurrent accesses on the uninitialized client handle." - Error::from_return_code(unsafe { ffi::mpv_initialize(inner.as_ptr()) }) - .expect("could not initialize mpv handle"); - - Self { - inner, - wakeup, - wait_event_cell: RefCell::new(()), - } - } - - pub fn client_name(&self) -> &str { - unsafe { CStr::from_ptr(ffi::mpv_client_name(self.inner.as_ptr())) } - .to_str() - .unwrap() - } - - pub fn client_id(&self) -> i64 { - unsafe { ffi::mpv_client_id(self.inner.as_ptr()) } - } - - pub fn set_property(&self, name: &str, value: impl FormatValue) -> Result<(), Error> { - // need to add zero terminator - let name = CString::new(name).expect("null bytes in property name"); - value.into_data(|format, data| { - Error::from_return_code(unsafe { - ffi::mpv_set_property(self.inner.as_ptr(), name.as_ptr(), format, data) - }) - }) - } - - pub fn command<'a>(&self, args: impl IntoIterator) -> Result<(), Error> { - // add zero terminators to Everything - let args: Vec = args - .into_iter() - .map(CString::new) - .map(Result::unwrap) - .collect(); - let mut args: Vec<*const c_char> = args - .iter() - .map(CString::as_c_str) - .map(CStr::as_ptr) - .collect(); - // must be null terminated - args.push(std::ptr::null_mut()); - let args: *mut *const c_char = args.as_mut_ptr(); - - Error::from_return_code(unsafe { ffi::mpv_command(self.inner.as_ptr(), args) }) - } - - pub fn tick(&self) -> Option { - // take listener before we drain the event queue, so we don't miss any notifications - let listener = self.wakeup.listen(); - let borrowed = self - .wait_event_cell - .try_borrow_mut() - .expect("Mpv::tick is not reentrant"); - - loop { - let event = unsafe { &*ffi::mpv_wait_event(self.inner.as_ptr(), 0.0) }; - - match event.event_id { - ffi::mpv_event_id_MPV_EVENT_NONE => break, - - ffi::mpv_event_id_MPV_EVENT_LOG_MESSAGE => { - let data = unsafe { &*(event.data as *mut ffi::mpv_event_log_message) }; - // TODO: actual logging? - let prefix = unsafe { CStr::from_ptr(data.prefix) }.to_str().unwrap(); - let level = unsafe { CStr::from_ptr(data.level) }.to_str().unwrap(); - let text = unsafe { CStr::from_ptr(data.text) }.to_str().unwrap(); - print!("[{prefix}] {level}: {text}"); - } - - ffi::mpv_event_id_MPV_EVENT_SHUTDOWN => { - return None; // good bye forever - } - - 11 => { /* deprecated, ignore */ } - - _ => todo!("event {}", event.event_id), - } - } - - drop(borrowed); // make sure the borrow is held until here - Some(listener) - } -} - -impl Drop for Handle { - fn drop(&mut self) { - println!("dropping MpvHandle"); - - // let any executor ticking tasks know we're ded - self.wakeup.notify(u32::MAX.relaxed()); - - // just in case - unsafe { - ffi::mpv_wait_async_requests(self.inner.as_ptr()); - } - // drain event queue (we're &mut so we know we have exclusive access) - self.tick(); - - unsafe { - ffi::mpv_destroy(self.inner.as_ptr()); - } - } -} - -pub trait FormatValue: Sized { - const FORMAT: ffi::mpv_format; - fn into_data(self, f: impl FnOnce(ffi::mpv_format, *mut c_void) -> T) -> T; - fn from_data( - format: ffi::mpv_format, - data: *mut c_void, - f: impl FnOnce(Option) -> T, - ) -> T; -} - -impl<'a> FormatValue for &'a str { - const FORMAT: ffi::mpv_format = ffi::mpv_format_MPV_FORMAT_STRING; - - fn into_data(self, f: impl FnOnce(ffi::mpv_format, *mut c_void) -> T) -> T { - let str = CString::new(self).expect("null bytes in string"); - let str_ptr = str.as_ptr(); - f(Self::FORMAT, (&str_ptr) as *const _ as *mut c_void) - } - - fn from_data( - _format: ffi::mpv_format, - _data: *mut c_void, - _f: impl FnOnce(Option) -> T, - ) -> T { - todo!() - } -} - -impl FormatValue for bool { - const FORMAT: ffi::mpv_format = ffi::mpv_format_MPV_FORMAT_FLAG; - - fn into_data(self, f: impl FnOnce(ffi::mpv_format, *mut c_void) -> T) -> T { - let flag: c_int = if self { 1 } else { 0 }; - f(Self::FORMAT, (&flag) as *const c_int as *mut c_void) - } - - fn from_data( - _format: ffi::mpv_format, - _data: *mut c_void, - _f: impl FnOnce(Option) -> T, - ) -> T { - todo!() - } -} - -impl FormatValue for f64 { - const FORMAT: ffi::mpv_format = ffi::mpv_format_MPV_FORMAT_DOUBLE; - - fn into_data(self, f: impl FnOnce(ffi::mpv_format, *mut c_void) -> T) -> T { - f(Self::FORMAT, (&self) as *const f64 as *mut c_void) - } - - fn from_data( - format: ffi::mpv_format, - data: *mut c_void, - f: impl FnOnce(Option) -> T, - ) -> T { - match format { - ffi::mpv_format_MPV_FORMAT_NONE => f(None), - ffi::mpv_format_MPV_FORMAT_DOUBLE => f(Some(unsafe { *(data as *mut f64) })), - _ => panic!(), - } - } -} +mod handle; +pub use handle::Handle; diff --git a/src/mpv/error.rs b/src/mpv/error.rs new file mode 100644 index 0000000..13343be --- /dev/null +++ b/src/mpv/error.rs @@ -0,0 +1,36 @@ +use super::ffi; +use std::ffi::{c_int, CStr}; +use std::fmt; + +#[derive(Copy, Clone)] +pub struct Error(c_int); + +impl Error { + pub(super) fn from_return_code(rc: c_int) -> Result<(), Self> { + if rc == 0 { + Ok(()) + } else { + Err(Self(rc)) + } + } + + pub fn as_str(self) -> &'static str { + unsafe { CStr::from_ptr(ffi::mpv_error_string(self.0)) } + .to_str() + .unwrap() + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + fmt::Display::fmt(&self.as_str(), f) + } +} + +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.debug_tuple("Error").field(&self.as_str()).finish() + } +} + +impl std::error::Error for Error {} diff --git a/src/mpv/format.rs b/src/mpv/format.rs new file mode 100644 index 0000000..a7bbbf0 --- /dev/null +++ b/src/mpv/format.rs @@ -0,0 +1,67 @@ +use super::ffi; +use std::ffi::{c_int, c_void, CString}; + +pub trait FormatValue: Sized { + const FORMAT: ffi::mpv_format; + fn into_data(self, f: impl FnOnce(ffi::mpv_format, *mut c_void) -> T) -> T; + fn from_data( + format: ffi::mpv_format, + data: *mut c_void, + f: impl FnOnce(Option) -> T, + ) -> T; +} + +impl<'a> FormatValue for &'a str { + const FORMAT: ffi::mpv_format = ffi::mpv_format_MPV_FORMAT_STRING; + + fn into_data(self, f: impl FnOnce(ffi::mpv_format, *mut c_void) -> T) -> T { + let str = CString::new(self).expect("null bytes in string"); + let str_ptr = str.as_ptr(); + f(Self::FORMAT, (&str_ptr) as *const _ as *mut c_void) + } + + fn from_data( + _format: ffi::mpv_format, + _data: *mut c_void, + _f: impl FnOnce(Option) -> T, + ) -> T { + todo!() + } +} + +impl FormatValue for bool { + const FORMAT: ffi::mpv_format = ffi::mpv_format_MPV_FORMAT_FLAG; + + fn into_data(self, f: impl FnOnce(ffi::mpv_format, *mut c_void) -> T) -> T { + let flag: c_int = if self { 1 } else { 0 }; + f(Self::FORMAT, (&flag) as *const c_int as *mut c_void) + } + + fn from_data( + _format: ffi::mpv_format, + _data: *mut c_void, + _f: impl FnOnce(Option) -> T, + ) -> T { + todo!() + } +} + +impl FormatValue for f64 { + const FORMAT: ffi::mpv_format = ffi::mpv_format_MPV_FORMAT_DOUBLE; + + fn into_data(self, f: impl FnOnce(ffi::mpv_format, *mut c_void) -> T) -> T { + f(Self::FORMAT, (&self) as *const f64 as *mut c_void) + } + + fn from_data( + format: ffi::mpv_format, + data: *mut c_void, + f: impl FnOnce(Option) -> T, + ) -> T { + match format { + ffi::mpv_format_MPV_FORMAT_NONE => f(None), + ffi::mpv_format_MPV_FORMAT_DOUBLE => f(Some(unsafe { *(data as *mut f64) })), + _ => panic!(), + } + } +} diff --git a/src/mpv/handle.rs b/src/mpv/handle.rs new file mode 100644 index 0000000..341ca6b --- /dev/null +++ b/src/mpv/handle.rs @@ -0,0 +1,165 @@ +use super::{ffi, Error, FormatValue}; +use event_listener::{Event, EventListener, IntoNotification}; +use std::cell::RefCell; +use std::ffi::{c_char, c_void, CStr, CString}; +use std::fmt; +use std::pin::Pin; +use std::ptr::NonNull; + +pub struct Handle { + inner: NonNull, + wakeup: Pin>, // the wakeup callback holds a pointer to this + wait_event_cell: RefCell<()>, +} + +// The client API is generally fully thread-safe, unless otherwise noted. +//unsafe impl Send for Handle {} +//unsafe impl Sync for Handle {} +// ........but we'd rather keep this in a single thread to play better with gtk + +impl Default for Handle { + fn default() -> Self { + Self::new() + } +} + +impl fmt::Debug for Handle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.debug_tuple("Handle").field(&self.client_name()).finish() + } +} + +impl Handle { + pub fn new() -> Self { + let inner = + NonNull::new(unsafe { ffi::mpv_create() }).expect("could not create mpv handle"); + + let wakeup = Box::pin(Event::new()); + + extern "C" fn wakeup_callback(d: *mut c_void) { + let d = d as *const Event; + let wakeup = unsafe { &*d }; + wakeup.notify(1_u32.relaxed()); // mpv has its own synchronization, trust it + } + unsafe { + ffi::mpv_set_wakeup_callback( + inner.as_ptr(), + Some(wakeup_callback), + std::ptr::from_ref::(&wakeup) as *mut c_void, + ); + } + + // set up verbose logging for now + Error::from_return_code(unsafe { + ffi::mpv_request_log_messages(inner.as_ptr(), c"v".as_ptr()) + }) + .unwrap(); + + // TODO: maybe we need to set something before initialization, but idk + // also wed need a builder, since "Note that you should avoid doing concurrent accesses on the uninitialized client handle." + Error::from_return_code(unsafe { ffi::mpv_initialize(inner.as_ptr()) }) + .expect("could not initialize mpv handle"); + + Self { + inner, + wakeup, + wait_event_cell: RefCell::new(()), + } + } + + pub fn client_name(&self) -> &str { + unsafe { CStr::from_ptr(ffi::mpv_client_name(self.inner.as_ptr())) } + .to_str() + .unwrap() + } + + pub fn client_id(&self) -> i64 { + unsafe { ffi::mpv_client_id(self.inner.as_ptr()) } + } + + pub fn set_property(&self, name: &str, value: impl FormatValue) -> Result<(), Error> { + // need to add zero terminator + let name = CString::new(name).expect("null bytes in property name"); + value.into_data(|format, data| { + Error::from_return_code(unsafe { + ffi::mpv_set_property(self.inner.as_ptr(), name.as_ptr(), format, data) + }) + }) + } + + pub fn command<'a>(&self, args: impl IntoIterator) -> Result<(), Error> { + // add zero terminators to Everything + let args: Vec = args + .into_iter() + .map(CString::new) + .map(Result::unwrap) + .collect(); + let mut args: Vec<*const c_char> = args + .iter() + .map(CString::as_c_str) + .map(CStr::as_ptr) + .collect(); + // must be null terminated + args.push(std::ptr::null_mut()); + let args: *mut *const c_char = args.as_mut_ptr(); + + Error::from_return_code(unsafe { ffi::mpv_command(self.inner.as_ptr(), args) }) + } + + pub fn tick(&self) -> Option { + // take listener before we drain the event queue, so we don't miss any notifications + let listener = self.wakeup.listen(); + let borrowed = self + .wait_event_cell + .try_borrow_mut() + .expect("Mpv::tick is not reentrant"); + + loop { + let event = unsafe { &*ffi::mpv_wait_event(self.inner.as_ptr(), 0.0) }; + + match event.event_id { + ffi::mpv_event_id_MPV_EVENT_NONE => break, + + ffi::mpv_event_id_MPV_EVENT_LOG_MESSAGE => { + let data = unsafe { &*(event.data as *mut ffi::mpv_event_log_message) }; + // TODO: actual logging? + let prefix = unsafe { CStr::from_ptr(data.prefix) }.to_str().unwrap(); + let level = unsafe { CStr::from_ptr(data.level) }.to_str().unwrap(); + let text = unsafe { CStr::from_ptr(data.text) }.to_str().unwrap(); + print!("[{prefix}] {level}: {text}"); + } + + ffi::mpv_event_id_MPV_EVENT_SHUTDOWN => { + return None; // good bye forever + } + + 11 => { /* deprecated, ignore */ } + + _ => todo!("event {}", event.event_id), + } + } + + drop(borrowed); // make sure the borrow is held until here + Some(listener) + } +} + +impl Drop for Handle { + fn drop(&mut self) { + println!("dropping MpvHandle"); + + // let any executor ticking tasks know we're ded + self.wakeup.notify(u32::MAX.relaxed()); + + // just in case + unsafe { + ffi::mpv_wait_async_requests(self.inner.as_ptr()); + } + // drain event queue (we're &mut so we know we have exclusive access) + self.tick(); + + unsafe { + ffi::mpv_destroy(self.inner.as_ptr()); + } + } +}