use crate::{BrlApiError, Result};
use std::time::Duration;
pub type KeyCode = brlapi_sys::brlapi_keyCode_t;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KeyReadResult {
Key(KeyCode),
NoKey,
Interrupted,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RangeType {
All,
Type,
Command,
Key,
Code,
}
impl From<RangeType> for brlapi_sys::brlapi_rangeType_t::Type {
fn from(range_type: RangeType) -> Self {
match range_type {
RangeType::All => brlapi_sys::brlapi_rangeType_t::brlapi_rangeType_all,
RangeType::Type => brlapi_sys::brlapi_rangeType_t::brlapi_rangeType_type,
RangeType::Command => brlapi_sys::brlapi_rangeType_t::brlapi_rangeType_command,
RangeType::Key => brlapi_sys::brlapi_rangeType_t::brlapi_rangeType_key,
RangeType::Code => brlapi_sys::brlapi_rangeType_t::brlapi_rangeType_code,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct KeyRange {
pub first: KeyCode,
pub last: KeyCode,
}
impl From<KeyRange> for brlapi_sys::brlapi_range_t {
fn from(range: KeyRange) -> Self {
brlapi_sys::brlapi_range_t {
first: range.first,
last: range.last,
}
}
}
impl KeyRange {
pub fn new(first: KeyCode, last: KeyCode) -> Self {
Self { first, last }
}
pub fn single(key: KeyCode) -> Self {
Self {
first: key,
last: key,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ExpandedKeyCode {
pub key_type: u32,
pub command: u32,
pub argument: u32,
pub flags: u32,
}
impl From<brlapi_sys::brlapi_expandedKeyCode_t> for ExpandedKeyCode {
fn from(expanded: brlapi_sys::brlapi_expandedKeyCode_t) -> Self {
Self {
key_type: expanded.type_,
command: expanded.command,
argument: expanded.argument,
flags: expanded.flags,
}
}
}
#[derive(Debug, Clone)]
pub struct DescribedKeyCode {
pub key_type: Option<String>,
pub command: Option<String>,
pub argument: u32,
pub flags: u32,
pub flag_descriptions: Vec<String>,
pub expanded: ExpandedKeyCode,
}
#[derive(Debug)]
pub struct KeyReader<'a> {
tty_mode: &'a crate::TtyMode<'a>,
}
impl<'a> KeyReader<'a> {
pub fn from_tty_mode(tty_mode: &'a crate::TtyMode<'a>) -> Self {
Self { tty_mode }
}
#[doc(hidden)]
pub fn new(tty_mode: &'a crate::TtyMode<'a>) -> Self {
Self::from_tty_mode(tty_mode)
}
fn handle_key_read_result(result: i32, key_code: KeyCode) -> Result<Option<KeyCode>> {
match result {
-1 => Err(BrlApiError::from_brlapi_error()),
0 => Ok(None), 1 => Ok(Some(key_code)), _ => Err(BrlApiError::unexpected_return_value(result)), }
}
pub fn read_key(&self, wait: bool) -> Result<Option<KeyCode>> {
let mut key_code: KeyCode = 0;
let wait_flag = if wait { 1 } else { 0 };
let result = unsafe {
brlapi_sys::brlapi__readKey(
self.tty_mode.connection().handle_ptr(),
wait_flag,
&mut key_code,
)
};
Self::handle_key_read_result(result, key_code)
}
pub fn read_key_timeout(&self, timeout: Duration) -> Result<Option<KeyCode>> {
let mut key_code: KeyCode = 0;
let timeout_ms = if timeout.is_zero() {
0
} else if timeout >= Duration::from_millis(i32::MAX as u64) {
-1 } else {
timeout.as_millis() as i32
};
let result = unsafe { brlapi_sys::brlapi_readKeyWithTimeout(timeout_ms, &mut key_code) };
Self::handle_key_read_result(result, key_code)
}
pub fn accept_keys(&self, range_type: RangeType, keys: &[KeyCode]) -> Result<()> {
crate::brlapi_call!(unsafe {
brlapi_sys::brlapi_acceptKeys(range_type.into(), keys.as_ptr(), keys.len() as u32)
})?;
Ok(())
}
pub fn ignore_keys(&self, range_type: RangeType, keys: &[KeyCode]) -> Result<()> {
crate::brlapi_call!(unsafe {
brlapi_sys::brlapi_ignoreKeys(range_type.into(), keys.as_ptr(), keys.len() as u32)
})?;
Ok(())
}
pub fn accept_key_ranges(&self, ranges: &[KeyRange]) -> Result<()> {
let c_ranges: Vec<brlapi_sys::brlapi_range_t> = ranges.iter().map(|&r| r.into()).collect();
crate::brlapi_call!(unsafe {
brlapi_sys::brlapi_acceptKeyRanges(c_ranges.as_ptr(), ranges.len() as u32)
})?;
Ok(())
}
pub fn ignore_key_ranges(&self, ranges: &[KeyRange]) -> Result<()> {
let c_ranges: Vec<brlapi_sys::brlapi_range_t> = ranges.iter().map(|&r| r.into()).collect();
crate::brlapi_call!(unsafe {
brlapi_sys::brlapi_ignoreKeyRanges(c_ranges.as_ptr(), ranges.len() as u32)
})?;
Ok(())
}
pub fn accept_all_keys(&self) -> Result<()> {
crate::brlapi_call!(unsafe {
brlapi_sys::brlapi__acceptAllKeys(self.tty_mode.connection().handle_ptr())
})?;
Ok(())
}
pub fn ignore_all_keys(&self) -> Result<()> {
crate::brlapi_call!(unsafe { brlapi_sys::brlapi_ignoreAllKeys() })?;
Ok(())
}
pub fn tty_mode(&self) -> &crate::TtyMode<'a> {
self.tty_mode
}
pub fn connection(&self) -> &crate::Connection {
self.tty_mode.connection()
}
pub fn expand_key_code(&self, key_code: KeyCode) -> Result<ExpandedKeyCode> {
let mut expansion: brlapi_sys::brlapi_expandedKeyCode_t = unsafe { std::mem::zeroed() };
crate::brlapi_call!(unsafe { brlapi_sys::brlapi_expandKeyCode(key_code, &mut expansion) })?;
Ok(ExpandedKeyCode::from(expansion))
}
pub fn describe_key_code(&self, key_code: KeyCode) -> Result<DescribedKeyCode> {
let mut description: brlapi_sys::brlapi_describedKeyCode_t = unsafe { std::mem::zeroed() };
crate::brlapi_call!(unsafe {
brlapi_sys::brlapi_describeKeyCode(key_code, &mut description)
})?;
let key_type = if description.type_.is_null() {
None
} else {
let c_str = unsafe { std::ffi::CStr::from_ptr(description.type_) };
c_str.to_str().ok().map(|s| s.to_owned())
};
let command = if description.command.is_null() {
None
} else {
let c_str = unsafe { std::ffi::CStr::from_ptr(description.command) };
c_str.to_str().ok().map(|s| s.to_owned())
};
let mut flag_descriptions = Vec::new();
for i in 0..32 {
if !description.flag[i].is_null() {
let c_str = unsafe { std::ffi::CStr::from_ptr(description.flag[i]) };
if let Ok(flag_str) = c_str.to_str() {
if !flag_str.is_empty() {
flag_descriptions.push(flag_str.to_owned());
}
}
}
}
Ok(DescribedKeyCode {
key_type,
command,
argument: description.argument,
flags: description.flags,
flag_descriptions,
expanded: ExpandedKeyCode::from(description.values),
})
}
}
pub mod util {
use super::*;
pub fn read_key_timeout(timeout: Duration) -> Result<Option<KeyCode>> {
use crate::{Connection, TtyMode};
let connection = Connection::open()?;
let (tty_mode, _) = TtyMode::enter_auto(&connection, None)?;
let key_reader = tty_mode.key_reader();
key_reader.read_key_timeout(timeout)
}
pub fn read_key_blocking() -> Result<KeyCode> {
use crate::{Connection, TtyMode};
let connection = Connection::open()?;
let (tty_mode, _) = TtyMode::enter_auto(&connection, None)?;
let key_reader = tty_mode.key_reader();
match key_reader.read_key(true)? {
Some(key_code) => Ok(key_code),
None => unreachable!("Blocking read should never return None"),
}
}
pub fn expand_key_code(key_code: KeyCode) -> Result<ExpandedKeyCode> {
use crate::{Connection, TtyMode};
let connection = Connection::open()?;
let (tty_mode, _) = TtyMode::enter_auto(&connection, None)?;
let key_reader = tty_mode.key_reader();
key_reader.expand_key_code(key_code)
}
pub fn describe_key_code(key_code: KeyCode) -> Result<DescribedKeyCode> {
use crate::{Connection, TtyMode};
let connection = Connection::open()?;
let (tty_mode, _) = TtyMode::enter_auto(&connection, None)?;
let key_reader = tty_mode.key_reader();
key_reader.describe_key_code(key_code)
}
}
pub mod constants {
use super::KeyCode;
pub const KEY_FLAGS_MASK: KeyCode = 0xFFFFFFFF00000000;
pub const KEY_FLAGS_SHIFT: u32 = brlapi_sys::BRLAPI_KEY_FLAGS_SHIFT;
pub const KEY_TYPE_MASK: KeyCode = 0x00000000E0000000;
pub const KEY_TYPE_CMD: KeyCode = 0x0000000020000000;
pub const KEY_TYPE_SYM: KeyCode = 0x0000000000000000;
pub const KEY_CODE_MASK: KeyCode = 0x000000001FFFFFFF;
pub const KEY_CMD_BLK_MASK: KeyCode = 0x1FFF0000;
pub const KEY_CMD_ARG_MASK: KeyCode = 0x0000FFFF;
pub const KEY_FLG_SHIFT: KeyCode = 0x0000000100000000;
pub const KEY_FLG_LOCK: KeyCode = 0x0000000200000000;
pub const KEY_FLG_CONTROL: KeyCode = 0x0000000400000000;
pub const KEY_FLG_MOD1: KeyCode = 0x0000000800000000;
pub const KEY_FLG_MOD2: KeyCode = 0x0000001000000000;
pub const KEY_FLG_MOD3: KeyCode = 0x0000002000000000;
pub const KEY_FLG_MOD4: KeyCode = 0x0000004000000000;
pub const KEY_FLG_MOD5: KeyCode = 0x0000008000000000;
pub const DRV_KEY_PRESS: KeyCode = 0x8000000000000000;
pub const KEY_SYM_BACKSPACE: KeyCode = 0x0000FF08;
pub const KEY_SYM_TAB: KeyCode = 0x0000FF09;
pub const KEY_SYM_LINEFEED: KeyCode = 0x0000FF0D;
pub const KEY_SYM_ESCAPE: KeyCode = 0x0000FF1B;
pub const KEY_SYM_HOME: KeyCode = 0x0000FF50;
pub const KEY_SYM_LEFT: KeyCode = 0x0000FF51;
pub const KEY_SYM_UP: KeyCode = 0x0000FF52;
pub const KEY_SYM_RIGHT: KeyCode = 0x0000FF53;
pub const KEY_SYM_DOWN: KeyCode = 0x0000FF54;
pub const KEY_SYM_PAGE_UP: KeyCode = 0x0000FF55;
pub const KEY_SYM_PAGE_DOWN: KeyCode = 0x0000FF56;
pub const KEY_SYM_END: KeyCode = 0x0000FF57;
pub const KEY_SYM_INSERT: KeyCode = 0x0000FF63;
pub const KEY_SYM_DELETE: KeyCode = 0x0000FFFF;
pub const KEY_SYM_UNICODE: KeyCode = 0x01000000;
const KEY_CMD_BASE: KeyCode = KEY_TYPE_CMD;
pub const KEY_CMD_NOOP: KeyCode = KEY_CMD_BASE;
pub const KEY_CMD_LNUP: KeyCode = KEY_CMD_BASE + 1;
pub const KEY_CMD_LNDN: KeyCode = KEY_CMD_BASE + 2;
pub const KEY_CMD_WINUP: KeyCode = KEY_CMD_BASE + 3;
pub const KEY_CMD_WINDN: KeyCode = KEY_CMD_BASE + 4;
pub const KEY_CMD_TOP: KeyCode = KEY_CMD_BASE + 9;
pub const KEY_CMD_BOT: KeyCode = KEY_CMD_BASE + 10;
pub const KEY_CMD_HOME: KeyCode = KEY_CMD_BASE + 29;
pub const KEY_CMD_BACK: KeyCode = KEY_CMD_BASE + 30;
pub const KEY_CMD_RETURN: KeyCode = KEY_CMD_BASE + 31;
pub const KEY_CMD_FREEZE: KeyCode = KEY_CMD_BASE + 32;
pub const KEY_CMD_CHRLT: KeyCode = KEY_CMD_BASE + 19;
pub const KEY_CMD_CHRRT: KeyCode = KEY_CMD_BASE + 20;
pub const KEY_CMD_FWINLT: KeyCode = KEY_CMD_BASE + 23;
pub const KEY_CMD_FWINRT: KeyCode = KEY_CMD_BASE + 24;
pub const KEY_CMD_LNBEG: KeyCode = KEY_CMD_BASE + 27;
pub const KEY_CMD_LNEND: KeyCode = KEY_CMD_BASE + 28;
pub const KEY_CMD_ROUTE: KeyCode = KEY_TYPE_CMD | (1 << 16);
pub const KEY_CMD_PASTE: KeyCode = KEY_TYPE_CMD | (2 << 16);
pub mod utils {
use super::*;
pub const fn is_braille_command(key: KeyCode) -> bool {
(key & KEY_TYPE_MASK) == KEY_TYPE_CMD
}
pub const fn is_keysym(key: KeyCode) -> bool {
(key & KEY_TYPE_MASK) == KEY_TYPE_SYM
}
pub const fn get_command(key: KeyCode) -> u32 {
((key & KEY_CMD_BLK_MASK) >> 16) as u32
}
pub const fn get_argument(key: KeyCode) -> u32 {
(key & KEY_CMD_ARG_MASK) as u32
}
pub const fn get_flags(key: KeyCode) -> u32 {
((key & KEY_FLAGS_MASK) >> KEY_FLAGS_SHIFT) as u32
}
pub const fn has_flag(key: KeyCode, flag: KeyCode) -> bool {
(key & flag) != 0
}
pub const fn is_unicode_keysym(key: KeyCode) -> bool {
is_keysym(key) && (key & KEY_SYM_UNICODE) != 0
}
pub const fn get_unicode_value(key: KeyCode) -> Option<u32> {
if is_unicode_keysym(key) {
Some((key & (KEY_SYM_UNICODE - 1)) as u32)
} else {
None
}
}
pub const fn make_command(command: u32, argument: u32) -> KeyCode {
KEY_TYPE_CMD | ((command as KeyCode) << 16) | (argument as KeyCode & KEY_CMD_ARG_MASK)
}
pub const fn make_route_command(cell: u32) -> KeyCode {
make_command((KEY_CMD_ROUTE >> 16) as u32, cell)
}
pub const fn add_flags(key: KeyCode, flags: KeyCode) -> KeyCode {
key | (flags & KEY_FLAGS_MASK)
}
pub fn format_key(key: KeyCode) -> String {
format!("{:016X}", key)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_key_range_creation() {
let range = KeyRange::new(0x100, 0x200);
assert_eq!(range.first, 0x100);
assert_eq!(range.last, 0x200);
let single = KeyRange::single(0x100);
assert_eq!(single.first, 0x100);
assert_eq!(single.last, 0x100);
}
#[test]
fn test_range_type_conversion() {
assert_eq!(
brlapi_sys::brlapi_rangeType_t::Type::from(RangeType::All),
brlapi_sys::brlapi_rangeType_t::brlapi_rangeType_all
);
assert_eq!(
brlapi_sys::brlapi_rangeType_t::Type::from(RangeType::Type),
brlapi_sys::brlapi_rangeType_t::brlapi_rangeType_type
);
}
#[test]
fn test_key_constants() {
use constants::*;
assert!(KEY_SYM_TAB > 0);
assert!(KEY_SYM_ESCAPE > 0);
assert!(KEY_CMD_HOME > 0);
assert!(constants::utils::is_keysym(KEY_SYM_TAB));
assert!(constants::utils::is_braille_command(KEY_CMD_HOME));
let route_cmd = constants::utils::make_route_command(5);
assert!(constants::utils::is_braille_command(route_cmd));
assert_eq!(constants::utils::get_argument(route_cmd), 5);
}
#[test]
fn test_key_reader_creation() {
use crate::{Connection, TtyMode};
match Connection::open() {
Ok(connection) => {
println!("BrlAPI connection available for KeyReader test");
match TtyMode::enter_auto(&connection, None) {
Ok((tty_mode, tty_num)) => {
println!("TTY mode entered ({}), creating KeyReader", tty_num);
let key_reader = tty_mode.key_reader();
let _tty_ref = key_reader.tty_mode();
let _conn_ref = key_reader.connection();
println!("KeyReader created successfully through TtyMode");
}
Err(e) => {
println!(
"TTY mode failed: {} (expected if no daemon or permissions)",
e
);
}
}
}
Err(e) => {
println!("BrlAPI connection failed: {} (expected if no daemon)", e);
}
}
assert!(true, "KeyReader API structure test completed");
}
#[test]
fn test_key_expansion_and_description() {
use crate::{Connection, TtyMode};
use constants::*;
match Connection::open() {
Ok(connection) => {
println!("BrlAPI connection available for key expansion test");
match TtyMode::enter_auto(&connection, None) {
Ok((tty_mode, tty_num)) => {
println!("TTY mode entered ({}), testing key expansion", tty_num);
let key_reader = tty_mode.key_reader();
match key_reader.expand_key_code(KEY_CMD_HOME) {
Ok(expansion) => {
println!("Key expansion succeeded for KEY_CMD_HOME");
println!(
" Type: {}, Command: {}, Argument: {}, Flags: {}",
expansion.key_type,
expansion.command,
expansion.argument,
expansion.flags
);
assert!(expansion.command > 0, "Command should be non-zero");
assert_eq!(
expansion.argument, 0,
"Simple commands should have no argument"
);
assert_eq!(
expansion.flags, 0,
"Simple commands should have no flags"
);
}
Err(e) => {
println!("Key expansion failed: {} (this may be expected)", e);
}
}
let key_with_flags = KEY_CMD_HOME | KEY_FLG_CONTROL;
match key_reader.expand_key_code(key_with_flags) {
Ok(expansion) => {
println!("Key expansion with flags succeeded");
println!(" Flags: {:#x}", expansion.flags);
assert!(expansion.flags > 0, "Should have flags set");
}
Err(e) => {
println!(
"Key expansion with flags failed: {} (this may be expected)",
e
);
}
}
match key_reader.describe_key_code(KEY_CMD_HOME) {
Ok(description) => {
println!("[SUCCESS] Key description succeeded for KEY_CMD_HOME");
if let Some(cmd) = &description.command {
println!(" Command description: {}", cmd);
}
if let Some(key_type) = &description.key_type {
println!(" Type description: {}", key_type);
}
assert!(
description.command.is_some() || description.key_type.is_some(),
"Description should contain some information"
);
}
Err(e) => {
println!("Key description failed: {} (this may be expected)", e);
}
}
let key_with_multiple_flags =
KEY_CMD_HOME | KEY_FLG_CONTROL | KEY_FLG_SHIFT;
match key_reader.describe_key_code(key_with_multiple_flags) {
Ok(description) => {
println!("[SUCCESS] Key description with flags succeeded");
println!(
" Flag descriptions count: {}",
description.flag_descriptions.len()
);
for (i, flag_desc) in
description.flag_descriptions.iter().enumerate()
{
println!(" Flag {}: {}", i, flag_desc);
}
assert!(
!description.flag_descriptions.is_empty(),
"Should have flag descriptions"
);
}
Err(e) => {
println!(
"Key description with flags failed: {} (this may be expected)",
e
);
}
}
println!("[SUCCESS] Key expansion and description API test completed");
}
Err(e) => {
println!(
"TTY mode failed: {} (expected if no daemon or permissions)",
e
);
}
}
}
Err(e) => {
println!("BrlAPI connection failed: {} (expected if no daemon)", e);
}
}
assert!(true, "Key expansion and description API test completed");
}
#[test]
fn test_expanded_key_code_from_conversion() {
use brlapi_sys::brlapi_expandedKeyCode_t;
let raw_expansion = brlapi_expandedKeyCode_t {
type_: 1,
command: 2,
argument: 3,
flags: 4,
};
let expansion = ExpandedKeyCode::from(raw_expansion);
assert_eq!(expansion.key_type, 1);
assert_eq!(expansion.command, 2);
assert_eq!(expansion.argument, 3);
assert_eq!(expansion.flags, 4);
}
#[test]
fn test_key_utility_functions() {
use constants::*;
assert!(KEY_CMD_HOME > 0);
assert!(KEY_FLG_CONTROL > 0);
assert!(KEY_SYM_TAB > 0);
assert!(constants::utils::is_braille_command(KEY_CMD_HOME));
assert!(constants::utils::is_keysym(KEY_SYM_TAB));
assert!(!constants::utils::is_braille_command(KEY_SYM_TAB));
assert!(!constants::utils::is_keysym(KEY_CMD_HOME));
let key_with_flag = constants::utils::add_flags(KEY_CMD_HOME, KEY_FLG_CONTROL);
assert!(constants::utils::has_flag(key_with_flag, KEY_FLG_CONTROL));
assert!(!constants::utils::has_flag(KEY_CMD_HOME, KEY_FLG_CONTROL));
let route_cmd = constants::utils::make_route_command(5);
assert!(constants::utils::is_braille_command(route_cmd));
assert_eq!(constants::utils::get_argument(route_cmd), 5);
let formatted = constants::utils::format_key(KEY_CMD_HOME);
assert!(formatted.len() == 16); assert!(
formatted
.chars()
.all(|c| c.is_ascii_hexdigit() || c.is_ascii_uppercase())
);
}
}