#![cfg_attr(docsrs, feature(doc_cfg))]
mod sys;
#[cfg(feature = "poll")]
use nix::poll::{poll, PollFd};
#[cfg(feature = "poll")]
#[cfg_attr(docsrs, doc(cfg(feature = "poll")))]
pub use nix::poll::{PollFlags, PollTimeout};
use std::{
io::Result,
mem::MaybeUninit,
os::fd::{AsFd, AsRawFd},
};
use sys::{
capabilities, get_event, get_log, get_mode, get_phys, receive, set_log, set_mode, set_phys,
transmit, CecEventType, CecTxError, RxStatus, CEC_MODE_FOLLOWER_MSK, CEC_MODE_INITIATOR_MSK,
};
pub use sys::{
Capabilities, CecAbortReason, CecCaps, CecEventLostMsgs, CecEventStateChange, CecLogAddrFlags,
CecLogAddrMask, CecLogAddrType, CecLogAddrs, CecLogicalAddress, CecModeFollower,
CecModeInitiator, CecMsg, CecOpcode, CecPhysicalAddress, CecPowerStatus, CecPrimDevType,
CecTimer, CecUserControlCode, DeckControlMode, DeckInfo, DisplayControl, MenuRequestType,
OSDStr, PlayMode, RecordingSequence, StatusRequest, TxStatus, VendorID, Version,
};
#[cfg(feature = "tokio")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
pub mod tokio;
pub struct CecDevice(std::fs::File);
impl CecDevice {
pub fn open<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(path)
.map(Self)
}
#[cfg(feature = "poll")]
#[cfg_attr(docsrs, doc(cfg(feature = "poll")))]
pub fn poll<T: Into<PollTimeout>>(&self, events: PollFlags, timeout: T) -> Result<PollFlags> {
let mut fds = [PollFd::new(self.0.as_fd(), events)];
poll(&mut fds, timeout)?;
fds[0].revents().ok_or(std::io::ErrorKind::Other.into())
}
pub fn get_capas(&self) -> Result<CecCaps> {
let mut capas = MaybeUninit::uninit();
unsafe { capabilities(self.0.as_raw_fd(), capas.as_mut_ptr()) }?;
Ok(unsafe { capas.assume_init() })
}
pub fn set_mode(&self, initiator: CecModeInitiator, follower: CecModeFollower) -> Result<()> {
let mode = u32::from(initiator) | u32::from(follower);
unsafe { set_mode(self.0.as_raw_fd(), &mode) }?;
Ok(())
}
pub fn get_mode(&self) -> Result<(CecModeInitiator, CecModeFollower)> {
let mut mode = 0;
unsafe { get_mode(self.0.as_raw_fd(), &mut mode) }?;
let i = CecModeInitiator::try_from(mode & CEC_MODE_INITIATOR_MSK);
let e = CecModeFollower::try_from(mode & CEC_MODE_FOLLOWER_MSK);
match (i, e) {
(Ok(i), Ok(e)) => Ok((i, e)),
_ => Err(std::io::ErrorKind::Other.into()),
}
}
pub fn set_phys(&self, addr: CecPhysicalAddress) -> Result<()> {
unsafe { set_phys(self.0.as_raw_fd(), &addr) }?;
Ok(())
}
pub fn get_phys(&self) -> Result<CecPhysicalAddress> {
let mut addr = CecPhysicalAddress::INVALID;
unsafe { get_phys(self.0.as_raw_fd(), &mut addr) }?;
Ok(addr)
}
pub fn set_log(&self, mut log: CecLogAddrs) -> Result<()> {
unsafe { set_log(self.0.as_raw_fd(), &mut log) }?;
Ok(())
}
pub fn get_log(&self) -> Result<CecLogAddrs> {
let mut log = MaybeUninit::uninit();
unsafe { get_log(self.0.as_raw_fd(), log.as_mut_ptr()) }?;
Ok(unsafe { log.assume_init() })
}
pub fn get_event(&self) -> Result<CecEvent> {
let mut evt = MaybeUninit::uninit();
unsafe {
get_event(self.0.as_raw_fd(), evt.as_mut_ptr())?;
let evt = evt.assume_init();
match evt.typ {
CecEventType::LostMsgs => return Ok(CecEvent::LostMsgs(evt.payload.lost_msgs)),
CecEventType::StateChange => {
return Ok(CecEvent::StateChange(evt.payload.state_change))
}
}
}
Err(std::io::ErrorKind::Other.into())
}
pub fn turn_on(&self, from: CecLogicalAddress, to: CecLogicalAddress) -> Result<()> {
if to == CecLogicalAddress::Tv {
self.transmit(from, to, CecOpcode::ImageViewOn)
} else {
self.keypress(from, to, CecUserControlCode::Power)
}
}
pub fn keypress(
&self,
from: CecLogicalAddress,
to: CecLogicalAddress,
key: CecUserControlCode,
) -> Result<()> {
self.transmit_data(from, to, CecOpcode::UserControlPressed, &[key.into()])?;
self.transmit(from, to, CecOpcode::UserControlReleased)
}
pub fn transmit_poll(&self, from: CecLogicalAddress, to: CecLogicalAddress) -> Result<()> {
let mut msg = CecMsg::init(from, to);
unsafe { transmit(self.0.as_raw_fd(), &mut msg) }?;
msg_to_io_result(msg)
}
pub fn transmit(
&self,
from: CecLogicalAddress,
to: CecLogicalAddress,
opcode: CecOpcode,
) -> Result<()> {
let mut msg = CecMsg::init(from, to);
msg.msg[1] = opcode.into();
msg.len = 2;
unsafe { transmit(self.0.as_raw_fd(), &mut msg) }?;
msg_to_io_result(msg)
}
pub fn transmit_data(
&self,
from: CecLogicalAddress,
to: CecLogicalAddress,
opcode: CecOpcode,
data: &[u8],
) -> Result<()> {
let mut msg = CecMsg::init(from, to);
msg.msg[1] = opcode.into();
msg.len = 2 + data.len() as u32;
msg.msg[2..msg.len as usize].copy_from_slice(data);
unsafe { transmit(self.0.as_raw_fd(), &mut msg) }?;
msg_to_io_result(msg)
}
pub fn request_data(
&self,
from: CecLogicalAddress,
to: CecLogicalAddress,
opcode: CecOpcode,
data: &[u8],
wait_for: CecOpcode,
) -> Result<Vec<u8>> {
let mut msg = CecMsg::init(from, to);
msg.msg[1] = opcode.into();
msg.len = 2 + data.len() as u32;
msg.msg[2..msg.len as usize].copy_from_slice(data);
msg.reply = wait_for;
msg.timeout = 1000;
unsafe { transmit(self.0.as_raw_fd(), &mut msg) }?;
if msg.reply == CecOpcode::FeatureAbort && !msg.tx_status.contains(TxStatus::OK) {
return Err(std::io::Error::new(
std::io::ErrorKind::TimedOut,
CecTxError::from(msg),
));
}
if msg.reply != CecOpcode::FeatureAbort
|| (msg.reply == CecOpcode::FeatureAbort
&& msg.rx_status.contains(RxStatus::FEATURE_ABORT))
{
let l = msg.len as usize;
let data = if l > 2 {
let mut data = Vec::with_capacity(l - 2);
data.extend_from_slice(&msg.msg[2..l]);
data
} else {
Vec::with_capacity(0)
};
return Ok(data);
}
Err(std::io::Error::new(
std::io::ErrorKind::TimedOut,
CecTxError::from(msg),
))
}
#[inline]
pub fn rec(&self) -> Result<CecMsg> {
self.rec_for(0)
}
pub fn rec_for(&self, timeout: u32) -> Result<CecMsg> {
let mut msg = MaybeUninit::uninit();
let ptr: *mut CecMsg = msg.as_mut_ptr();
unsafe { std::ptr::addr_of_mut!((*ptr).timeout).write(timeout) };
unsafe { receive(self.0.as_raw_fd(), ptr) }?;
Ok(unsafe { msg.assume_init() })
}
}
impl AsRawFd for CecDevice {
fn as_raw_fd(&self) -> std::os::unix::prelude::RawFd {
self.0.as_raw_fd()
}
}
#[derive(Debug)]
pub enum CecEvent {
StateChange(CecEventStateChange),
LostMsgs(CecEventLostMsgs),
}
fn msg_to_io_result(msg: CecMsg) -> Result<()> {
if msg.tx_status.contains(TxStatus::OK) || msg.tx_status.is_empty() {
Ok(())
} else {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
CecTxError::from(msg),
))
}
}