use std::ffi::NulError;
use std::str::Utf8Error;
use std::string::FromUtf8Error;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum BrlApiError {
#[doc(hidden)]
#[error("Success")]
Success,
#[error("Out of memory. The system may be low on available memory")]
NoMem,
#[error(
"The TTY is already in use by another BrlAPI connection. Try a different TTY or close other braille applications"
)]
TTYBusy,
#[error("The braille device is busy. Another application may be using it in RAW mode")]
DeviceBusy,
#[error("This operation is not implemented in the BrlAPI protocol")]
UnknownInstruction,
#[error("Operation forbidden in current mode")]
IllegalInstruction,
#[error("Invalid parameter value provided")]
InvalidParameter,
#[error("Invalid packet size")]
InvalidPacket,
#[error(
"Failed to connect to BrlAPI server. Is BRLTTY running? Try 'sudo systemctl start brltty'"
)]
ConnectionRefused,
#[error("This operation is not supported by your braille display or BRLTTY version")]
OperationNotSupported,
#[error("Network address resolution error")]
GetAddrInfoError,
#[error("System library error occurred")]
LibCError,
#[error(
"Could not determine which TTY to use. Try running from a console (Ctrl+Alt+F2) or use enter_tty_mode_auto()"
)]
UnknownTTY,
#[error(
"BrlAPI protocol version mismatch. Your BrlAPI library and BRLTTY daemon versions may be incompatible"
)]
ProtocolVersion,
#[error("Unexpected end of file or connection closed")]
UnexpectedEndOfFile,
#[error("BrlAPI authentication key file is empty")]
EmptyKey,
#[error(
"Braille display driver error. Check your braille device connection and BRLTTY configuration"
)]
DriverError,
#[error("BrlAPI authentication failed. Check your BrlAPI key or permissions")]
AuthenticationFailed,
#[error("Parameter cannot be changed (read-only)")]
ParameterCannotBeChanged,
#[error("Connection attempt timed out. The BrlAPI daemon may be unresponsive or unreachable")]
ConnectionTimeout,
#[error("String contains null bytes and cannot be converted to C string: {0}")]
NullByteInString(#[from] NulError),
#[error("Invalid UTF-8 sequence: {0}")]
InvalidUtf8(#[from] Utf8Error),
#[error("Failed to convert bytes to UTF-8 string: {0}")]
StringConversion(#[from] FromUtf8Error),
#[error(
"BrlAPI function returned unexpected value: {value}. This may indicate a protocol error or library bug"
)]
UnexpectedReturnValue { value: i32 },
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("Thread join error: {0}")]
ThreadJoin(String),
#[error("Channel communication error during timeout operation")]
ChannelError,
#[error("Text contraction failed: {0}")]
ContractionError(#[from] liblouis::LouisError),
#[error("{message}")]
Custom { message: String },
}
impl BrlApiError {
pub fn from_c_error(error_code: i32) -> Self {
match error_code as u32 {
brlapi_sys::brlapi_error::BRLAPI_ERROR_SUCCESS => BrlApiError::Success,
brlapi_sys::brlapi_error::BRLAPI_ERROR_NOMEM => BrlApiError::NoMem,
brlapi_sys::brlapi_error::BRLAPI_ERROR_TTYBUSY => BrlApiError::TTYBusy,
brlapi_sys::brlapi_error::BRLAPI_ERROR_DEVICEBUSY => BrlApiError::DeviceBusy,
brlapi_sys::brlapi_error::BRLAPI_ERROR_UNKNOWN_INSTRUCTION => {
BrlApiError::UnknownInstruction
}
brlapi_sys::brlapi_error::BRLAPI_ERROR_ILLEGAL_INSTRUCTION => {
BrlApiError::IllegalInstruction
}
brlapi_sys::brlapi_error::BRLAPI_ERROR_INVALID_PARAMETER => {
BrlApiError::InvalidParameter
}
brlapi_sys::brlapi_error::BRLAPI_ERROR_INVALID_PACKET => BrlApiError::InvalidPacket,
brlapi_sys::brlapi_error::BRLAPI_ERROR_CONNREFUSED => BrlApiError::ConnectionRefused,
brlapi_sys::brlapi_error::BRLAPI_ERROR_OPNOTSUPP => BrlApiError::OperationNotSupported,
brlapi_sys::brlapi_error::BRLAPI_ERROR_GAIERR => BrlApiError::GetAddrInfoError,
brlapi_sys::brlapi_error::BRLAPI_ERROR_LIBCERR => BrlApiError::LibCError,
brlapi_sys::brlapi_error::BRLAPI_ERROR_UNKNOWNTTY => BrlApiError::UnknownTTY,
brlapi_sys::brlapi_error::BRLAPI_ERROR_PROTOCOL_VERSION => BrlApiError::ProtocolVersion,
brlapi_sys::brlapi_error::BRLAPI_ERROR_EOF => BrlApiError::UnexpectedEndOfFile,
brlapi_sys::brlapi_error::BRLAPI_ERROR_EMPTYKEY => BrlApiError::EmptyKey,
brlapi_sys::brlapi_error::BRLAPI_ERROR_DRIVERERROR => BrlApiError::DriverError,
brlapi_sys::brlapi_error::BRLAPI_ERROR_AUTHENTICATION => {
BrlApiError::AuthenticationFailed
}
brlapi_sys::brlapi_error::BRLAPI_ERROR_READONLY_PARAMETER => {
BrlApiError::ParameterCannotBeChanged
}
65536 => BrlApiError::ConnectionTimeout, _ => BrlApiError::OperationNotSupported, }
}
pub fn get_last_error() -> Self {
unsafe {
let error_ptr = brlapi_sys::brlapi_error_location();
if !error_ptr.is_null() {
let error_struct = &*error_ptr;
return Self::from_c_error(error_struct.brlerrno as i32);
}
BrlApiError::OperationNotSupported
}
}
pub fn get_error_message() -> Option<String> {
unsafe {
let error_ptr = brlapi_sys::brlapi_error_location();
if error_ptr.is_null() {
return None;
}
let message_ptr = brlapi_sys::brlapi_strerror(error_ptr);
if message_ptr.is_null() {
return None;
}
let c_str = std::ffi::CStr::from_ptr(message_ptr);
c_str.to_str().ok().map(|s| s.to_string())
}
}
pub fn from_brlapi_error() -> Self {
Self::get_last_error()
}
pub fn custom(message: impl Into<String>) -> Self {
BrlApiError::Custom {
message: message.into(),
}
}
pub fn raw_mode_not_supported(driver: &str) -> Self {
BrlApiError::Custom {
message: format!(
"Raw mode not supported by driver '{}'. Raw mode is only available for certain hardware drivers.",
driver
),
}
}
pub fn raw_mode_in_use() -> Self {
BrlApiError::DeviceBusy }
pub fn raw_mode_receive_timeout(timeout_secs: u64) -> Self {
BrlApiError::Custom {
message: format!(
"Raw mode receive operation timed out after {} seconds. The device may not be responding.",
timeout_secs
),
}
}
pub fn raw_mode_device_error(details: &str) -> Self {
BrlApiError::Custom {
message: format!(
"Raw mode device communication failed: {}. The device may be disconnected or unresponsive.",
details
),
}
}
pub fn suspend_failed(driver: &str, details: &str) -> Self {
BrlApiError::Custom {
message: format!(
"Failed to suspend driver '{}': {}. The driver may be busy or not support suspension.",
driver, details
),
}
}
pub fn resume_failed(driver: &str, details: &str) -> Self {
BrlApiError::Custom {
message: format!(
"Failed to resume driver '{}': {}. Manual intervention may be required.",
driver, details
),
}
}
pub fn driver_not_found(driver: &str) -> Self {
BrlApiError::Custom {
message: format!(
"Driver '{}' not found or not currently active. Check that the driver is loaded and the device is connected.",
driver
),
}
}
pub fn suspend_mode_not_supported(driver: &str) -> Self {
BrlApiError::Custom {
message: format!(
"Suspend mode not supported by driver '{}'. Only certain hardware drivers support complete suspension.",
driver
),
}
}
pub fn not_suspended() -> Self {
BrlApiError::Custom {
message: "No driver is currently suspended. Resume can only be called when a driver is suspended.".to_string(),
}
}
pub fn unexpected_return_value(value: i32) -> Self {
BrlApiError::UnexpectedReturnValue { value }
}
pub fn is_connection_error(&self) -> bool {
matches!(
self,
BrlApiError::ConnectionRefused
| BrlApiError::AuthenticationFailed
| BrlApiError::GetAddrInfoError
| BrlApiError::LibCError
| BrlApiError::ProtocolVersion
| BrlApiError::UnexpectedEndOfFile
| BrlApiError::ConnectionTimeout
| BrlApiError::ChannelError
)
}
pub fn is_resource_busy(&self) -> bool {
matches!(
self,
BrlApiError::TTYBusy | BrlApiError::DeviceBusy | BrlApiError::NoMem
)
}
pub fn is_operation_error(&self) -> bool {
matches!(
self,
BrlApiError::OperationNotSupported
| BrlApiError::UnknownInstruction
| BrlApiError::IllegalInstruction
| BrlApiError::InvalidParameter
| BrlApiError::InvalidPacket
| BrlApiError::ParameterCannotBeChanged
| BrlApiError::UnexpectedReturnValue { .. }
)
}
pub fn is_conversion_error(&self) -> bool {
matches!(
self,
BrlApiError::NullByteInString(_)
| BrlApiError::InvalidUtf8(_)
| BrlApiError::StringConversion(_)
)
}
pub fn is_contraction_error(&self) -> bool {
matches!(self, BrlApiError::ContractionError(_))
}
pub fn suggestions(&self) -> Vec<&'static str> {
match self {
BrlApiError::ConnectionRefused => vec![
"Start BRLTTY daemon: sudo systemctl start brltty",
"Check if BRLTTY is running: systemctl status brltty",
"Verify BrlAPI is enabled in /etc/brltty.conf",
],
BrlApiError::AuthenticationFailed => vec![
"Check BrlAPI key file permissions: ls -la /etc/brlapi.key",
"Add your user to the brlapi group: sudo usermod -a -G brlapi $USER",
"Restart your session after adding to group",
],
BrlApiError::TTYBusy => vec![
"Close other braille applications",
"Try a different virtual console (TTY 2-6)",
"Check for running braille applications: ps aux | grep brl",
],
BrlApiError::UnknownTTY => vec![
"Run from a text console: press Ctrl+Alt+F2",
"Use enter_tty_mode_auto() for automatic TTY detection",
"Specify a TTY explicitly with enter_tty_mode_with_tty(Some(2))",
],
BrlApiError::DriverError => vec![
"Check braille display connection (USB/Bluetooth)",
"Verify correct driver in /etc/brltty.conf",
"Test display with: sudo brltty -l debug",
],
BrlApiError::ConnectionTimeout => vec![
"Check if BRLTTY daemon is running: systemctl status brltty",
"Start BRLTTY daemon: sudo systemctl start brltty",
"Increase connection timeout with ConnectionSettings::set_timeout()",
"Check network connectivity if connecting to remote host",
],
BrlApiError::NullByteInString(_) => vec![
"Remove null bytes (\\0) from your string",
"Use a different string that doesn't contain null characters",
],
BrlApiError::InvalidUtf8(_) | BrlApiError::StringConversion(_) => vec![
"Ensure input text is valid UTF-8",
"Check the source of the text data for encoding issues",
],
_ => vec![],
}
}
}
impl<T> From<std::sync::mpsc::SendError<T>> for BrlApiError {
fn from(_: std::sync::mpsc::SendError<T>) -> Self {
BrlApiError::ChannelError
}
}
impl From<std::sync::mpsc::RecvError> for BrlApiError {
fn from(_: std::sync::mpsc::RecvError) -> Self {
BrlApiError::ChannelError
}
}
impl From<std::sync::mpsc::RecvTimeoutError> for BrlApiError {
fn from(err: std::sync::mpsc::RecvTimeoutError) -> Self {
match err {
std::sync::mpsc::RecvTimeoutError::Timeout => BrlApiError::ConnectionTimeout,
std::sync::mpsc::RecvTimeoutError::Disconnected => BrlApiError::ChannelError,
}
}
}
impl<T: std::fmt::Debug> From<std::sync::PoisonError<T>> for BrlApiError {
fn from(err: std::sync::PoisonError<T>) -> Self {
BrlApiError::custom(format!("Mutex poisoned: {:?}", err))
}
}
pub type Result<T> = std::result::Result<T, BrlApiError>;
#[macro_export]
macro_rules! brlapi_call {
($call:expr) => {{
let result = $call;
if result == -1 {
Err($crate::error::BrlApiError::from_brlapi_error())
} else {
Ok(result)
}
}};
}