use std::ffi::c_char;
use std::net::Ipv4Addr;
use std::time::{Duration, UNIX_EPOCH};
use mx_remote::{
ControlError, DeviceUid, EdidProfile, MultiviewerAspectRatio, MultiviewerEdidTemplate,
MultiviewerHdcpMode, MultiviewerItcMode, MultiviewerOutputMode, MultiviewerPipPosition,
MultiviewerPipSize, MultiviewerSource, MultiviewerViewMode, RcAction, RcKey, UnitNetConfig,
V2ipAudioFormat, V2ipColourSpace, V2ipDeviceSetting, V2ipDeviceSettings, V2ipOutputMode,
V2ipPowerSaveLevel, V2ipPowerSaveSchedule, V2ipPtpMode, V2ipRoute, V2ipRouteTarget,
V2ipTestPattern, V2ipVlan, V2ipVlanFlag, VideoWallWindow,
};
use mx_remote::{
OsdCaption, OsdCaptionAlign, OsdCaptionFormat, OsdLogo, OsdMessagePriority, OsdSyncContent,
OsdSyncEntry, OsdSyncImage, OsdSyncSize, OsdSyncValue, OsdSyncWhat,
};
use crate::abi::{
fail, from_control, mxr_bay_uid_t, mxr_result_t, mxr_tribool_t, mxr_uid_t, opt_str, req_str,
};
use crate::bits::{
MXR_OSD_SYNC_TYPE_BACKGROUND, MXR_OSD_SYNC_TYPE_CAPTION, MXR_OSD_SYNC_TYPE_LOGO,
};
use crate::info::mxr_amp_zone_settings_t;
use crate::remote::{mxr_remote_t, with};
use crate::subsystems::{
mxr_v2ip_device_settings_t, mxr_v2ip_downmix_t, mxr_v2ip_power_save_t, mxr_v2ip_ptp_messages_t,
mxr_v2ip_ptp_settings_t, mxr_v2ip_test_sync_t, mxr_v2ip_test_tone_t, mxr_v2ip_vlan_t,
MXR_OSD_SYNC_SLOTS,
};
#[repr(C)]
#[derive(Clone, Copy)]
pub struct mxr_audio_format_t {
pub sample_rate: u32,
pub channels: u8,
}
impl From<mxr_audio_format_t> for V2ipAudioFormat {
fn from(f: mxr_audio_format_t) -> Self {
Self {
sample_rate: f.sample_rate,
channels: f.channels,
}
}
}
#[no_mangle]
pub unsafe extern "C" fn mxr_select_video_source(
remote: *const mxr_remote_t,
sink: mxr_bay_uid_t,
source_port: u16,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.select_video_source(sink.into(), source_port))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_select_audio_source(
remote: *const mxr_remote_t,
sink: mxr_bay_uid_t,
source_port: u16,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.select_audio_source(sink.into(), source_port))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_select_video_source_by_name(
remote: *const mxr_remote_t,
sink: mxr_bay_uid_t,
name: *const c_char,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
match unsafe { req_str(name) } {
Ok(name) => from_control(r.remote.select_video_source_by_name(sink.into(), name)),
Err(code) => code,
}
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_select_audio_source_by_name(
remote: *const mxr_remote_t,
sink: mxr_bay_uid_t,
name: *const c_char,
format: *const mxr_audio_format_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let name = match unsafe { req_str(name) } {
Ok(name) => name,
Err(code) => return code,
};
let format = unsafe { format.as_ref() }.map(|f| (*f).into());
from_control(
r.remote
.select_audio_source_by_name(sink.into(), name, format),
)
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_select_audio_source_addr(
remote: *const mxr_remote_t,
sink: mxr_bay_uid_t,
audio_ip: *const c_char,
audio_port: u16,
format: *const mxr_audio_format_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let text = match unsafe { req_str(audio_ip) } {
Ok(text) => text,
Err(code) => return code,
};
let Ok(ip) = text.parse() else {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
&format!("audio_ip is not an IPv4 address: {text:?}"),
);
};
let format = unsafe { format.as_ref() }.map(|f| (*f).into());
from_control(r.remote.select_audio_source_addr(
sink.into(),
ip,
(audio_port != 0).then_some(audio_port),
format,
))
})
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct mxr_stream_addr_t {
pub ip: *const c_char,
pub port: u16,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct mxr_v2ip_route_t {
pub video: mxr_stream_addr_t,
pub audio: mxr_stream_addr_t,
pub anc: mxr_stream_addr_t,
}
unsafe fn to_target(slot: mxr_stream_addr_t, what: &str) -> Result<V2ipRouteTarget, mxr_result_t> {
let text = unsafe { opt_str(slot.ip) }?.unwrap_or_default();
if text.is_empty() {
return Ok(V2ipRouteTarget::default());
}
match text.parse() {
Ok(ip) => Ok(V2ipRouteTarget {
ip,
port: slot.port,
}),
Err(_) => Err(fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
&format!("{what} is not an IPv4 address: {text:?}"),
)),
}
}
#[no_mangle]
pub unsafe extern "C" fn mxr_select_source_addr(
remote: *const mxr_remote_t,
sink: mxr_bay_uid_t,
route: *const mxr_v2ip_route_t,
format: *const mxr_audio_format_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let Some(route) = (unsafe { route.as_ref() }) else {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"the route pointer is null",
);
};
let route = unsafe {
match (
to_target(route.video, "video ip"),
to_target(route.audio, "audio ip"),
to_target(route.anc, "anc ip"),
) {
(Ok(video), Ok(audio), Ok(anc)) => V2ipRoute { video, audio, anc },
(Err(code), _, _) | (_, Err(code), _) | (_, _, Err(code)) => return code,
}
};
let format = unsafe { format.as_ref() }.map(|f| (*f).into());
from_control(r.remote.select_source_addr(sink.into(), route, format))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_bay_name(
remote: *const mxr_remote_t,
bay: mxr_bay_uid_t,
name: *const c_char,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
match unsafe { req_str(name) } {
Ok(name) => from_control(r.remote.set_bay_name(bay.into(), name)),
Err(code) => code,
}
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_bay_hidden(
remote: *const mxr_remote_t,
bay: mxr_bay_uid_t,
hidden: bool,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.set_bay_hidden(bay.into(), hidden))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_select_edid_profile(
remote: *const mxr_remote_t,
bay: mxr_bay_uid_t,
profile: u16,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(
r.remote
.select_edid_profile(bay.into(), EdidProfile::from_wire(profile)),
)
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_send_action(
remote: *const mxr_remote_t,
bay: mxr_bay_uid_t,
action: u16,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(
r.remote
.send_action(bay.into(), RcAction::from_wire(action)),
)
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_send_key(
remote: *const mxr_remote_t,
bay: mxr_bay_uid_t,
key: u16,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.send_key(bay.into(), RcKey::from_wire(key)))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_power_on(
remote: *const mxr_remote_t,
bay: mxr_bay_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| from_control(r.remote.power_on(bay.into())))
}
#[no_mangle]
pub unsafe extern "C" fn mxr_power_off(
remote: *const mxr_remote_t,
bay: mxr_bay_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| from_control(r.remote.power_off(bay.into())))
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_volume(
remote: *const mxr_remote_t,
bay: mxr_bay_uid_t,
volume: u8,
muted: mxr_tribool_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.set_volume(
bay.into(),
volume,
match muted {
mxr_tribool_t::MXR_UNKNOWN => None,
mxr_tribool_t::MXR_FALSE => Some(false),
mxr_tribool_t::MXR_TRUE => Some(true),
},
))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_volume_up(
remote: *const mxr_remote_t,
bay: mxr_bay_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| from_control(r.remote.volume_up(bay.into())))
}
#[no_mangle]
pub unsafe extern "C" fn mxr_volume_down(
remote: *const mxr_remote_t,
bay: mxr_bay_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| from_control(r.remote.volume_down(bay.into())))
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_muted(
remote: *const mxr_remote_t,
bay: mxr_bay_uid_t,
muted: bool,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.set_muted(bay.into(), muted))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_amp_zone_settings(
remote: *const mxr_remote_t,
bay: mxr_bay_uid_t,
settings: *const mxr_amp_zone_settings_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let Some(settings) = (unsafe { settings.as_ref() }) else {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"the amp zone settings pointer is null",
);
};
from_control(
r.remote
.set_amp_zone_settings(bay.into(), (*settings).into()),
)
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_audio_endpoint_muted(
remote: *const mxr_remote_t,
device: mxr_uid_t,
endpoint: u16,
muted: bool,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(
r.remote
.set_audio_endpoint_muted(device.into(), endpoint, muted),
)
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_audio_endpoint_locked(
remote: *const mxr_remote_t,
device: mxr_uid_t,
endpoint: u8,
locked: bool,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(
r.remote
.set_audio_endpoint_locked(device.into(), endpoint, locked),
)
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_audio_endpoint_trigger(
remote: *const mxr_remote_t,
device: mxr_uid_t,
endpoint: u16,
active: bool,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(
r.remote
.set_audio_endpoint_trigger(device.into(), endpoint, active),
)
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_audio_endpoint_volume(
remote: *const mxr_remote_t,
device: mxr_uid_t,
endpoint: u16,
volume: u32,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(
r.remote
.set_audio_endpoint_volume(device.into(), endpoint, volume),
)
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_select_audio_endpoint_input(
remote: *const mxr_remote_t,
sink: mxr_uid_t,
sink_endpoint: u16,
source: mxr_uid_t,
source_endpoint: u16,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.select_audio_endpoint_input(
sink.into(),
sink_endpoint,
source.into(),
source_endpoint,
))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_request_edid(
remote: *const mxr_remote_t,
device: mxr_uid_t,
output: bool,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.request_edid(device.into(), output))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_request_signal_status(
remote: *const mxr_remote_t,
device: mxr_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let uid = DeviceUid::from(device);
let target = (uid != DeviceUid::ZERO).then_some(uid);
from_control(r.remote.request_signal_status(target))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_subscribe_v2ip_stats(
remote: *const mxr_remote_t,
device: mxr_uid_t,
subscribe: bool,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.subscribe_v2ip_stats(device.into(), subscribe))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_reboot(
remote: *const mxr_remote_t,
device: mxr_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| from_control(r.remote.reboot(device.into())))
}
#[no_mangle]
pub unsafe extern "C" fn mxr_ping(remote: *const mxr_remote_t, device: mxr_uid_t) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| from_control(r.remote.ping(device.into())))
}
#[no_mangle]
pub unsafe extern "C" fn mxr_auto_assign_v2ip_source_addresses(
remote: *const mxr_remote_t,
device: mxr_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.auto_assign_v2ip_source_addresses(device.into()))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_mesh_time_zone(
remote: *const mxr_remote_t,
zone: *const c_char,
rule: *const c_char,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
match unsafe { (req_str(zone), req_str(rule)) } {
(Ok(zone), Ok(rule)) => from_control(r.remote.set_mesh_time_zone(zone, rule)),
(Err(code), _) | (_, Err(code)) => code,
}
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_clear_mesh_time_zone(remote: *const mxr_remote_t) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| from_control(r.remote.clear_mesh_time_zone()))
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_mesh_time(remote: *const mxr_remote_t, utc: u64) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let time = UNIX_EPOCH.checked_add(Duration::from_secs(utc));
match time {
Some(time) => from_control(r.remote.set_mesh_time(time)),
None => fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"the time does not fit the frame",
),
}
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_send_monitoring_pulse(remote: *const mxr_remote_t) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| from_control(r.remote.send_monitoring_pulse()))
}
pub const MXR_V2IP_COLOUR_RGB: u8 = 0;
pub const MXR_V2IP_COLOUR_YCBCR444: u8 = 1;
pub const MXR_V2IP_COLOUR_YCBCR422: u8 = 2;
pub const MXR_V2IP_COLOUR_YCBCR420: u8 = 3;
pub const MXR_V2IP_SCALING_REFRESH_MIN: u16 = 24;
pub const MXR_V2IP_SCALING_REFRESH_MAX: u16 = 120;
#[repr(C)]
#[derive(Clone, Copy)]
pub struct mxr_v2ip_output_mode_t {
pub svd: u8,
pub depth: u8,
pub colour: u8,
pub refresh: u16,
}
impl From<mxr_v2ip_output_mode_t> for V2ipOutputMode {
fn from(m: mxr_v2ip_output_mode_t) -> Self {
Self {
svd: m.svd,
depth: m.depth,
colour: V2ipColourSpace::from_wire(m.colour),
refresh: m.refresh,
}
}
}
unsafe fn output_mode(mode: *const mxr_v2ip_output_mode_t) -> Result<V2ipOutputMode, mxr_result_t> {
match unsafe { mode.as_ref() } {
Some(m) => Ok((*m).into()),
None => Err(fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"the mode pointer is null",
)),
}
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_v2ip_auto_scaling(
remote: *const mxr_remote_t,
device: mxr_uid_t,
enabled: bool,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.set_v2ip_auto_scaling(device.into(), enabled))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_v2ip_device_setting(
remote: *const mxr_remote_t,
device: mxr_uid_t,
setting: u32,
enabled: bool,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.set_v2ip_device_setting(
device.into(),
V2ipDeviceSetting::from_bits(setting),
enabled,
))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_v2ip_ir_profile(
remote: *const mxr_remote_t,
device: mxr_uid_t,
profile: i8,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.set_v2ip_ir_profile(device.into(), profile))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_v2ip_auto_power_save(
remote: *const mxr_remote_t,
device: mxr_uid_t,
minutes: u16,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.set_v2ip_auto_power_save(device.into(), minutes))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_v2ip_power_save_schedule(
remote: *const mxr_remote_t,
device: mxr_uid_t,
schedule: *const mxr_v2ip_power_save_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let Some(schedule) = (unsafe { schedule.as_ref() }) else {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"the schedule pointer is null",
);
};
from_control(r.remote.set_v2ip_power_save_schedule(
device.into(),
V2ipPowerSaveSchedule {
start: schedule.start,
end: schedule.end,
},
))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_all_v2ip_device_settings(
remote: *const mxr_remote_t,
settings: *const mxr_v2ip_device_settings_t,
power_save: *const mxr_v2ip_power_save_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let Some(settings) = (unsafe { settings.as_ref() }) else {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"the settings pointer is null",
);
};
let valid = V2ipDeviceSetting::from_bits(settings.valid);
if !(valid & V2ipDeviceSetting::PTP_SETTINGS).is_empty() {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"PTP settings go through mxr_set_all_v2ip_ptp_settings()",
);
}
if valid.has(V2ipDeviceSetting::POWER_SAVE_LEVEL) {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"the power save level goes through mxr_set_all_v2ip_power_save_level()",
);
}
if !(valid & V2ipDeviceSetting::SETTINGS_BLOCK).is_empty() {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"PTP message, SAP timing, stream TTL and rotation settings go through mxr_set_all_v2ip_ptp_messages()",
);
}
let needs_power_save = valid.has(V2ipDeviceSetting::AUTO_POWER_SAVE)
|| valid.has(V2ipDeviceSetting::POWER_SAVE_SCHEDULE);
let power_save = match unsafe { power_save.as_ref() } {
Some(p) => *p,
None if needs_power_save => {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"the power save pointer is null",
)
}
None => mxr_v2ip_power_save_t {
auto_minutes: 0,
start: [0; 7],
end: [0; 7],
},
};
from_control(r.remote.set_all_v2ip_device_settings(V2ipDeviceSettings {
valid,
flags: V2ipDeviceSetting::from_bits(settings.flags),
ir_profiles: 0,
ir_profile: settings.ir_profile,
ir_profile_sink: settings.ir_profile_sink,
auto_power_save: power_save.auto_minutes,
power_save: V2ipPowerSaveSchedule {
start: power_save.start,
end: power_save.end,
},
..V2ipDeviceSettings::default()
}))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_v2ip_power_save_level(
remote: *const mxr_remote_t,
device: mxr_uid_t,
level: u8,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(
r.remote
.set_v2ip_power_save_level(device.into(), V2ipPowerSaveLevel::from_wire(level)),
)
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_all_v2ip_power_save_level(
remote: *const mxr_remote_t,
level: u8,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.set_all_v2ip_device_settings(V2ipDeviceSettings {
valid: V2ipDeviceSetting::POWER_SAVE_LEVEL,
power_save_level: V2ipPowerSaveLevel::from_wire(level),
..V2ipDeviceSettings::default()
}))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_v2ip_ptp_messages(
remote: *const mxr_remote_t,
device: mxr_uid_t,
valid: u32,
messages: *const mxr_v2ip_ptp_messages_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let Some(messages) = (unsafe { messages.as_ref() }) else {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"the PTP messages pointer is null",
);
};
from_control(r.remote.set_v2ip_ptp_messages(
device.into(),
V2ipDeviceSetting::from_bits(valid),
(*messages).into(),
))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_v2ip_downmix(
remote: *const mxr_remote_t,
device: mxr_uid_t,
downmix: *const mxr_v2ip_downmix_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let Some(downmix) = (unsafe { downmix.as_ref() }) else {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"the downmix pointer is null",
);
};
from_control(r.remote.set_v2ip_downmix(device.into(), (*downmix).into()))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_all_v2ip_ptp_messages(
remote: *const mxr_remote_t,
valid: u32,
messages: *const mxr_v2ip_ptp_messages_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let Some(messages) = (unsafe { messages.as_ref() }) else {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"the PTP messages pointer is null",
);
};
let valid = V2ipDeviceSetting::from_bits(valid);
if valid.is_empty() || !V2ipDeviceSetting::SETTINGS_BLOCK.has(valid) {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"only PTP message, SAP timing, stream TTL and rotation settings are carried here",
);
}
from_control(r.remote.set_all_v2ip_device_settings(V2ipDeviceSettings {
valid,
ptp_messages: (*messages).into(),
..V2ipDeviceSettings::default()
}))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_v2ip_ptp_mode(
remote: *const mxr_remote_t,
device: mxr_uid_t,
mode: u8,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(
r.remote
.set_v2ip_ptp_mode(device.into(), V2ipPtpMode::from_wire(mode)),
)
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_v2ip_ptp_domain(
remote: *const mxr_remote_t,
device: mxr_uid_t,
domain: u8,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.set_v2ip_ptp_domain(device.into(), domain))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_v2ip_ptp_priority1(
remote: *const mxr_remote_t,
device: mxr_uid_t,
priority1: u8,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.set_v2ip_ptp_priority1(device.into(), priority1))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_all_v2ip_ptp_settings(
remote: *const mxr_remote_t,
valid: u32,
ptp: *const mxr_v2ip_ptp_settings_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let Some(ptp) = (unsafe { ptp.as_ref() }) else {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"the PTP settings pointer is null",
);
};
let valid = V2ipDeviceSetting::from_bits(valid);
if valid.is_empty() || !V2ipDeviceSetting::PTP_SETTINGS.has(valid) {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"only PTP settings are carried here",
);
}
from_control(r.remote.set_all_v2ip_device_settings(V2ipDeviceSettings {
valid,
ptp_mode: V2ipPtpMode::from_wire(ptp.mode),
ptp_domain: ptp.domain,
ptp_priority1: ptp.priority1,
..V2ipDeviceSettings::default()
}))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_play_v2ip_sdp(
remote: *const mxr_remote_t,
device: mxr_uid_t,
sdp: *const c_char,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let text = match unsafe { req_str(sdp) } {
Ok(text) => text,
Err(code) => return code,
};
from_control(r.remote.play_v2ip_sdp(device.into(), text))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_stop_v2ip_sdp(
remote: *const mxr_remote_t,
device: mxr_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.stop_v2ip_sdp(device.into()))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_v2ip_sdp_screen(
remote: *const mxr_remote_t,
device: mxr_uid_t,
screen: u8,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.set_v2ip_sdp_screen(device.into(), screen))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_request_v2ip_sink_sdp(
remote: *const mxr_remote_t,
device: mxr_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.request_v2ip_sink_sdp(device.into()))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_request_v2ip_privacy(
remote: *const mxr_remote_t,
device: mxr_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.request_v2ip_privacy(device.into()))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_request_all_v2ip_privacy(remote: *const mxr_remote_t) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.request_all_v2ip_privacy())
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_request_osd_message(
remote: *const mxr_remote_t,
device: mxr_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.request_osd_message(device.into()))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_show_osd_message(
remote: *const mxr_remote_t,
device: mxr_uid_t,
logo: i8,
priority: u8,
text: *const c_char,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let text = match unsafe { req_str(text) } {
Ok(text) => text,
Err(code) => return code,
};
from_control(r.remote.show_osd_message(
device.into(),
logo,
OsdMessagePriority::from_wire(priority),
text,
))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_clear_osd_message(
remote: *const mxr_remote_t,
device: mxr_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.clear_osd_message(device.into()))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_clear_unit_status(
remote: *const mxr_remote_t,
device: mxr_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.clear_unit_status(device.into()))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_upgrade_unit_fpga(
remote: *const mxr_remote_t,
device: mxr_uid_t,
force: bool,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.upgrade_unit_fpga(device.into(), force))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_unit_dhcp(
remote: *const mxr_remote_t,
device: mxr_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.set_unit_dhcp(device.into()))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_unit_network(
remote: *const mxr_remote_t,
device: mxr_uid_t,
ip: *const c_char,
netmask: *const c_char,
gateway: *const c_char,
dns1: *const c_char,
dns2: *const c_char,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let read = |text: *const c_char, what: &str| -> Result<Ipv4Addr, mxr_result_t> {
let text = unsafe { opt_str(text) }?.unwrap_or_default();
if text.is_empty() {
return Ok(Ipv4Addr::UNSPECIFIED);
}
text.parse().map_err(|_| {
fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
&format!("{what} is not an IPv4 address: {text:?}"),
)
})
};
let network = (|| {
Ok(UnitNetConfig {
ip: read(ip, "ip")?,
netmask: read(netmask, "netmask")?,
gateway: read(gateway, "gateway")?,
dns1: read(dns1, "dns1")?,
dns2: read(dns2, "dns2")?,
})
})();
match network {
Ok(network) => from_control(r.remote.set_unit_network(device.into(), network)),
Err(code) => code,
}
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_confirm_unit_network(
remote: *const mxr_remote_t,
device: mxr_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.confirm_unit_network(device.into()))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_v2ip_vlan(
remote: *const mxr_remote_t,
device: mxr_uid_t,
vlan: *const mxr_v2ip_vlan_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let Some(vlan) = (unsafe { vlan.as_ref() }) else {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"the VLAN pointer is null",
);
};
from_control(r.remote.set_v2ip_vlan(
device.into(),
V2ipVlan {
flags: V2ipVlanFlag::from_bits(vlan.flags),
device: vlan.device,
port: vlan.port,
uplink: vlan.uplink,
active_uplink: vlan.active_uplink,
revert_s: vlan.revert_s,
},
))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_request_v2ip_testcard(
remote: *const mxr_remote_t,
device: mxr_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.request_v2ip_testcard(device.into()))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_v2ip_test_pattern(
remote: *const mxr_remote_t,
device: mxr_uid_t,
pattern: u8,
colour: u32,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.set_v2ip_test_pattern(
device.into(),
V2ipTestPattern::from_wire(pattern),
colour,
))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_v2ip_test_tone(
remote: *const mxr_remote_t,
device: mxr_uid_t,
tone: *const mxr_v2ip_test_tone_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let Some(tone) = (unsafe { tone.as_ref() }) else {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"the tone pointer is null",
);
};
from_control(r.remote.set_v2ip_test_tone(device.into(), (*tone).into()))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_v2ip_test_sync(
remote: *const mxr_remote_t,
device: mxr_uid_t,
sync: *const mxr_v2ip_test_sync_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let Some(sync) = (unsafe { sync.as_ref() }) else {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"the lip-sync pointer is null",
);
};
from_control(r.remote.set_v2ip_test_sync(device.into(), (*sync).into()))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_v2ip_sink_ir_profile(
remote: *const mxr_remote_t,
device: mxr_uid_t,
profile: i8,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.set_v2ip_sink_ir_profile(device.into(), profile))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_v2ip_output_mode(
remote: *const mxr_remote_t,
device: mxr_uid_t,
mode: *const mxr_v2ip_output_mode_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
match unsafe { output_mode(mode) } {
Ok(m) => from_control(r.remote.set_v2ip_output_mode(device.into(), m)),
Err(code) => code,
}
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_clear_v2ip_output_mode(
remote: *const mxr_remote_t,
device: mxr_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.clear_v2ip_output_mode(device.into()))
})
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct mxr_video_wall_window_t {
pub pos_x: u16,
pub pos_y: u16,
pub width: u16,
pub height: u16,
pub raster_w: u16,
pub raster_h: u16,
}
pub const MXR_VIDEO_WALL_POS_ALIGN: u16 = 64;
pub const MXR_VIDEO_WALL_WIDTH_ALIGN: u16 = 4;
pub const MXR_VIDEO_WALL_MIN_SIZE: u16 = 64;
impl From<mxr_video_wall_window_t> for VideoWallWindow {
fn from(w: mxr_video_wall_window_t) -> Self {
Self {
pos_x: w.pos_x,
pos_y: w.pos_y,
width: w.width,
height: w.height,
raster_w: w.raster_w,
raster_h: w.raster_h,
}
}
}
unsafe fn wall_window(
window: *const mxr_video_wall_window_t,
) -> Result<VideoWallWindow, mxr_result_t> {
match unsafe { window.as_ref() } {
Some(w) => Ok((*w).into()),
None => Err(fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"the window pointer is null",
)),
}
}
#[no_mangle]
pub unsafe extern "C" fn mxr_preview_video_wall(
remote: *const mxr_remote_t,
sink: mxr_uid_t,
window: *const mxr_video_wall_window_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
match unsafe { wall_window(window) } {
Ok(w) => from_control(r.remote.preview_video_wall(sink.into(), w)),
Err(code) => code,
}
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_store_video_wall(
remote: *const mxr_remote_t,
sink: mxr_uid_t,
window: *const mxr_video_wall_window_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
match unsafe { wall_window(window) } {
Ok(w) => from_control(r.remote.store_video_wall(sink.into(), w)),
Err(code) => code,
}
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_revert_video_wall(
remote: *const mxr_remote_t,
sink: mxr_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.revert_video_wall(sink.into()))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_multiviewer_view_mode(
remote: *const mxr_remote_t,
device: mxr_uid_t,
mode: u8,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(
r.remote
.set_multiviewer_view_mode(device.into(), MultiviewerViewMode::from_wire(mode)),
)
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_multiviewer_video_source(
remote: *const mxr_remote_t,
device: mxr_uid_t,
screen: u8,
source: u8,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.set_multiviewer_video_source(
device.into(),
screen,
MultiviewerSource::from_wire(source),
))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_multiviewer_audio_source(
remote: *const mxr_remote_t,
device: mxr_uid_t,
source: u8,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(
r.remote
.set_multiviewer_audio_source(device.into(), MultiviewerSource::from_wire(source)),
)
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_multiviewer_audio_volume(
remote: *const mxr_remote_t,
device: mxr_uid_t,
volume: u8,
muted: bool,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(
r.remote
.set_multiviewer_audio_volume(device.into(), volume, muted),
)
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_multiviewer_edid_template(
remote: *const mxr_remote_t,
device: mxr_uid_t,
template: u8,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.set_multiviewer_edid_template(
device.into(),
MultiviewerEdidTemplate::from_wire(template),
))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_multiviewer_remote_control(
remote: *const mxr_remote_t,
device: mxr_uid_t,
source: u8,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(
r.remote.set_multiviewer_remote_control(
device.into(),
MultiviewerSource::from_wire(source),
),
)
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_multiviewer_pip_size(
remote: *const mxr_remote_t,
device: mxr_uid_t,
size: u8,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(
r.remote
.set_multiviewer_pip_size(device.into(), MultiviewerPipSize::from_wire(size)),
)
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_multiviewer_pip_position(
remote: *const mxr_remote_t,
device: mxr_uid_t,
position: u8,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.set_multiviewer_pip_position(
device.into(),
MultiviewerPipPosition::from_wire(position),
))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_multiviewer_aspect_ratio(
remote: *const mxr_remote_t,
device: mxr_uid_t,
aspect: u8,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(
r.remote.set_multiviewer_aspect_ratio(
device.into(),
MultiviewerAspectRatio::from_wire(aspect),
),
)
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_multiviewer_auto_switch(
remote: *const mxr_remote_t,
device: mxr_uid_t,
enable: bool,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.set_multiviewer_auto_switch(device.into(), enable))
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_multiviewer_output_mode(
remote: *const mxr_remote_t,
device: mxr_uid_t,
mode: u8,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(
r.remote
.set_multiviewer_output_mode(device.into(), MultiviewerOutputMode::from_wire(mode)),
)
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_multiviewer_output_itc(
remote: *const mxr_remote_t,
device: mxr_uid_t,
mode: u8,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(
r.remote
.set_multiviewer_output_itc(device.into(), MultiviewerItcMode::from_wire(mode)),
)
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_multiviewer_hdcp_mode(
remote: *const mxr_remote_t,
device: mxr_uid_t,
mode: u8,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(
r.remote
.set_multiviewer_hdcp_mode(device.into(), MultiviewerHdcpMode::from_wire(mode)),
)
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_set_multiviewer_input_source(
remote: *const mxr_remote_t,
device: mxr_uid_t,
input: u8,
source: mxr_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(
r.remote
.set_multiviewer_input_source(device.into(), input, source.into()),
)
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_multiviewer_auto_route(
remote: *const mxr_remote_t,
device: mxr_uid_t,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
from_control(r.remote.multiviewer_auto_route(device.into()))
})
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct mxr_osd_sync_entry_t {
pub kind: u8,
pub size: u8,
pub layers: u16,
pub set: u16,
pub font: *const c_char,
pub font_size: u16,
pub x: i16,
pub y: i16,
pub wrap: i16,
pub align: u8,
pub format: u8,
pub colour: u32,
pub text: *const c_char,
pub logo: i8,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct mxr_osd_sync_image_t {
pub png: *const u8,
pub len: usize,
pub percent: u8,
}
unsafe fn osd_entry(e: &mxr_osd_sync_entry_t) -> Result<OsdSyncEntry, mxr_result_t> {
let value = match e.kind {
MXR_OSD_SYNC_TYPE_CAPTION => {
let (font, text) = unsafe { (opt_str(e.font)?, opt_str(e.text)?) };
OsdSyncValue::Caption(OsdCaption {
font: font.unwrap_or_default().to_owned(),
size: e.font_size,
x: e.x,
y: e.y,
wrap: e.wrap,
align: OsdCaptionAlign::from_wire(e.align),
format: OsdCaptionFormat::from_wire(e.format),
colour: e.colour,
text: text.unwrap_or_default().to_owned(),
})
}
MXR_OSD_SYNC_TYPE_LOGO => OsdSyncValue::Logo(OsdLogo {
logo: e.logo,
x: e.x,
y: e.y,
}),
MXR_OSD_SYNC_TYPE_BACKGROUND => OsdSyncValue::Background(e.colour),
_ => {
return Err(fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"an overlay setting of a MXR_OSD_SYNC_TYPE_* kind",
))
}
};
Ok(OsdSyncEntry {
size: OsdSyncSize::from_wire(e.size),
layers: e.layers,
set: e.set,
value,
})
}
unsafe fn osd_content(
what: u8,
entries: *const mxr_osd_sync_entry_t,
count: usize,
images: *const mxr_osd_sync_image_t,
) -> Result<OsdSyncContent, mxr_result_t> {
let entries: &[mxr_osd_sync_entry_t] = match (entries.is_null(), count) {
(_, 0) => &[],
(true, _) => {
return Err(fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"entries is null with a count above zero",
))
}
(false, _) => unsafe { std::slice::from_raw_parts(entries, count) },
};
let entries = entries
.iter()
.map(|e| unsafe { osd_entry(e) })
.collect::<Result<Vec<_>, _>>()?;
let images = if images.is_null() {
None
} else {
let images = unsafe { std::slice::from_raw_parts(images, MXR_OSD_SYNC_SLOTS) };
let mut out: [OsdSyncImage; MXR_OSD_SYNC_SLOTS] = Default::default();
for (o, i) in out.iter_mut().zip(images) {
o.percent = i.percent;
if i.len == 0 {
continue;
}
if i.png.is_null() {
return Err(fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"an image's png is null with a length above zero",
));
}
o.png = unsafe { std::slice::from_raw_parts(i.png, i.len) }.to_vec();
}
Some(out)
};
Ok(OsdSyncContent {
what: OsdSyncWhat::from_bits(what),
entries,
images,
})
}
unsafe fn sent_seq(result: Result<u16, ControlError>, out: *mut u16) -> mxr_result_t {
match result {
Ok(seq) => {
if !out.is_null() {
unsafe { out.write(seq) };
}
mxr_result_t::MXR_OK
}
Err(e) => from_control(Err(e)),
}
}
#[no_mangle]
pub unsafe extern "C" fn mxr_send_osd_sync(
remote: *const mxr_remote_t,
what: u8,
entries: *const mxr_osd_sync_entry_t,
count: usize,
images: *const mxr_osd_sync_image_t,
seq: *mut u16,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
let content = match unsafe { osd_content(what, entries, count, images) } {
Ok(content) => content,
Err(code) => return code,
};
unsafe { sent_seq(r.remote.send_osd_sync(content), seq) }
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_resend_osd_sync(
remote: *const mxr_remote_t,
device: mxr_uid_t,
seq: *mut u16,
) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
unsafe { sent_seq(r.remote.resend_osd_sync(device.into()), seq) }
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_clear_osd_sync(remote: *const mxr_remote_t) -> mxr_result_t {
let handle = unsafe { remote.as_ref() };
with(handle, |r| {
r.remote.clear_osd_sync();
mxr_result_t::MXR_OK
})
}
#[cfg(test)]
mod tests {
use std::ptr;
use super::*;
fn entry(kind: u8) -> mxr_osd_sync_entry_t {
mxr_osd_sync_entry_t {
kind,
size: 2,
layers: 0x2010,
set: 0x0155,
font: c"Sans".as_ptr(),
font_size: 0x0124,
x: -0x0203,
y: 0x0405,
wrap: -2,
align: 2,
format: 1,
colour: 0x00A1_B2C3,
text: c"No signal".as_ptr(),
logo: 3,
}
}
#[test]
fn each_kind_reads_its_own_fields() {
let entries = [
entry(MXR_OSD_SYNC_TYPE_CAPTION),
entry(MXR_OSD_SYNC_TYPE_LOGO),
entry(MXR_OSD_SYNC_TYPE_BACKGROUND),
];
let c = unsafe { osd_content(0x05, entries.as_ptr(), 3, ptr::null()) }.expect("read");
assert_eq!(c.what, OsdSyncWhat::from_bits(0x05));
assert_eq!(c.images, None);
for e in &c.entries {
assert_eq!(e.size, OsdSyncSize::SMALL);
assert_eq!(e.layers, 0x2010);
assert_eq!(e.set, 0x0155);
}
assert_eq!(
c.entries[0].value,
OsdSyncValue::Caption(OsdCaption {
font: "Sans".to_owned(),
size: 0x0124,
x: -0x0203,
y: 0x0405,
wrap: -2,
align: OsdCaptionAlign::RIGHT,
format: OsdCaptionFormat::MARKDOWN,
colour: 0x00A1_B2C3,
text: "No signal".to_owned(),
})
);
assert_eq!(
c.entries[1].value,
OsdSyncValue::Logo(OsdLogo {
logo: 3,
x: -0x0203,
y: 0x0405,
})
);
assert_eq!(c.entries[2].value, OsdSyncValue::Background(0x00A1_B2C3));
}
#[test]
fn null_strings_read_as_empty() {
let mut e = entry(MXR_OSD_SYNC_TYPE_CAPTION);
e.font = ptr::null();
e.text = ptr::null();
let c = unsafe { osd_content(1, &e, 1, ptr::null()) }.expect("read");
let OsdSyncValue::Caption(caption) = &c.entries[0].value else {
panic!("not a caption");
};
assert_eq!((caption.font.as_str(), caption.text.as_str()), ("", ""));
}
#[test]
fn images_are_copied_slot_by_slot() {
let png = [0x89u8, b'P', b'N', b'G'];
let images: [mxr_osd_sync_image_t; MXR_OSD_SYNC_SLOTS] = [
mxr_osd_sync_image_t {
png: png.as_ptr(),
len: 4,
percent: 25,
},
mxr_osd_sync_image_t {
png: ptr::null(),
len: 0,
percent: 0,
},
mxr_osd_sync_image_t {
png: png.as_ptr(),
len: 2,
percent: 100,
},
mxr_osd_sync_image_t {
png: png.as_ptr(),
len: 0,
percent: 7,
},
];
let c = unsafe { osd_content(1, ptr::null(), 0, images.as_ptr()) }.expect("read");
let got = c.images.expect("images");
assert_eq!(got[0].png, png);
assert_eq!(got[0].percent, 25);
assert!(got[1].png.is_empty());
assert_eq!(got[2].png, &png[..2]);
assert_eq!(got[2].percent, 100);
assert!(got[3].png.is_empty());
assert_eq!(got[3].percent, 7);
}
#[test]
fn a_sent_sync_reports_its_number_and_a_failed_one_does_not() {
let mut seq = 0u16;
assert_eq!(
unsafe { sent_seq(Ok(0xA1B2), &mut seq) },
mxr_result_t::MXR_OK
);
assert_eq!(seq, 0xA1B2);
assert_eq!(
unsafe { sent_seq(Ok(1), ptr::null_mut()) },
mxr_result_t::MXR_OK
);
let refused = Err(ControlError::InvalidRequest("no overlay sync is held"));
let rc = unsafe { sent_seq(refused, &mut seq) };
assert_eq!(rc, mxr_result_t::MXR_ERR_INVALID_ARGUMENT);
assert_eq!(seq, 0xA1B2);
}
#[test]
fn what_the_caller_cannot_mean_is_refused() {
let bad = entry(3);
let r = unsafe { osd_content(1, &bad, 1, ptr::null()) };
assert!(
matches!(r, Err(mxr_result_t::MXR_ERR_INVALID_ARGUMENT)),
"kind"
);
let r = unsafe { osd_content(1, ptr::null(), 1, ptr::null()) };
assert!(
matches!(r, Err(mxr_result_t::MXR_ERR_INVALID_ARGUMENT)),
"entries"
);
let images = [mxr_osd_sync_image_t {
png: ptr::null(),
len: 1,
percent: 0,
}; MXR_OSD_SYNC_SLOTS];
let r = unsafe { osd_content(1, ptr::null(), 0, images.as_ptr()) };
assert!(
matches!(r, Err(mxr_result_t::MXR_ERR_INVALID_ARGUMENT)),
"png"
);
}
}