use std::ffi::c_char;
use mx_remote::{
AmpZoneSettings, ArcStatus, BayAudioDetails, BayInfo, BaySignalDetails, DeviceInfo,
DeviceStatus, PowerStatus, AMP_EQ_BANDS,
};
use crate::abi::{
bay_or_zero, fail, guard, mxr_bay_uid_t, mxr_result_t, mxr_tribool_t, mxr_uid_t, put_str,
};
use crate::bits::mxr_signal_type_t;
use crate::remote::{mxr_remote_t, with, MXR_IP_STRING_LEN};
pub const MXR_NAME_LEN: usize = 32;
pub const MXR_SERIAL_LEN: usize = 32;
pub const MXR_MODEL_LEN: usize = 48;
pub const MXR_VERSION_LEN: usize = 129;
pub const MXR_SIGNAL_TYPE_LEN: usize = 48;
pub const MXR_MESSAGE_LEN: usize = 128;
pub const MXR_AMP_EQ_BANDS: usize = 5;
const _: () = assert!(MXR_AMP_EQ_BANDS == AMP_EQ_BANDS);
#[repr(i32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum mxr_device_status_t {
MXR_DEVICE_ONLINE = 0,
MXR_DEVICE_OFFLINE = 1,
MXR_DEVICE_REBOOTING = 2,
MXR_DEVICE_BOOTING = 3,
MXR_DEVICE_INACTIVE = 4,
}
impl From<DeviceStatus> for mxr_device_status_t {
fn from(status: DeviceStatus) -> Self {
match status {
DeviceStatus::Online => Self::MXR_DEVICE_ONLINE,
DeviceStatus::Offline => Self::MXR_DEVICE_OFFLINE,
DeviceStatus::Rebooting => Self::MXR_DEVICE_REBOOTING,
DeviceStatus::Booting => Self::MXR_DEVICE_BOOTING,
DeviceStatus::Inactive => Self::MXR_DEVICE_INACTIVE,
}
}
}
#[repr(i32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum mxr_power_status_t {
MXR_POWER_UNKNOWN = 0,
MXR_POWER_ON = 1,
MXR_POWER_OFF = 2,
}
impl From<Option<PowerStatus>> for mxr_power_status_t {
fn from(status: Option<PowerStatus>) -> Self {
match status {
Some(PowerStatus::On) => Self::MXR_POWER_ON,
Some(PowerStatus::Off) => Self::MXR_POWER_OFF,
Some(PowerStatus::Unknown) | None => Self::MXR_POWER_UNKNOWN,
}
}
}
#[repr(i32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum mxr_arc_status_t {
MXR_ARC_INACTIVE = 0,
MXR_ARC_HDMI = 1,
MXR_ARC_OPTICAL = 2,
MXR_ARC_ANALOG = 3,
}
impl From<ArcStatus> for mxr_arc_status_t {
fn from(arc: ArcStatus) -> Self {
match arc {
ArcStatus::Inactive => Self::MXR_ARC_INACTIVE,
ArcStatus::Hdmi => Self::MXR_ARC_HDMI,
ArcStatus::Optical => Self::MXR_ARC_OPTICAL,
ArcStatus::Analog => Self::MXR_ARC_ANALOG,
}
}
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct mxr_device_info_t {
pub uid: mxr_uid_t,
pub name: [c_char; MXR_NAME_LEN],
pub serial: [c_char; MXR_SERIAL_LEN],
pub model: [c_char; MXR_MODEL_LEN],
pub version: [c_char; MXR_VERSION_LEN],
pub supported_protocol: u16,
pub features: u32,
pub address: [c_char; MXR_IP_STRING_LEN],
pub status: mxr_device_status_t,
pub online: bool,
pub configuration_complete: bool,
pub config_initialised: bool,
pub mesh_master: mxr_uid_t,
pub hdbt_outputs: u8,
pub setup_done: mxr_tribool_t,
pub installer_id: i32,
pub has_system_status: bool,
pub system_status: u16,
pub system_message: [c_char; MXR_MESSAGE_LEN],
pub temperature_count: usize,
pub bay_count: usize,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct mxr_bay_info_t {
pub uid: mxr_bay_uid_t,
pub port_name: [c_char; MXR_NAME_LEN],
pub user_name: [c_char; MXR_NAME_LEN],
pub bay_num: u8,
pub features: u32,
pub is_input: bool,
pub is_output: bool,
pub is_audio: bool,
pub has_dolby: bool,
pub is_local: bool,
pub video_source: mxr_bay_uid_t,
pub audio_source: mxr_bay_uid_t,
pub power_status: mxr_power_status_t,
pub hidden: mxr_tribool_t,
pub faulty: mxr_tribool_t,
pub poe_powered: mxr_tribool_t,
pub hdbt_connected: mxr_tribool_t,
pub signal_detected: mxr_tribool_t,
pub hpd_detected: mxr_tribool_t,
pub cec_detected: mxr_tribool_t,
pub encoder_disabled: mxr_tribool_t,
pub decoder_disabled: mxr_tribool_t,
pub signal_type: [c_char; MXR_SIGNAL_TYPE_LEN],
pub signal_mode: mxr_signal_type_t,
pub arc: mxr_arc_status_t,
pub has_volume: bool,
pub volume: u8,
pub muted: mxr_tribool_t,
pub has_rc_type: bool,
pub rc_type: u8,
pub has_edid_profile: bool,
pub edid_profile: u16,
pub mirror: mxr_bay_uid_t,
pub audio_endpoint: i16,
pub linked_bay: mxr_bay_uid_t,
pub v2ip_uid: mxr_uid_t,
pub filtered_count: usize,
}
pub const MXR_EDID_LEN: usize = 256;
#[repr(C)]
#[derive(Clone, Copy)]
pub struct mxr_audio_details_t {
pub format: u8,
pub channels: u8,
pub sample_rate: u32,
pub has_coding: bool,
pub coding: u8,
}
impl From<BayAudioDetails> for mxr_audio_details_t {
fn from(d: BayAudioDetails) -> Self {
Self {
format: d.format,
channels: d.channels,
sample_rate: d.sample_rate,
has_coding: d.coding.is_some(),
coding: d.coding.unwrap_or(0),
}
}
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct mxr_signal_details_t {
pub frame_rate: f64,
pub tmds_clock: u32,
pub clock_rate: u32,
pub status: u32,
pub scaling: mxr_signal_type_t,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct mxr_amp_zone_settings_t {
pub gain_left: u8,
pub gain_right: u8,
pub volume_min: u8,
pub volume_max: u8,
pub bass: u8,
pub treble: u8,
pub bridged: u8,
pub power_mode: u8,
pub power_level: u8,
pub delay_left: u32,
pub delay_right: u32,
pub power_timeout: u32,
pub eq_left: [u8; MXR_AMP_EQ_BANDS],
pub eq_right: [u8; MXR_AMP_EQ_BANDS],
}
impl From<AmpZoneSettings> for mxr_amp_zone_settings_t {
fn from(s: AmpZoneSettings) -> Self {
Self {
gain_left: s.gain_left,
gain_right: s.gain_right,
volume_min: s.volume_min,
volume_max: s.volume_max,
bass: s.bass,
treble: s.treble,
bridged: s.bridged,
power_mode: s.power_mode,
power_level: s.power_level,
delay_left: s.delay_left,
delay_right: s.delay_right,
power_timeout: s.power_timeout,
eq_left: s.eq_left,
eq_right: s.eq_right,
}
}
}
impl From<mxr_amp_zone_settings_t> for AmpZoneSettings {
fn from(s: mxr_amp_zone_settings_t) -> Self {
Self {
gain_left: s.gain_left,
gain_right: s.gain_right,
volume_min: s.volume_min,
volume_max: s.volume_max,
delay_left: s.delay_left,
delay_right: s.delay_right,
bass: s.bass,
treble: s.treble,
bridged: s.bridged,
power_mode: s.power_mode,
power_level: s.power_level,
power_timeout: s.power_timeout,
eq_left: s.eq_left,
eq_right: s.eq_right,
}
}
}
impl From<BaySignalDetails> for mxr_signal_details_t {
fn from(d: BaySignalDetails) -> Self {
Self {
frame_rate: d.frame_rate,
tmds_clock: d.tmds_clock,
clock_rate: d.clock_rate,
status: d.status.bits(),
scaling: d.scaling.to_wire(),
}
}
}
impl mxr_device_info_t {
fn of(info: &DeviceInfo) -> Self {
let mut out = Self {
uid: info.uid.into(),
name: [0; MXR_NAME_LEN],
serial: [0; MXR_SERIAL_LEN],
model: [0; MXR_MODEL_LEN],
version: [0; MXR_VERSION_LEN],
supported_protocol: info.supported_protocol,
features: info.features.bits(),
address: [0; MXR_IP_STRING_LEN],
status: info.status.into(),
online: info.online,
configuration_complete: info.configuration_complete,
config_initialised: info.config_initialised,
mesh_master: info.mesh_master.into(),
hdbt_outputs: info.hdbt_outputs,
setup_done: info.setup_done.into(),
installer_id: info.installer_id.map_or(-1, i32::from),
has_system_status: info.system_status.is_some(),
system_status: info.system_status.as_ref().map_or(0, |(code, _)| *code),
system_message: [0; MXR_MESSAGE_LEN],
temperature_count: info.temperatures.len(),
bay_count: info.bays.len(),
};
put_str(&mut out.name, &info.name);
put_str(&mut out.serial, &info.serial);
put_str(&mut out.model, &info.model);
put_str(&mut out.version, &info.version);
if let Some(address) = info.address {
put_str(&mut out.address, &address.to_string());
}
if let Some((_, message)) = &info.system_status {
put_str(&mut out.system_message, message);
}
out
}
}
impl mxr_bay_info_t {
fn of(info: &BayInfo) -> Self {
let mut out = Self {
uid: info.uid.into(),
port_name: [0; MXR_NAME_LEN],
user_name: [0; MXR_NAME_LEN],
bay_num: info.bay_num,
features: info.features.bits(),
is_input: info.is_input,
is_output: info.is_output,
is_audio: info.is_audio,
has_dolby: info.has_dolby,
is_local: info.is_local,
video_source: bay_or_zero(info.video_source),
audio_source: bay_or_zero(info.audio_source),
power_status: info.power_status.into(),
hidden: info.hidden.into(),
faulty: info.faulty.into(),
poe_powered: info.poe_powered.into(),
hdbt_connected: info.hdbt_connected.into(),
signal_detected: info.signal_detected.into(),
hpd_detected: info.hpd_detected.into(),
cec_detected: info.cec_detected.into(),
encoder_disabled: info.encoder_disabled.into(),
decoder_disabled: info.decoder_disabled.into(),
signal_type: [0; MXR_SIGNAL_TYPE_LEN],
signal_mode: info.signal_mode.to_wire(),
arc: info.arc.into(),
has_volume: info.volume.is_some(),
volume: info.volume.map_or(0, |v| v.volume()),
muted: info.volume.and_then(|v| v.muted()).into(),
has_rc_type: info.rc_type.is_some(),
rc_type: info.rc_type.map_or(0, |t| t.to_wire()),
has_edid_profile: info.edid_profile.is_some(),
edid_profile: info.edid_profile.map_or(0, |p| p.to_wire()),
mirror: bay_or_zero(info.mirror.target),
audio_endpoint: info.audio_endpoint.map_or(-1, i16::from),
linked_bay: bay_or_zero(info.linked_bay),
v2ip_uid: info.v2ip_uid.into(),
filtered_count: info.filtered.len(),
};
put_str(&mut out.port_name, &info.port_name);
put_str(&mut out.user_name, &info.user_name);
if let Some(signal_type) = &info.signal_type {
put_str(&mut out.signal_type, signal_type);
}
out
}
}
#[no_mangle]
pub unsafe extern "C" fn mxr_device(
remote: *const mxr_remote_t,
uid: mxr_uid_t,
out: *mut mxr_device_info_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
if out.is_null() {
return null_out("device info");
}
match r.remote.device(uid.into()) {
Some(info) => {
unsafe { *out = mxr_device_info_t::of(&info) };
mxr_result_t::MXR_OK
}
None => not_heard_from(uid),
}
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_bay(
remote: *const mxr_remote_t,
bay: mxr_bay_uid_t,
out: *mut mxr_bay_info_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
if out.is_null() {
return null_out("bay info");
}
match r.remote.bay(bay.into()) {
Some(info) => {
unsafe { *out = mxr_bay_info_t::of(&info) };
mxr_result_t::MXR_OK
}
None => fail(
mxr_result_t::MXR_ERR_NOT_FOUND,
&format!("no bay {}", mx_remote::BayUid::from(bay)),
),
}
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_bay_signal_details(
remote: *const mxr_remote_t,
bay: mxr_bay_uid_t,
out: *mut mxr_signal_details_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
if out.is_null() {
return null_out("signal details");
}
let Some(info) = r.remote.bay(bay.into()) else {
return fail(
mxr_result_t::MXR_ERR_NOT_FOUND,
&format!("no bay {}", mx_remote::BayUid::from(bay)),
);
};
match info.signal_details {
Some(details) => {
unsafe { *out = details.into() };
mxr_result_t::MXR_OK
}
None => fail(
mxr_result_t::MXR_ERR_NOT_REPORTED,
"the bay has reported no signal details",
),
}
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_bay_audio_details(
remote: *const mxr_remote_t,
bay: mxr_bay_uid_t,
out: *mut mxr_audio_details_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
if out.is_null() {
return null_out("audio details");
}
let Some(info) = r.remote.bay(bay.into()) else {
return fail(
mxr_result_t::MXR_ERR_NOT_FOUND,
&format!("no bay {}", mx_remote::BayUid::from(bay)),
);
};
match info.signal_details.and_then(|d| d.audio) {
Some(audio) => {
unsafe { *out = audio.into() };
mxr_result_t::MXR_OK
}
None => fail(
mxr_result_t::MXR_ERR_NOT_REPORTED,
"the bay has reported no audio alongside its signal",
),
}
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_device_edid(
remote: *const mxr_remote_t,
device: mxr_uid_t,
output: bool,
out: *mut u8,
cap: usize,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
if out.is_null() {
return null_out("edid");
}
if cap < MXR_EDID_LEN {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
&format!("an EDID needs {MXR_EDID_LEN} bytes, and {cap} were offered"),
);
}
let Some(edid) = r.remote.edid(device.into(), output) else {
return fail(
mxr_result_t::MXR_ERR_NOT_REPORTED,
"the device has reported no EDID",
);
};
let n = edid.len().min(MXR_EDID_LEN);
unsafe { std::ptr::copy_nonoverlapping(edid.as_ptr(), out, n) };
mxr_result_t::MXR_OK
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_bay_amp_settings(
remote: *const mxr_remote_t,
bay: mxr_bay_uid_t,
out: *mut mxr_amp_zone_settings_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
if out.is_null() {
return null_out("amp zone settings");
}
let Some(info) = r.remote.bay(bay.into()) else {
return fail(
mxr_result_t::MXR_ERR_NOT_FOUND,
&format!("no bay {}", mx_remote::BayUid::from(bay)),
);
};
match info.amp_settings {
Some(settings) => {
unsafe { *out = settings.into() };
mxr_result_t::MXR_OK
}
None => fail(
mxr_result_t::MXR_ERR_NOT_REPORTED,
"the bay has reported no amplifier settings",
),
}
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_device_bays(
remote: *const mxr_remote_t,
uid: mxr_uid_t,
out: *mut mxr_bay_uid_t,
cap: usize,
) -> usize {
guard(0, || {
let Some(info) = (unsafe { device_info(remote, uid) }) else {
return 0;
};
unsafe { copy_into(&info.bays, out, cap) }
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_device_temperatures(
remote: *const mxr_remote_t,
uid: mxr_uid_t,
out: *mut u8,
cap: usize,
) -> usize {
guard(0, || {
let Some(info) = (unsafe { device_info(remote, uid) }) else {
return 0;
};
unsafe { copy_into(&info.temperatures, out, cap) }
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_bay_filtered(
remote: *const mxr_remote_t,
bay: mxr_bay_uid_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;
};
let Some(info) = r.remote.bay(bay.into()) else {
fail(
mxr_result_t::MXR_ERR_NOT_FOUND,
&format!("no bay {}", mx_remote::BayUid::from(bay)),
);
return 0;
};
unsafe { copy_into(&info.filtered, out, cap) }
})
}
unsafe fn device_info(remote: *const mxr_remote_t, uid: mxr_uid_t) -> Option<DeviceInfo> {
let Some(r) = (unsafe { remote.as_ref() }) else {
fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"the client handle is null",
);
return None;
};
let info = r.remote.device(uid.into());
if info.is_none() {
not_heard_from(uid);
}
info
}
pub(crate) unsafe fn copy_into<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()
}
pub(crate) fn null_out(what: &str) -> mxr_result_t {
fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
&format!("the {what} output pointer is null"),
)
}
pub(crate) fn not_heard_from(uid: mxr_uid_t) -> mxr_result_t {
fail(
mxr_result_t::MXR_ERR_NOT_FOUND,
&format!("no device {}", mx_remote::DeviceUid::from(uid)),
)
}