use jack_sys as j;
use std::{
ffi,
panic::catch_unwind,
sync::atomic::{AtomicBool, Ordering},
};
use crate::{Client, ClientStatus, Control, Error, Frames, PortId, ProcessScope};
pub trait NotificationHandler: Send {
fn thread_init(&self, _: &Client) {}
unsafe fn shutdown(&mut self, _status: ClientStatus, _reason: &str) {}
fn freewheel(&mut self, _: &Client, _is_freewheel_enabled: bool) {}
fn sample_rate(&mut self, _: &Client, _srate: Frames) -> Control {
Control::Continue
}
fn client_registration(&mut self, _: &Client, _name: &str, _is_registered: bool) {}
fn port_registration(&mut self, _: &Client, _port_id: PortId, _is_registered: bool) {}
fn port_rename(
&mut self,
_: &Client,
_port_id: PortId,
_old_name: &str,
_new_name: &str,
) -> Control {
Control::Continue
}
fn ports_connected(
&mut self,
_: &Client,
_port_id_a: PortId,
_port_id_b: PortId,
_are_connected: bool,
) {
}
fn graph_reorder(&mut self, _: &Client) -> Control {
Control::Continue
}
fn xrun(&mut self, _: &Client) -> Control {
Control::Continue
}
}
pub trait ProcessHandler: Send {
const SLOW_SYNC: bool = false;
fn process(&mut self, _: &Client, _process_scope: &ProcessScope) -> Control;
fn buffer_size(&mut self, _: &Client, _size: Frames) -> Control {
Control::Continue
}
fn sync(
&mut self,
_: &Client,
_state: crate::TransportState,
_pos: &crate::TransportPosition,
) -> bool {
true
}
}
unsafe extern "C" fn thread_init_callback<N, P>(data: *mut libc::c_void)
where
N: 'static + Send + Sync + NotificationHandler,
P: 'static + Send + ProcessHandler,
{
let res = catch_unwind(|| {
let Some(ctx) = CallbackContext::<N, P>::from_raw(data) else {
return;
};
ctx.notification.thread_init(&ctx.client);
});
if let Err(err) = res {
if let Some(ctx) = CallbackContext::<N, P>::from_raw(data) {
ctx.mark_invalid(true)
}
eprintln!("{err:?}");
std::mem::forget(err);
}
}
unsafe extern "C" fn shutdown<N, P>(
code: j::jack_status_t,
reason: *const libc::c_char,
data: *mut libc::c_void,
) where
N: 'static + Send + Sync + NotificationHandler,
P: 'static + Send + ProcessHandler,
{
let res = catch_unwind(|| {
let Some(ctx) = CallbackContext::<N, P>::from_raw(data) else {
return;
};
let cstr = ffi::CStr::from_ptr(reason);
let reason_str = cstr.to_str().unwrap_or("Failed to interpret error.");
ctx.notification.shutdown(
ClientStatus::from_bits(code).unwrap_or_else(ClientStatus::empty),
reason_str,
);
});
if let Err(err) = res {
if let Some(ctx) = CallbackContext::<N, P>::from_raw(data) {
ctx.mark_invalid(true)
}
eprintln!("{err:?}");
std::mem::forget(err);
}
}
unsafe extern "C" fn process<N, P>(n_frames: Frames, data: *mut libc::c_void) -> libc::c_int
where
N: 'static + Send + Sync + NotificationHandler,
P: 'static + Send + ProcessHandler,
{
let res = catch_unwind(|| {
let Some(ctx) = CallbackContext::<N, P>::from_raw(data) else {
return Control::Quit;
};
let scope = ProcessScope::from_raw(n_frames, ctx.client.raw());
let c = ctx.process.process(&ctx.client, &scope);
if c == Control::Quit {
ctx.mark_invalid(false);
}
c
});
match res {
Ok(res) => res.to_ffi(),
Err(err) => {
if let Some(ctx) = CallbackContext::<N, P>::from_raw(data) {
ctx.mark_invalid(true)
}
eprintln!("JACK process callback panicked.\n{err:?}");
std::mem::forget(err);
Control::Quit.to_ffi()
}
}
}
unsafe extern "C" fn sync<N, P>(
state: jack_sys::jack_transport_state_t,
pos: *mut jack_sys::jack_position_t,
data: *mut libc::c_void,
) -> libc::c_int
where
N: 'static + Send + Sync + NotificationHandler,
P: 'static + Send + ProcessHandler,
{
let res = catch_unwind(|| {
let Some(ctx) = CallbackContext::<N, P>::from_raw(data) else {
return false;
};
let is_ready = ctx.process.sync(
&ctx.client,
crate::Transport::state_from_ffi(state),
&*(pos as *mut crate::TransportPosition),
);
if !is_ready {
ctx.mark_invalid(false);
}
is_ready
});
match res {
Ok(true) => 1,
Ok(false) => 0,
Err(err) => {
if let Some(ctx) = CallbackContext::<N, P>::from_raw(data) {
ctx.mark_invalid(true)
}
eprintln!("{err:?}");
std::mem::forget(err);
0
}
}
}
unsafe extern "C" fn freewheel<N, P>(starting: libc::c_int, data: *mut libc::c_void)
where
N: 'static + Send + Sync + NotificationHandler,
P: 'static + Send + ProcessHandler,
{
let res = catch_unwind(|| {
let Some(ctx) = CallbackContext::<N, P>::from_raw(data) else {
return;
};
let is_starting = !matches!(starting, 0);
ctx.notification.freewheel(&ctx.client, is_starting);
});
if let Err(err) = res {
if let Some(ctx) = CallbackContext::<N, P>::from_raw(data) {
ctx.mark_invalid(true)
}
eprintln!("{err:?}");
std::mem::forget(err);
}
}
unsafe extern "C" fn buffer_size<N, P>(n_frames: Frames, data: *mut libc::c_void) -> libc::c_int
where
N: 'static + Send + Sync + NotificationHandler,
P: 'static + Send + ProcessHandler,
{
let res = catch_unwind(|| {
let Some(ctx) = CallbackContext::<N, P>::from_raw(data) else {
return Control::Quit;
};
let c = ctx.process.buffer_size(&ctx.client, n_frames);
if c == Control::Quit {
ctx.mark_invalid(false);
}
c
});
match res {
Ok(c) => c.to_ffi(),
Err(err) => {
if let Some(ctx) = CallbackContext::<N, P>::from_raw(data) {
ctx.mark_invalid(true)
}
eprintln!("{err:?}");
std::mem::forget(err);
Control::Quit.to_ffi()
}
}
}
unsafe extern "C" fn sample_rate<N, P>(n_frames: Frames, data: *mut libc::c_void) -> libc::c_int
where
N: 'static + Send + Sync + NotificationHandler,
P: 'static + Send + ProcessHandler,
{
let res = catch_unwind(|| {
let Some(ctx) = CallbackContext::<N, P>::from_raw(data) else {
return Control::Quit;
};
let c = ctx.notification.sample_rate(&ctx.client, n_frames);
if c == Control::Quit {
ctx.mark_invalid(false);
}
c
});
match res {
Ok(c) => c.to_ffi(),
Err(err) => {
if let Some(ctx) = CallbackContext::<N, P>::from_raw(data) {
ctx.mark_invalid(true)
}
eprintln!("{err:?}");
std::mem::forget(err);
Control::Quit.to_ffi()
}
}
}
unsafe extern "C" fn client_registration<N, P>(
name: *const libc::c_char,
register: libc::c_int,
data: *mut libc::c_void,
) where
N: 'static + Send + Sync + NotificationHandler,
P: 'static + Send + ProcessHandler,
{
let res = catch_unwind(|| {
let Some(ctx) = CallbackContext::<N, P>::from_raw(data) else {
return;
};
let name = ffi::CStr::from_ptr(name).to_str().unwrap();
let register = !matches!(register, 0);
ctx.notification
.client_registration(&ctx.client, name, register);
});
if let Err(err) = res {
if let Some(ctx) = CallbackContext::<N, P>::from_raw(data) {
ctx.mark_invalid(true)
}
eprintln!("{err:?}");
std::mem::forget(err);
}
}
unsafe extern "C" fn port_registration<N, P>(
port_id: PortId,
register: libc::c_int,
data: *mut libc::c_void,
) where
N: 'static + Send + Sync + NotificationHandler,
P: 'static + Send + ProcessHandler,
{
let res = catch_unwind(|| {
let Some(ctx) = CallbackContext::<N, P>::from_raw(data) else {
return;
};
let register = !matches!(register, 0);
ctx.notification
.port_registration(&ctx.client, port_id, register);
});
if let Err(err) = res {
if let Some(ctx) = CallbackContext::<N, P>::from_raw(data) {
ctx.mark_invalid(true)
}
eprintln!("{err:?}");
std::mem::forget(err);
}
}
#[allow(dead_code)] unsafe extern "C" fn port_rename<N, P>(
port_id: PortId,
old_name: *const libc::c_char,
new_name: *const libc::c_char,
data: *mut libc::c_void,
) -> libc::c_int
where
N: 'static + Send + Sync + NotificationHandler,
P: 'static + Send + ProcessHandler,
{
let res = catch_unwind(|| {
let Some(ctx) = CallbackContext::<N, P>::from_raw(data) else {
return Control::Quit;
};
let old_name = ffi::CStr::from_ptr(old_name).to_str().unwrap();
let new_name = ffi::CStr::from_ptr(new_name).to_str().unwrap();
let c = ctx
.notification
.port_rename(&ctx.client, port_id, old_name, new_name);
if c == Control::Quit {
ctx.mark_invalid(false);
}
c
});
match res {
Ok(c) => c.to_ffi(),
Err(err) => {
if let Some(ctx) = CallbackContext::<N, P>::from_raw(data) {
ctx.mark_invalid(true)
}
eprintln!("{err:?}");
std::mem::forget(err);
Control::Quit.to_ffi()
}
}
}
unsafe extern "C" fn port_connect<N, P>(
port_id_a: PortId,
port_id_b: PortId,
connect: libc::c_int,
data: *mut libc::c_void,
) where
N: 'static + Send + Sync + NotificationHandler,
P: 'static + Send + ProcessHandler,
{
let res = catch_unwind(|| {
let Some(ctx) = CallbackContext::<N, P>::from_raw(data) else {
return;
};
let are_connected = !matches!(connect, 0);
ctx.notification
.ports_connected(&ctx.client, port_id_a, port_id_b, are_connected);
});
if let Err(err) = res {
if let Some(ctx) = CallbackContext::<N, P>::from_raw(data) {
ctx.mark_invalid(true)
}
eprintln!("{err:?}");
std::mem::forget(err);
}
}
unsafe extern "C" fn graph_order<N, P>(data: *mut libc::c_void) -> libc::c_int
where
N: 'static + Send + Sync + NotificationHandler,
P: 'static + Send + ProcessHandler,
{
let res = catch_unwind(|| {
let Some(ctx) = CallbackContext::<N, P>::from_raw(data) else {
return Control::Quit;
};
let c = ctx.notification.graph_reorder(&ctx.client);
if c == Control::Quit {
ctx.mark_invalid(false);
}
c
});
match res {
Ok(c) => c.to_ffi(),
Err(err) => {
if let Some(ctx) = CallbackContext::<N, P>::from_raw(data) {
ctx.mark_invalid(true)
}
eprintln!("{err:?}");
std::mem::forget(err);
Control::Quit.to_ffi()
}
}
}
unsafe extern "C" fn xrun<N, P>(data: *mut libc::c_void) -> libc::c_int
where
N: 'static + Send + Sync + NotificationHandler,
P: 'static + Send + ProcessHandler,
{
let res = catch_unwind(|| {
let Some(ctx) = CallbackContext::<N, P>::from_raw(data) else {
return Control::Quit;
};
let c = ctx.notification.xrun(&ctx.client);
if c == Control::Quit {
ctx.mark_invalid(false);
}
c
});
match res {
Ok(c) => c.to_ffi(),
Err(err) => {
if let Some(ctx) = CallbackContext::<N, P>::from_raw(data) {
ctx.mark_invalid(true)
}
eprintln!("{err:?}");
std::mem::forget(err);
Control::Quit.to_ffi()
}
}
}
pub unsafe fn clear_callbacks(client: *mut j::jack_client_t) -> Result<(), Error> {
j::jack_set_thread_init_callback(client, None, std::ptr::null_mut());
j::jack_set_process_callback(client, None, std::ptr::null_mut());
Ok(())
}
pub struct CallbackContext<N, P> {
pub client: Client,
pub notification: N,
pub process: P,
pub is_valid_for_callback: AtomicBool,
pub has_panic: AtomicBool,
}
impl<N, P> CallbackContext<N, P>
where
N: 'static + Send + Sync + NotificationHandler,
P: 'static + Send + ProcessHandler,
{
pub unsafe fn from_raw<'a>(ptr: *mut libc::c_void) -> Option<&'a mut CallbackContext<N, P>> {
debug_assert!(!ptr.is_null());
let obj_ptr = ptr as *mut CallbackContext<N, P>;
let obj_ref = &mut *obj_ptr;
if obj_ref.is_valid_for_callback.load(Ordering::Relaxed) {
Some(obj_ref)
} else {
None
}
}
#[cold]
pub fn mark_invalid(&mut self, did_panic: bool) {
self.is_valid_for_callback.store(true, Ordering::Relaxed);
self.has_panic.store(did_panic, Ordering::Relaxed);
}
fn raw(b: &mut Box<Self>) -> *mut libc::c_void {
let ptr: *mut Self = b.as_mut();
ptr as *mut libc::c_void
}
pub unsafe fn register_callbacks(b: &mut Box<Self>) -> Result<(), Error> {
let data_ptr = CallbackContext::raw(b);
let client = b.client.raw();
j::jack_set_thread_init_callback(client, Some(thread_init_callback::<N, P>), data_ptr);
j::jack_on_info_shutdown(client, Some(shutdown::<N, P>), data_ptr);
j::jack_set_process_callback(client, Some(process::<N, P>), data_ptr);
if P::SLOW_SYNC {
j::jack_set_sync_callback(client, Some(sync::<N, P>), data_ptr);
}
j::jack_set_freewheel_callback(client, Some(freewheel::<N, P>), data_ptr);
j::jack_set_buffer_size_callback(client, Some(buffer_size::<N, P>), data_ptr);
j::jack_set_sample_rate_callback(client, Some(sample_rate::<N, P>), data_ptr);
j::jack_set_client_registration_callback(
client,
Some(client_registration::<N, P>),
data_ptr,
);
j::jack_set_port_registration_callback(client, Some(port_registration::<N, P>), data_ptr);
j::jack_set_port_connect_callback(client, Some(port_connect::<N, P>), data_ptr);
j::jack_set_graph_order_callback(client, Some(graph_order::<N, P>), data_ptr);
j::jack_set_xrun_callback(client, Some(xrun::<N, P>), data_ptr);
Ok(())
}
}