diff --git a/lektord/src/c_wrapper/abort.rs b/lektord/src/c_wrapper/abort.rs new file mode 100644 index 0000000000000000000000000000000000000000..2dd284464ae030e52ee4e156c754128284d9ba2d --- /dev/null +++ b/lektord/src/c_wrapper/abort.rs @@ -0,0 +1,6 @@ +/// So the C/C++ part of the code can call the Rust panic and unwind things. With that tokio can +/// catch an abort and stay alive. +#[export_name = "___lkt_abort"] +pub(super) extern "C" fn lkt_abort() { + panic!() +} diff --git a/lektord/src/c_wrapper/commands.rs b/lektord/src/c_wrapper/commands.rs new file mode 100644 index 0000000000000000000000000000000000000000..ca218d47b84b0ea2da68c5f641e38a43a2893f86 --- /dev/null +++ b/lektord/src/c_wrapper/commands.rs @@ -0,0 +1,142 @@ +use super::{PlayerEvent, STATE}; +use anyhow::{bail, Error, Result}; +use lektor_nkdb::PlayState; +use lektor_utils::*; +use std::{ffi::*, path::Path, ptr::NonNull}; + +/// Safe wrapper around `mod_stop_playback`. Stops the playback. Same as [player_toggle_pause], don't update +/// the database yourself, it will be done by a callback chain. +pub(crate) fn player_stop() -> Result<()> { + extern "C" { + fn mod_stop_playback() -> c_int; + } + if 0 != unsafe { mod_stop_playback() } { + bail!("failed to stop playback") + } + Ok(()) +} + +/// Safe wrapper around `mod_set_paused`. Set the pause state of the player. Same remark as +/// [player_toggle_pause], don't update the database yourself, it will be done by a callback chain +/// latter. +pub(crate) fn player_set_paused(state: PlayState) -> Result<()> { + extern "C" { + fn mod_set_paused(_: c_int) -> c_int; + } + let paused = match state { + PlayState::Stop => bail!("can't convert stop into a paused flag"), + PlayState::Play => 0 as c_int, + PlayState::Pause => 1 as c_int, + }; + if 0 != unsafe { mod_set_paused(paused) } { + bail!("failed to set paused state to {state:?}") + } + Ok(()) +} + +/// Safe wrapper around `mod_toggle_pause`. Send the toggle signal to the player. No need to update +/// the database in addition to this call, the database will be updated when the change will be +/// made in the mpv player. +pub(crate) fn player_toggle_pause() -> Result<()> { + extern "C" { + fn mod_toggle_pause() -> c_int; + } + if 0 != unsafe { mod_toggle_pause() } { + anyhow::bail!("failed to toggle pause state") + } + Ok(()) +} + +/// Safe wrapper around `mod_set_position` Set the position in the player module. +#[allow(dead_code)] +pub(crate) fn player_set_position(position: i64) -> Result<()> { + extern "C" { + fn mod_set_position(_: c_int) -> c_int; + } + let position = c_int::try_from(position)?; + if 0 != unsafe { mod_set_position(position) } { + anyhow::bail!("failed to set player position to {position}s") + } + Ok(()) +} + +/// Safe wrapper around `mod_get_duration` Get the duration of the current playing file. +pub(crate) fn player_get_duration() -> Result<i64> { + extern "C" { + fn mod_get_duration(_: NonNull<c_int>) -> c_int; + } + let mut duration: c_int = 0; + if 0 != unsafe { mod_get_duration(NonNull::new_unchecked((&mut duration) as *mut _)) } { + anyhow::bail!("failed to get current kara duration") + } + Ok(duration.into()) +} + +/// Safe wrapper around `mod_get_elapsed` Get the duration since the begin of the kara. +pub(crate) fn player_get_elapsed() -> Result<i64> { + extern "C" { + fn mod_get_elapsed(_: NonNull<c_int>) -> c_int; + } + let mut elapsed: c_int = 0; + if 0 != unsafe { mod_get_elapsed(NonNull::new_unchecked((&mut elapsed) as *mut _)) } { + anyhow::bail!("failed to get current kara elapsed time since start of file") + } + Ok(elapsed.into()) +} + +/// Safe wrapper around `mod_load_file` Tell the player to load a file by its path. +pub(crate) fn player_load_file(path: impl AsRef<Path>, id: u64) -> Result<()> { + extern "C" { + fn mod_load_file(_: NonNull<c_char>, _: u64) -> c_int; + } + let path = path + .as_ref() + .to_str() + .ok_or_else(|| Error::msg("path contained non-utf8 characters"))?; + let cstr = CString::new(path)?; + if 0 != unsafe { mod_load_file(NonNull::new_unchecked(cstr.as_ptr() as *mut _), id) } { + anyhow::bail!("failed load file {path}") + } + Ok(()) +} + +/// Safe wrapper around `mod_set_volume` Set the volume of the player. +#[allow(dead_code)] +pub(crate) fn player_set_playback_volume(vol: i64) -> Result<()> { + extern "C" { + fn mod_set_volume(_: c_int) -> c_int; + } + let vol = c_int::try_from(vol)?.clamp(0, 100); + if 0 != unsafe { mod_set_volume(vol) } { + anyhow::bail!("failed to set player volume to {vol}%") + } + Ok(()) +} + +fn send_msg(msg: PlayerEvent) { + if let Some((sender, _)) = unsafe { STATE.get() } { + if let Err(err) = sender.blocking_send(msg) { + log::error!("failed to send msg {msg:?}: {err}"); + } + } else { + log::error!("no lektord state was set for the player module") + } +} + +/// Set the play state in the database, the signal is send from the player. +#[no_mangle] +pub(super) extern "C" fn lkt_toggle_play_state(state: c_int) { + send_msg(PlayerEvent::SetPlayState(state)) +} + +/// The player needs to play the next kara in the queue. +#[no_mangle] +pub(super) extern "C" fn lkt_play_next() { + send_msg(PlayerEvent::PlayNext) +} + +/// The player needs to play the previous kara in the queue or in the history. +#[no_mangle] +pub(super) extern "C" fn lkt_play_prev() { + send_msg(PlayerEvent::PlayPrev) +} diff --git a/lektord/src/c_wrapper/mod.rs b/lektord/src/c_wrapper/mod.rs index e5be5087b8ab9f6bf6f36dd0d8051053b0ad6da4..83386bb3793f6f7fd236180771f1e691ab112872 100644 --- a/lektord/src/c_wrapper/mod.rs +++ b/lektord/src/c_wrapper/mod.rs @@ -1,19 +1,22 @@ //! Interface with the C/C++ part of the code. The only place we allow unsafe code in this crate. use crate::LektorStatePtr; -use anyhow::{bail, Error, Result}; +use anyhow::{bail, Result}; use lektor_nkdb::PlayState; use lektor_utils::{config::LektorPlayerConfig, *}; use std::{ ffi::*, - path::Path, ptr::NonNull, sync::{Arc, OnceLock}, }; +mod abort; +mod commands; mod loging; mod playstate; -use playstate::*; + +use self::{abort::*, playstate::*}; +pub(crate) use commands::*; /// The lektord state pointer to pass to the player module. Should be better to pass to its /// constructor... @@ -159,147 +162,3 @@ pub async fn close_player_module() -> Result<()> { Ok(()) } - -/// Safe wrapper around `mod_stop_playback`. Stops the playback. Same as [player_toggle_pause], don't update -/// the database yourself, it will be done by a callback chain. -pub(crate) fn player_stop() -> Result<()> { - extern "C" { - fn mod_stop_playback() -> c_int; - } - if 0 != unsafe { mod_stop_playback() } { - bail!("failed to stop playback") - } - Ok(()) -} - -/// Safe wrapper around `mod_set_paused`. Set the pause state of the player. Same remark as -/// [player_toggle_pause], don't update the database yourself, it will be done by a callback chain -/// latter. -pub(crate) fn player_set_paused(state: PlayState) -> Result<()> { - extern "C" { - fn mod_set_paused(_: c_int) -> c_int; - } - let paused = match state { - PlayState::Stop => bail!("can't convert stop into a paused flag"), - PlayState::Play => 0 as c_int, - PlayState::Pause => 1 as c_int, - }; - if 0 != unsafe { mod_set_paused(paused) } { - bail!("failed to set paused state to {state:?}") - } - Ok(()) -} - -/// Safe wrapper around `mod_toggle_pause`. Send the toggle signal to the player. No need to update -/// the database in addition to this call, the database will be updated when the change will be -/// made in the mpv player. -pub(crate) fn player_toggle_pause() -> Result<()> { - extern "C" { - fn mod_toggle_pause() -> c_int; - } - if 0 != unsafe { mod_toggle_pause() } { - anyhow::bail!("failed to toggle pause state") - } - Ok(()) -} - -/// Safe wrapper around `mod_set_position` Set the position in the player module. -#[allow(dead_code)] -pub(crate) fn player_set_position(position: i64) -> Result<()> { - extern "C" { - fn mod_set_position(_: c_int) -> c_int; - } - let position = c_int::try_from(position)?; - if 0 != unsafe { mod_set_position(position) } { - anyhow::bail!("failed to set player position to {position}s") - } - Ok(()) -} - -/// Safe wrapper around `mod_get_duration` Get the duration of the current playing file. -pub(crate) fn player_get_duration() -> Result<i64> { - extern "C" { - fn mod_get_duration(_: NonNull<c_int>) -> c_int; - } - let mut duration: c_int = 0; - if 0 != unsafe { mod_get_duration(NonNull::new_unchecked((&mut duration) as *mut _)) } { - anyhow::bail!("failed to get current kara duration") - } - Ok(duration.into()) -} - -/// Safe wrapper around `mod_get_elapsed` Get the duration since the begin of the kara. -pub(crate) fn player_get_elapsed() -> Result<i64> { - extern "C" { - fn mod_get_elapsed(_: NonNull<c_int>) -> c_int; - } - let mut elapsed: c_int = 0; - if 0 != unsafe { mod_get_elapsed(NonNull::new_unchecked((&mut elapsed) as *mut _)) } { - anyhow::bail!("failed to get current kara elapsed time since start of file") - } - Ok(elapsed.into()) -} - -/// Safe wrapper around `mod_load_file` Tell the player to load a file by its path. -pub(crate) fn player_load_file(path: impl AsRef<Path>, id: u64) -> Result<()> { - extern "C" { - fn mod_load_file(_: NonNull<c_char>, _: u64) -> c_int; - } - let path = path - .as_ref() - .to_str() - .ok_or_else(|| Error::msg("path contained non-utf8 characters"))?; - let cstr = CString::new(path)?; - if 0 != unsafe { mod_load_file(NonNull::new_unchecked(cstr.as_ptr() as *mut _), id) } { - anyhow::bail!("failed load file {path}") - } - Ok(()) -} - -/// Safe wrapper around `mod_set_volume` Set the volume of the player. -#[allow(dead_code)] -pub(crate) fn player_set_playback_volume(vol: i64) -> Result<()> { - extern "C" { - fn mod_set_volume(_: c_int) -> c_int; - } - let vol = c_int::try_from(vol)?.clamp(0, 100); - if 0 != unsafe { mod_set_volume(vol) } { - anyhow::bail!("failed to set player volume to {vol}%") - } - Ok(()) -} - -fn send_msg(msg: PlayerEvent) { - if let Some((sender, _)) = unsafe { STATE.get() } { - if let Err(err) = sender.blocking_send(msg) { - log::error!("failed to send msg {msg:?}: {err}"); - } - } else { - log::error!("no lektord state was set for the player module") - } -} - -/// Set the play state in the database, the signal is send from the player. -#[no_mangle] -extern "C" fn lkt_toggle_play_state(state: c_int) { - send_msg(PlayerEvent::SetPlayState(state)) -} - -/// The player needs to play the next kara in the queue. -#[no_mangle] -extern "C" fn lkt_play_next() { - send_msg(PlayerEvent::PlayNext) -} - -/// The player needs to play the previous kara in the queue or in the history. -#[no_mangle] -extern "C" fn lkt_play_prev() { - send_msg(PlayerEvent::PlayPrev) -} - -/// So the C/C++ part of the code can call the Rust panic and unwind things. With that tokio can -/// catch an abort and stay alive. -#[export_name = "___lkt_abort"] -extern "C" fn lkt_abort() { - panic!() -}