brlapi 0.4.1

Safe Rust bindings for the BrlAPI library
// SPDX-License-Identifier: LGPL-2.1

//! High-level Rust bindings for BrlAPI
//!
//! This crate provides safe, idiomatic Rust bindings for BrlAPI, the
//! Application Programming Interface for BRLTTY (the screen reader for
//! blind people using braille displays).
//!
//! # Overview
//!
//! BrlAPI allows applications to interact with braille displays through the
//! BRLTTY daemon. This crate provides:
//!
//! - Safe connection management with automatic cleanup
//! - Thread-safe operations using handle-based API
//! - Rust-friendly error handling with thiserror integration
//! - High-level abstractions for display operations
//!
//! # Quick Start
//!
//! For a single message (most convenient):
//! ```no_run
//! use brlapi::util;
//!
//! fn main() -> Result<(), brlapi::BrlApiError> {
//!     // Send a quick message - handles everything automatically
//!     util::send_message("System notification: Build completed successfully")?;
//!     Ok(())
//! }
//! ```
//!
//! For multiple messages with manual management:
//! ```no_run
//! use brlapi::{Connection, text};
//!
//! fn main() -> Result<(), brlapi::BrlApiError> {
//!     // Connect to BrlAPI with default settings
//!     let connection = Connection::open()?;
//!
//!     // Get display information
//!     let (width, height) = connection.display_size()?;
//!     println!("Display: {width}x{height}");
//!
//!     // Send multiple oneshot messages
//!     text::util::write_message(&connection, "Processing file 1 of 5")?;
//!     text::util::write_message(&connection, "Processing file 2 of 5")?;
//!     text::util::write_message(&connection, "All files processed")?;
//!
//!     Ok(())
//! }
//! ```
//!
//! For sustained display control (most efficient):
//! ```no_run
//! use brlapi::{Connection, TtyMode};
//! use std::{thread, time::Duration};
//!
//! fn main() -> Result<(), brlapi::BrlApiError> {
//!     let connection = Connection::open()?;
//!     let (tty_mode, tty_num) = TtyMode::enter_auto(&connection, None)?;
//!     println!("Using virtual console {tty_num}");
//!
//!     // Write multiple messages efficiently
//!     tty_mode.write_text("Monitoring system status...")?;
//!     thread::sleep(Duration::from_secs(1));
//!     
//!     tty_mode.write_text("CPU usage: 45%, Memory: 2.1GB")?;
//!     thread::sleep(Duration::from_secs(1));
//!     
//!     tty_mode.write_text("All systems operational")?;
//!     
//!     // TTY mode automatically exited when tty_mode is dropped
//!     Ok(())
//! }
//! ```
//!
//! For interactive applications with key handling:
//! ```no_run
//! use brlapi::{Connection, TtyMode, keys::constants};
//! use std::time::Duration;
//!
//! fn main() -> Result<(), brlapi::BrlApiError> {
//!     let connection = Connection::open()?;
//!     let (tty_mode, _) = TtyMode::enter_auto(&connection, None)?;
//!     let key_reader = tty_mode.key_reader();
//!     
//!     // Accept only arrow keys and escape
//!     key_reader.accept_keys(brlapi::RangeType::Code, &[
//!         constants::KEY_SYM_LEFT,
//!         constants::KEY_SYM_RIGHT,
//!         constants::KEY_SYM_UP,
//!         constants::KEY_SYM_DOWN,
//!         constants::KEY_SYM_ESCAPE,
//!     ])?;
//!
//!     tty_mode.write_text("Use arrow keys to navigate, ESC to quit")?;
//!     
//!     loop {
//!         match key_reader.read_key_timeout(Duration::from_secs(1))? {
//!             Some(key_code) => {
//!                 if key_code == constants::KEY_SYM_ESCAPE {
//!                     tty_mode.write_text("Goodbye!")?;
//!                     break;
//!                 } else if constants::utils::is_keysym(key_code) {
//!                     tty_mode.write_text(&format!("Key: {:04X}", key_code))?;
//!                 }
//!             }
//!             None => {
//!                 // Timeout - continue waiting
//!             }
//!         }
//!     }
//!     
//!     Ok(())
//! }
//! ```
//!
//! For display information and capabilities:
//! ```no_run
//! use brlapi::Connection;
//!
//! fn main() -> Result<(), brlapi::BrlApiError> {
//!     let connection = Connection::open()?;
//!     
//!     // Convenient direct access via connection
//!     let display = connection.display_info()?;
//!     println!("Connected to {} braille display:", display.driver_name());
//!     println!("  Model: {}", display.model_identifier());
//!     println!("  Size: {}x{} cells ({} total)",
//!              display.width(), display.height(), display.total_cells());
//!     
//!     if display.is_single_line() {
//!         println!("  Type: Standard single-line display");
//!     } else {
//!         println!("  Type: Multi-line display");
//!     }
//!     
//!     // Or query individual properties
//!     let (width, height) = connection.display_size()?;
//!     let driver = connection.display_driver()?;
//!     
//!     Ok(())
//! }
//! ```
//!
//! # Connection Management
//!
//! Connections are managed through the [`Connection`] struct, which automatically
//! handles cleanup when dropped:
//!
//! ```no_run
//! use brlapi::{Connection, ConnectionSettings};
//!
//! // Default connection
//! let conn = Connection::open()?;
//!
//! // Custom connection settings
//! let settings = ConnectionSettings::localhost();
//! let conn = Connection::open_with_settings(Some(&settings))?;
//!
//! // Connection is automatically closed when conn goes out of scope
//! # Ok::<(), brlapi::BrlApiError>(())
//! ```
//!
//! # TTY Mode Management
//!
//! For RAII-style TTY mode management, use the [`TtyMode`] wrapper:
//!
//! ```no_run
//! use brlapi::{Connection, TtyMode};
//!
//! fn main() -> Result<(), brlapi::BrlApiError> {
//!     let connection = Connection::open()?;
//!
//!     // Automatic TTY detection with RAII cleanup
//!     let (tty_mode, tty_num) = TtyMode::enter_auto(&connection, None)?;
//!     println!("Using virtual console {tty_num}");
//!     
//!     tty_mode.write_text("Application started successfully")?;
//!     // TTY mode automatically exited when tty_mode is dropped
//!     
//!     Ok(())
//! }
//! ```
//!
//! For compatibility with existing code, you can also use `TryFrom`:
//!
//! ```no_run
//! use brlapi::{Connection, TtyMode};
//!
//! fn main() -> Result<(), brlapi::BrlApiError> {
//!     let connection = Connection::open()?;
//!     let tty_mode = TtyMode::try_from(&connection)?;
//!     
//!     tty_mode.write_text("Legacy mode compatibility test")?;
//!     
//!     Ok(())
//! }
//! ```
//!
//! # Cooperative Display Sharing
//!
//! For applications that need to share the braille display politely with
//! screen readers like Orca, use the [`cooperative`] module:
//!
//! ```no_run
//! use brlapi::cooperative;
//!
//! fn main() -> Result<(), brlapi::BrlApiError> {
//!     // Simple notifications that automatically cooperate with screen readers
//!     cooperative::notify("Build completed successfully")?;
//!     cooperative::alert("Critical error occurred!")?;
//!
//!     Ok(())
//! }
//! ```
//!
//! For applications that need ongoing display access:
//! ```no_run
//! use brlapi::cooperative::{CooperativeDisplay, AppType};
//!
//! fn main() -> Result<(), brlapi::BrlApiError> {
//!     let mut display = CooperativeDisplay::open(AppType::UserApp)?;
//!
//!     // Show brief messages that automatically yield to screen readers
//!     display.show_status("Processing file 5 of 20")?;
//!     display.show_brief_message("Press Enter to continue", std::time::Duration::from_secs(3))?;
//!
//!     Ok(())
//! }
//! ```

pub mod connection;
pub mod cooperative;
pub mod display;
pub mod error;
pub mod keys;
pub mod parameters;
#[cfg(feature = "dangerous-raw-mode")]
pub mod raw;
pub mod settings;
#[cfg(feature = "dangerous-suspend-mode")]
pub mod suspend;
pub mod text;
pub mod tty;

// Re-export main types for convenience
pub use connection::Connection;
pub use cooperative::{AppType, CooperativeDisplay};
pub use display::Display;
pub use error::{BrlApiError, Result};
pub use keys::{KeyCode, KeyRange, KeyReader, RangeType};
pub use parameters::polling::ParameterMonitor;
pub use settings::ConnectionSettings;
pub use text::{CursorPosition, TextWriter};
pub use tty::TtyMode;

/// Library version information
pub mod version {
    /// Get the BrlAPI library version as a tuple (major, minor, revision)
    pub fn library_version() -> (i32, i32, i32) {
        let mut major = 0;
        let mut minor = 0;
        let mut revision = 0;

        unsafe {
            brlapi_sys::brlapi_getLibraryVersion(&mut major, &mut minor, &mut revision);
        }

        (major, minor, revision)
    }

    /// Get the BrlAPI library version as a string
    pub fn library_version_string() -> String {
        let (major, minor, revision) = library_version();
        format!("{major}.{minor}.{revision}")
    }

    /// Get the expected BrlAPI library version from compile-time constants
    pub fn expected_version() -> (u32, u32, u32) {
        (
            brlapi_sys::BRLAPI_MAJOR,
            brlapi_sys::BRLAPI_MINOR,
            brlapi_sys::BRLAPI_REVISION,
        )
    }
}

/// Utility functions for working with BrlAPI
pub mod util {
    use crate::BrlApiError;

    /// Get the size required for a BrlAPI handle
    pub fn handle_size() -> usize {
        unsafe { brlapi_sys::brlapi_getHandleSize() }
    }

    /// Check if a file descriptor represents a valid BrlAPI connection
    pub fn is_valid_file_descriptor(fd: i32) -> bool {
        fd >= 3 && fd != brlapi_sys::BRLAPI_INVALID_FILE_DESCRIPTOR
    }

    /// Send a quick message to the braille display (global convenience function)
    ///
    /// This is the most convenient way to send a single message. It handles
    /// connection establishment, TTY mode management, and cleanup automatically.
    ///
    /// For applications that send multiple messages, it's more efficient to
    /// maintain a `Connection` and use the `text::util` functions or `TtyMode`.
    ///
    /// # Example
    /// ```no_run
    /// use brlapi::util;
    ///
    /// fn main() -> Result<(), brlapi::BrlApiError> {
    ///     util::send_message("Server maintenance complete")?;
    ///     Ok(())
    /// }
    /// ```
    pub fn send_message(text: &str) -> Result<(), BrlApiError> {
        crate::text::util::send_message(text)
    }
}

// Include comprehensive documentation tests
#[cfg(doctest)]
mod doc_tests {
    //! Documentation examples that are tested but don't run by default
    //! since they require a BrlAPI daemon to be running.
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_version_info() {
        let (major, minor, revision) = version::library_version();
        println!("BrlAPI version: {major}.{minor}.{revision}");

        // Versions should be non-negative
        assert!(major >= 0);
        assert!(minor >= 0);
        assert!(revision >= 0);

        let version_string = version::library_version_string();
        assert!(!version_string.is_empty());
        println!("Version string: {version_string}");
    }

    #[test]
    fn test_expected_version() {
        let (major, minor, revision) = version::expected_version();
        println!("Expected BrlAPI version: {major}.{minor}.{revision}");

        // Should match compile-time constants (testing our wrapper function)
        assert_eq!(major, brlapi_sys::BRLAPI_MAJOR);
        assert_eq!(minor, brlapi_sys::BRLAPI_MINOR);
        assert_eq!(revision, brlapi_sys::BRLAPI_REVISION);
    }

    #[test]
    fn test_handle_size() {
        let size = util::handle_size();
        println!("BrlAPI handle size: {size} bytes");

        // Handle size should be reasonable
        assert!(size > 0);
        assert!(size < 16384); // 16KB - generous upper bound for a handle struct
    }

    #[test]
    fn test_file_descriptor_validation() {
        // Test valid file descriptors
        assert!(!util::is_valid_file_descriptor(0));
        assert!(!util::is_valid_file_descriptor(1));
        assert!(util::is_valid_file_descriptor(10));

        // Test invalid file descriptors
        assert!(!util::is_valid_file_descriptor(-1));
        assert!(!util::is_valid_file_descriptor(
            brlapi_sys::BRLAPI_INVALID_FILE_DESCRIPTOR
        ));
    }
}