hauuu so complicated

This commit is contained in:
Erica Z 2024-11-02 15:43:43 +01:00
parent 8a6a056b36
commit 4e2c992cc9
2 changed files with 215 additions and 160 deletions

View file

@ -1,11 +1,13 @@
mod imp { mod imp {
use crate::ui; use crate::{mpv, ui};
use adw::prelude::*; use adw::{prelude::*, subclass::prelude::*};
use adw::subclass::prelude::*;
use gtk::glib; use gtk::glib;
use std::cell::RefCell;
#[derive(Default)] #[derive(Default)]
pub struct Application; pub struct Application {
mpv: RefCell<mpv::Handle>,
}
#[glib::object_subclass] #[glib::object_subclass]
impl ObjectSubclass for Application { impl ObjectSubclass for Application {
@ -24,6 +26,30 @@ mod imp {
let window = ui::Window::new(self.obj().as_ref()); let window = ui::Window::new(self.obj().as_ref());
window.present(); window.present();
let mut mpv = self.mpv.borrow_mut();
mpv.initialize().expect("could not initialize mpv");
mpv.set_property("audio-client-name", "audrey").unwrap();
mpv.set_property("user-agent", crate::USER_AGENT).unwrap();
mpv.set_property("video", false).unwrap();
mpv.set_property("prefetch-playlist", true).unwrap();
mpv.set_property("gapless-audio", true).unwrap();
// TODO: observe properties
mpv.observe_property("time-pos", |time_pos: Option<f64>| {
println!("new time pos: {time_pos:?}")
})
.unwrap();
glib::spawn_future_local(glib::clone!(
#[weak(rename_to = app)]
self,
async move {
loop {
app.mpv.borrow().wait().await;
app.mpv.borrow_mut().tick();
}
}
));
glib::spawn_future_local(async move { glib::spawn_future_local(async move {
let conn = zbus::connection::Builder::session() let conn = zbus::connection::Builder::session()
.expect("could not connect to the session bus") .expect("could not connect to the session bus")
@ -66,6 +92,8 @@ mod imp {
impl GtkApplicationImpl for Application {} impl GtkApplicationImpl for Application {}
impl AdwApplicationImpl for Application {} impl AdwApplicationImpl for Application {}
impl Application {}
} }
use gtk::{gio, glib}; use gtk::{gio, glib};

View file

@ -1,12 +1,14 @@
mod ffi; mod ffi;
use std::ffi::{c_int, c_void, CStr, CString};
#[link(name = "mpv")] #[link(name = "mpv")]
extern "C" {} extern "C" {}
pub struct Error(std::ffi::c_int); pub struct Error(c_int);
impl Error { impl Error {
fn from_return_code(rc: std::ffi::c_int) -> Result<(), Self> { fn from_return_code(rc: c_int) -> Result<(), Self> {
if rc == 0 { if rc == 0 {
Ok(()) Ok(())
} else { } else {
@ -18,182 +20,207 @@ impl Error {
impl std::fmt::Debug for Error { impl std::fmt::Debug for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.debug_tuple("Error") f.debug_tuple("Error")
.field(unsafe { .field(unsafe { &CStr::from_ptr(ffi::mpv_error_string(self.0 as c_int)) })
&std::ffi::CStr::from_ptr(ffi::mpv_error_string(self.0 as std::ffi::c_int))
})
.finish() .finish()
} }
} }
use std::borrow::Cow; use std::pin::Pin;
use std::ptr::NonNull; use std::ptr::NonNull;
pub struct Handle(NonNull<ffi::mpv_handle>); pub struct Handle {
inner: NonNull<ffi::mpv_handle>,
_wakeup_send: Pin<Box<async_channel::Sender<()>>>, // the wakeup callback controls this one
wakeup_recv: async_channel::Receiver<()>,
observed_property_callbacks:
Vec<Pin<Box<Box<dyn FnMut(ffi::mpv_format, *mut c_void) + 'static>>>>,
}
// The client API is generally fully thread-safe, unless otherwise noted.
unsafe impl Send for Handle {}
unsafe impl Sync for Handle {}
impl Default for Handle {
fn default() -> Self {
Self::create()
}
}
impl Handle { impl Handle {
pub fn create() -> Self { pub fn create() -> Self {
Self(NonNull::new(unsafe { ffi::mpv_create() }).expect("could not create mpv handle")) let inner =
NonNull::new(unsafe { ffi::mpv_create() }).expect("could not create mpv handle");
let (wakeup_send, wakeup_recv) = async_channel::bounded(1);
let wakeup_send = Box::pin(wakeup_send);
extern "C" fn wakeup_callback(d: *mut c_void) {
let d = d as *const async_channel::Sender<()>;
let wakeup_send = unsafe { &*d };
match wakeup_send.try_send(()) {
Ok(()) => {}
Err(async_channel::TrySendError::Full(())) => {
// assume the main thread has already been notified
}
Err(async_channel::TrySendError::Closed(())) => {
panic!("wakeup channgel was closed before mpv handle was finalized");
}
}
}
unsafe {
ffi::mpv_set_wakeup_callback(
inner.as_ptr(),
Some(wakeup_callback),
std::ptr::from_ref::<async_channel::Sender<()>>(&wakeup_send) as *mut c_void,
);
}
Self {
inner: inner,
_wakeup_send: wakeup_send,
wakeup_recv: wakeup_recv,
observed_property_callbacks: vec![],
}
} }
pub fn initialize(&self) -> Result<(), Error> { pub fn initialize(&self) -> Result<(), Error> {
Error::from_return_code(unsafe { ffi::mpv_initialize(self.0.as_ptr()) }) Error::from_return_code(unsafe { ffi::mpv_initialize(self.inner.as_ptr()) })
} }
pub fn wait_event(&mut self, timeout: f64) -> Option<Event<'_>> { pub fn set_property<'a>(&self, name: &str, value: impl FormatValue) -> Result<(), Error> {
let event = unsafe { &*ffi::mpv_wait_event(self.0.as_ptr(), timeout) }; // need to add zero terminator
match event.event_id { let name = CString::new(name).expect("null bytes in property name");
0 => None, value.into_data(|format, data| {
1 => Some(Event::Shutdown), Error::from_return_code(unsafe {
2 => todo!("Event::LogMessage"), ffi::mpv_set_property(self.inner.as_ptr(), name.as_ptr(), format, data)
3 => Some(Event::GetPropertyReply( })
event.reply_userdata, })
Error::from_return_code(event.error).map(|()| { }
// TODO: some way to deregister these maybe??
pub fn observe_property<T>(
&mut self,
name: &str,
mut cb: impl FnMut(Option<T>) + 'static,
) -> Result<(), Error>
where
T: FormatValue,
{
let name = CString::new(name).unwrap();
let cb = Box::new(move |format, data| T::from_data(format, data, |x| cb(x)));
let cb = Box::<dyn FnMut(ffi::mpv_format, *mut c_void)>::from(cb);
let cb = Box::pin(cb);
let userdata =
std::ptr::from_ref::<Box<dyn FnMut(ffi::mpv_format, *mut c_void)>>(&cb) as usize as u64;
Error::from_return_code(unsafe {
ffi::mpv_observe_property(self.inner.as_ptr(), userdata, name.as_ptr(), T::FORMAT)
})?;
self.observed_property_callbacks.push(cb);
Ok(())
}
pub async fn wait(&self) {
match self.wakeup_recv.recv().await {
Ok(()) => {}
Err(async_channel::RecvError) => unreachable!(),
}
}
pub fn tick(&mut self) {
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_PROPERTY_CHANGE => {
let cb = unsafe {
&mut *(event.reply_userdata
as *mut Box<dyn FnMut(ffi::mpv_format, *mut c_void)>)
};
let data = unsafe { &*(event.data as *mut ffi::mpv_event_property) }; let data = unsafe { &*(event.data as *mut ffi::mpv_event_property) };
EventProperty { cb(data.format, data.data);
name: unsafe { std::ffi::CStr::from_ptr(data.name) } }
.to_str()
.unwrap(), 11 => { /* deprecated, ignore */ }
value: unsafe { FormatData::new(data.format, data.data) },
} _ => todo!("event {}", event.event_id),
}), }
)),
4 => todo!("Event::SetPropertyReply"),
5 => todo!("Event::CommandReply"),
6 => todo!("Event::StartFile"),
7 => todo!("Event::EndFile"),
8 => todo!("Event::FileLoaded"),
16 => todo!("Event::ClientMessage"),
17 => todo!("Event::VideoReconfig"),
18 => todo!("Event::AudioReconfig"),
20 => todo!("Event::Seek"),
21 => todo!("Event::PlaybackRestart"),
22 => todo!("Event::PropertyChange"),
24 => todo!("Event::QueueOverflow"),
25 => todo!("Event::Hook"),
_ => Some(Event::Ignore(event.event_id)),
} }
} }
} }
pub enum FormatData<'a> {
None,
String(Cow<'a, str>),
OsdString(&'a str),
Flag(bool),
Int64(i64),
Double(f64),
//Node(Node<'a>),
}
impl<'a> FormatData<'a> {
unsafe fn new(format: ffi::mpv_format, data: *mut std::ffi::c_void) -> Self {
match format {
0 => Self::None,
1 => {
let data = data as *mut *mut std::ffi::c_char;
Self::String(String::from_utf8_lossy(
std::ffi::CStr::from_ptr(*data).to_bytes(),
))
}
2 => {
let data = data as *mut *mut std::ffi::c_char;
Self::OsdString(
std::ffi::CStr::from_ptr(*data)
.to_str()
.expect("OSD string wasn't UTF-8"),
)
}
3 => {
let data = data as *mut std::ffi::c_int;
Self::Flag(match *data {
0 => false,
1 => true,
_ => panic!("invalid mpv flag value"),
})
}
4 => {
let data = data as *mut i64;
Self::Int64(*data)
}
5 => {
let data = data as *mut f64;
Self::Double(*data)
}
6 => todo!(),
7 => todo!(),
8 => todo!(),
9 => todo!(),
_ => panic!("invalid mpv format"),
}
}
}
pub struct EventProperty<'a> {
pub name: &'a str,
pub value: FormatData<'a>,
}
pub struct EventLogMessage<'a> {
pub prefix: &'a str,
pub level: &'a str,
pub text: &'a str,
pub log_level: i32,
}
pub enum EndFileReason {
Eof,
Stop,
Quit,
Error(Error),
Redirect,
}
pub struct EventStartFile {
pub playlist_entry_id: i64,
}
pub struct EventEndFile {
pub reason: EndFileReason,
pub playlist_entry_id: i64,
pub playlist_insert_id: i64,
pub playlist_insert_num_entries: i32,
}
pub struct EventClientMessage<'a> {
pub num_args: i32,
pub args: &'a [&'a str],
}
pub struct EventHook<'a> {
pub name: &'a str,
pub id: u64,
}
pub enum Event<'a> {
Shutdown,
LogMessage(EventLogMessage<'a>),
GetPropertyReply(u64, Result<EventProperty<'a>, Error>),
SetPropertyReply(u64, Result<(), Error>),
//CommandReply(u64, Result<Node<'a>, Error>),
StartFile(EventStartFile),
EndFile(EventEndFile),
FileLoaded,
ClientMessage(EventClientMessage<'a>),
VideoReconfig,
AudioReconfig,
Seek,
PlaybackRestart,
PropertyChange(u64, EventProperty<'a>),
QueueOverflow,
Hook(u64, EventHook<'a>),
// "Keep in mind that later ABI compatible releases might add new event
// types. These should be ignored by the API user."
Ignore(u32),
}
impl Drop for Handle { impl Drop for Handle {
fn drop(&mut self) { fn drop(&mut self) {
// destroy? terminate_destroy?? // destroy? terminate_destroy??
todo!() todo!()
} }
} }
pub trait FormatValue: Sized {
const FORMAT: ffi::mpv_format;
fn into_data<T>(self, f: impl FnOnce(ffi::mpv_format, *mut c_void) -> T) -> T;
fn from_data<T>(
format: ffi::mpv_format,
data: *mut c_void,
f: impl FnOnce(Option<Self>) -> T,
) -> T;
}
impl<'a> FormatValue for &'a str {
const FORMAT: ffi::mpv_format = ffi::mpv_format_MPV_FORMAT_STRING;
fn into_data<T>(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<T>(
_format: ffi::mpv_format,
_data: *mut c_void,
_f: impl FnOnce(Option<Self>) -> T,
) -> T {
todo!()
}
}
impl FormatValue for bool {
const FORMAT: ffi::mpv_format = ffi::mpv_format_MPV_FORMAT_FLAG;
fn into_data<T>(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<T>(
_format: ffi::mpv_format,
_data: *mut c_void,
_f: impl FnOnce(Option<Self>) -> T,
) -> T {
todo!()
}
}
impl FormatValue for f64 {
const FORMAT: ffi::mpv_format = ffi::mpv_format_MPV_FORMAT_DOUBLE;
fn into_data<T>(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<T>(
format: ffi::mpv_format,
data: *mut c_void,
f: impl FnOnce(Option<Self>) -> 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!(),
}
}
}