use std::ffi::c_char;
use std::ffi::c_void;
use std::net::Ipv4Addr;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use mx_remote::{Config, DeviceUid, EventHandler, Remote};
use crate::abi::{
fail, from_io, from_send, guard, mxr_bay_uid_t, mxr_result_t, mxr_uid_t, opt_str, put_str,
req_str,
};
use crate::events::{mxr_callbacks_t, Bridge};
pub const MXR_IP_STRING_LEN: usize = 16;
pub struct mxr_remote_t {
pub(crate) remote: Remote,
}
#[repr(C)]
pub struct mxr_config_t {
pub target_ip: *const c_char,
pub port: u16,
pub broadcast: bool,
pub local_ip: *const c_char,
pub interface: *const c_char,
pub name: *const c_char,
pub uid: *const c_char,
pub uid_path: *const c_char,
}
unsafe fn opt_ip(ptr: *const c_char, what: &str) -> Result<Option<Ipv4Addr>, mxr_result_t> {
let text = match unsafe { opt_str(ptr) }? {
Some(s) => s,
None => return Ok(None),
};
match Ipv4Addr::from_str(text) {
Ok(ip) => Ok(Some(ip)),
Err(_) => Err(fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
&format!("{what} is not an IPv4 address: {text:?}"),
)),
}
}
unsafe fn to_config(c: &mxr_config_t) -> Result<Config, mxr_result_t> {
let (target_ip, local_ip) = unsafe {
(
opt_ip(c.target_ip, "target_ip")?,
opt_ip(c.local_ip, "local_ip")?,
)
};
let (interface, name, uid_text, uid_path) = unsafe {
(
opt_str(c.interface)?,
opt_str(c.name)?,
opt_str(c.uid)?,
opt_str(c.uid_path)?,
)
};
let uid = match uid_text {
Some(text) => match DeviceUid::from_str(text) {
Ok(uid) => Some(uid),
Err(e) => return Err(fail(mxr_result_t::MXR_ERR_INVALID_ARGUMENT, &e.to_string())),
},
None => None,
};
let mut config = Config::default();
config.target_ip = target_ip;
config.port = (c.port != 0).then_some(c.port);
config.local_ip = local_ip;
config.interface = interface.map(str::to_owned);
config.broadcast = c.broadcast;
config.name = name.map(str::to_owned);
config.uid = uid;
config.uid_path = uid_path.map(PathBuf::from);
Ok(config)
}
#[no_mangle]
pub unsafe extern "C" fn mxr_remote_new(
config: *const mxr_config_t,
callbacks: *const mxr_callbacks_t,
userdata: *mut c_void,
) -> *mut mxr_remote_t {
guard(std::ptr::null_mut(), || {
let config = match unsafe { config.as_ref() } {
Some(c) => match unsafe { to_config(c) } {
Ok(c) => c,
Err(_) => return std::ptr::null_mut(),
},
None => Config::default(),
};
let handler: Arc<dyn EventHandler> = match unsafe { callbacks.as_ref() } {
Some(table) => Arc::new(Bridge::new(table, userdata)),
None => Arc::new(()),
};
match Remote::new(config, handler) {
Ok(remote) => Box::into_raw(Box::new(mxr_remote_t { remote })),
Err(e) => {
fail(mxr_result_t::MXR_ERR_IO, &e.to_string());
std::ptr::null_mut()
}
}
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_remote_start(remote: *const mxr_remote_t) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| from_io(r.remote.start()))
}
#[no_mangle]
pub unsafe extern "C" fn mxr_remote_close(remote: *const mxr_remote_t) {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
r.remote.close();
mxr_result_t::MXR_OK
});
}
#[no_mangle]
pub unsafe extern "C" fn mxr_remote_free(remote: *mut mxr_remote_t) {
guard((), || {
if remote.is_null() {
return;
}
drop(unsafe { Box::from_raw(remote) });
});
}
pub(crate) fn with(
remote: Option<&mxr_remote_t>,
body: impl FnOnce(&mxr_remote_t) -> mxr_result_t,
) -> mxr_result_t {
guard(mxr_result_t::MXR_ERR_PANIC, || match remote {
Some(r) => body(r),
None => fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"the client handle is null",
),
})
}
unsafe fn copy_out<T: Copy, U: Copy + Into<T>>(items: &[U], out: *mut T, cap: usize) -> usize {
if !out.is_null() {
let dst = unsafe { std::slice::from_raw_parts_mut(out, cap) };
for (slot, item) in dst.iter_mut().zip(items) {
*slot = (*item).into();
}
}
items.len()
}
#[no_mangle]
pub unsafe extern "C" fn mxr_remote_uid(
remote: *const mxr_remote_t,
out: *mut mxr_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
if out.is_null() {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"uid output pointer is null",
);
}
unsafe { *out = r.remote.uid().into() };
mxr_result_t::MXR_OK
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_remote_name(
remote: *const mxr_remote_t,
out: *mut c_char,
cap: usize,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
if out.is_null() || cap == 0 {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"name buffer is null or empty",
);
}
put_str(
unsafe { std::slice::from_raw_parts_mut(out, cap) },
r.remote.name(),
);
mxr_result_t::MXR_OK
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_remote_target(
remote: *const mxr_remote_t,
ip: *mut c_char,
cap: usize,
port: *mut u16,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let target = match r.remote.target() {
Some(t) => t,
None => {
return fail(
mxr_result_t::MXR_ERR_NOT_CONNECTED,
"the client has no socket",
)
}
};
if !ip.is_null() {
if cap < MXR_IP_STRING_LEN {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"address buffer is shorter than MXR_IP_STRING_LEN",
);
}
let dst = unsafe { std::slice::from_raw_parts_mut(ip, cap) };
put_str(dst, &target.ip().to_string());
}
if !port.is_null() {
unsafe { *port = target.port() };
}
mxr_result_t::MXR_OK
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_devices(
remote: *const mxr_remote_t,
out: *mut mxr_uid_t,
cap: usize,
) -> usize {
guard(0, || {
let Some(r) = (unsafe { remote.as_ref() }) else {
fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"the client handle is null",
);
return 0;
};
unsafe { copy_out(&r.remote.devices(), out, cap) }
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_device_by_serial(
remote: *const mxr_remote_t,
serial: *const c_char,
out: *mut mxr_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let serial = match unsafe { req_str(serial) } {
Ok(s) => s,
Err(code) => return code,
};
unsafe { write_uid(r.remote.device_by_serial(serial), out, "serial", serial) }
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_resolve_device(
remote: *const mxr_remote_t,
name: *const c_char,
out: *mut mxr_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let name = match unsafe { req_str(name) } {
Ok(s) => s,
Err(code) => return code,
};
unsafe { write_uid(r.remote.resolve_device(name), out, "device", name) }
})
}
unsafe fn write_uid(
found: Option<mx_remote::DeviceUid>,
out: *mut mxr_uid_t,
what: &str,
key: &str,
) -> mxr_result_t {
if out.is_null() {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"uid output pointer is null",
);
}
match found {
Some(uid) => {
unsafe { *out = uid.into() };
mxr_result_t::MXR_OK
}
None => fail(
mxr_result_t::MXR_ERR_NOT_FOUND,
&format!("no device with {what} {key:?}"),
),
}
}
#[no_mangle]
pub unsafe extern "C" fn mxr_bay_by_name(
remote: *const mxr_remote_t,
device: mxr_uid_t,
port_name: *const c_char,
out: *mut mxr_bay_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let port_name = match unsafe { req_str(port_name) } {
Ok(s) => s,
Err(code) => return code,
};
let found = r.remote.bay_by_name(device.into(), port_name);
unsafe { write_bay(found, out, &format!("no bay named {port_name:?}")) }
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_bay_by_stream_ip(
remote: *const mxr_remote_t,
ip: *const c_char,
audio: bool,
out: *mut mxr_bay_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let ip = match unsafe { opt_ip(ip, "ip") } {
Ok(Some(ip)) => ip,
Ok(None) => {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"a required string argument was null",
)
}
Err(code) => return code,
};
let found = r.remote.bay_by_stream_ip(ip, audio);
unsafe { write_bay(found, out, &format!("no bay streams to {ip}")) }
})
}
unsafe fn write_bay(
found: Option<mx_remote::BayUid>,
out: *mut mxr_bay_uid_t,
message: &str,
) -> mxr_result_t {
if out.is_null() {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"bay output pointer is null",
);
}
match found {
Some(bay) => {
unsafe { *out = bay.into() };
mxr_result_t::MXR_OK
}
None => fail(mxr_result_t::MXR_ERR_NOT_FOUND, message),
}
}
#[no_mangle]
pub unsafe extern "C" fn mxr_remote_update_config(
remote: *const mxr_remote_t,
local_ip: *const c_char,
broadcast: bool,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let ip = match unsafe { opt_ip(local_ip, "local_ip") } {
Ok(ip) => ip,
Err(code) => return code,
};
from_io(r.remote.update_config(ip, broadcast))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_discover(remote: *const mxr_remote_t) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| from_send(r.remote.discover()))
}