use crate::{BrlApiError, Connection, Result, brlapi_call};
use std::ffi::CString;
#[derive(Debug)]
pub struct SuspendMode<'a> {
connection: &'a Connection,
driver_name: String,
suspended: bool,
}
impl<'a> SuspendMode<'a> {
pub fn enter(connection: &'a Connection, driver: &str) -> Result<Self> {
if driver.is_empty() {
return Err(BrlApiError::custom("Driver name cannot be empty"));
}
let current_driver = connection.display_driver().map_err(|e| {
BrlApiError::custom(&format!("Cannot verify driver '{}' exists: {}", driver, e))
})?;
if current_driver != driver {
return Err(BrlApiError::custom(&format!(
"Driver '{}' is not currently active (current: '{}')",
driver, current_driver
)));
}
let driver_cstring = CString::new(driver)
.map_err(|_| BrlApiError::custom("Driver name contains null bytes"))?;
crate::brlapi_call!(unsafe {
brlapi_sys::brlapi__suspendDriver(connection.handle_ptr(), driver_cstring.as_ptr())
}).map_err(|e| BrlApiError::suspend_failed(driver, &e.to_string()))?;
Ok(Self {
connection,
driver_name: driver.to_string(),
suspended: true,
})
}
pub fn is_suspended(&self) -> bool {
self.suspended
}
pub fn driver_name(&self) -> &str {
&self.driver_name
}
pub fn resume(&mut self) -> Result<()> {
if !self.suspended {
return Err(BrlApiError::custom("No driver is currently suspended"));
}
match crate::brlapi_call!(unsafe { brlapi_sys::brlapi__resumeDriver(self.connection.handle_ptr()) })
.map_err(|e| BrlApiError::resume_failed(&self.driver_name, &e.to_string()))
{
Ok(_) => {
self.suspended = false;
Ok(())
}
Err(e) => {
self.suspended = false;
Err(e)
}
}
}
pub fn is_driver_supported(driver: &str) -> bool {
match driver {
"nb" | "NoBraille" => false,
"fake" | "test" | "dummy" => false,
_ => true,
}
}
}
impl Drop for SuspendMode<'_> {
fn drop(&mut self) {
if self.suspended {
if let Err(e) = self.resume() {
eprintln!(
"WARNING: Failed to resume driver '{}' during cleanup: {}",
self.driver_name, e
);
eprintln!("The braille device may be left in an inaccessible state!");
eprintln!("You may need to restart the BrlAPI daemon or reboot the system.");
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_driver_support_detection() {
assert!(!SuspendMode::is_driver_supported("nb"));
assert!(!SuspendMode::is_driver_supported("NoBraille"));
assert!(!SuspendMode::is_driver_supported("fake"));
assert!(!SuspendMode::is_driver_supported("test"));
assert!(!SuspendMode::is_driver_supported("dummy"));
assert!(SuspendMode::is_driver_supported("baum"));
assert!(SuspendMode::is_driver_supported("hims"));
assert!(SuspendMode::is_driver_supported("alva"));
assert!(SuspendMode::is_driver_supported("papenmeier"));
assert!(SuspendMode::is_driver_supported("freedom"));
assert!(SuspendMode::is_driver_supported("unknown_driver"));
}
#[test]
fn test_driver_name_validation() {
let connection = Connection::open();
if connection.is_err() {
println!("Skipping suspend mode driver validation tests - no BrlAPI daemon");
return;
}
let connection = connection
.expect("Connection should be available for suspend mode driver validation tests");
let result = SuspendMode::enter(&connection, "");
assert!(result.is_err());
if let Err(e) = result {
assert!(e.to_string().contains("empty"));
}
let result = SuspendMode::enter(&connection, "driver\0name");
assert!(result.is_err());
if let Err(e) = result {
assert!(e.to_string().contains("null bytes"));
}
}
}