use std::collections::HashMap; use syscall::data::Stat; use syscall::error::{Error, Result, EBADF, EINVAL, ENOENT}; use syscall::flag::{MODE_DIR, MODE_FILE}; #[derive(Clone, Copy, Debug, PartialEq)] pub(crate) enum StickyKeyState { Off, Latched, Locked, } #[derive(Clone, Debug)] struct AccessibilityConfig { sticky_keys: bool, slow_keys: bool, slow_key_delay_ms: u64, bounce_keys: bool, bounce_key_delay_ms: u64, repeat_keys: bool, repeat_delay_ms: u64, repeat_interval_ms: u64, } impl Default for AccessibilityConfig { fn default() -> Self { AccessibilityConfig { sticky_keys: false, slow_keys: false, slow_key_delay_ms: 300, bounce_keys: false, bounce_key_delay_ms: 300, repeat_keys: true, repeat_delay_ms: 500, repeat_interval_ms: 50, } } } #[derive(Clone, Debug)] struct KeyRecord { pressed: bool, timestamp_ms: u64, } struct Handle { kind: HandleKind, offset: usize, } #[derive(Clone, Debug)] enum HandleKind { Root, Config, Status, StickyKeys, } pub struct AccessibilityScheme { next_id: usize, handles: HashMap, config: AccessibilityConfig, sticky_modifiers: HashMap, key_history: HashMap, } impl AccessibilityScheme { pub fn new() -> Self { AccessibilityScheme { next_id: 1, handles: HashMap::new(), config: AccessibilityConfig::default(), sticky_modifiers: HashMap::new(), key_history: HashMap::new(), } } pub fn filter_key(&mut self, code: u16, pressed: bool, now_ms: u64) -> Option<(u16, bool)> { if let Some(record) = self.key_history.get(&code) { if self.config.bounce_keys && pressed && !record.pressed { let elapsed = now_ms.saturating_sub(record.timestamp_ms); if elapsed < self.config.bounce_key_delay_ms { return None; } } if self.config.slow_keys && pressed && !record.pressed { let elapsed = now_ms.saturating_sub(record.timestamp_ms); if elapsed < self.config.slow_key_delay_ms { return None; } } } self.key_history.insert( code, KeyRecord { pressed, timestamp_ms: now_ms, }, ); if self.config.sticky_keys { let is_modifier = matches!( code, 0x1D | 0x2A | 0x36 | 0x38 | 0x5B | 0x5C | 0x5D | 0x61 | 0x64 ); if is_modifier && pressed { let state = self .sticky_modifiers .entry(code) .or_insert(StickyKeyState::Off); match *state { StickyKeyState::Off => { *state = StickyKeyState::Latched; return None; } StickyKeyState::Latched => { *state = StickyKeyState::Locked; return None; } StickyKeyState::Locked => { *state = StickyKeyState::Off; return None; } } } } Some((code, pressed)) } pub fn active_sticky_modifiers(&self) -> Vec<(u16, StickyKeyState)> { self.sticky_modifiers .iter() .filter(|(_, state)| **state != StickyKeyState::Off) .map(|(&code, &state)| (code, state)) .collect() } } impl redox_scheme::SchemeBlockMut for AccessibilityScheme { fn open(&mut self, path: &str, _flags: usize, _uid: u32, _gid: u32) -> Result> { let cleaned = path.trim_matches('/'); let kind = match cleaned { "" => HandleKind::Root, "config" => HandleKind::Config, "status" => HandleKind::Status, "sticky-keys" => HandleKind::StickyKeys, _ => return Err(Error::new(ENOENT)), }; let id = self.next_id; self.next_id += 1; self.handles.insert(id, Handle { kind, offset: 0 }); Ok(Some(id)) } fn read(&mut self, id: usize, buf: &mut [u8]) -> Result> { let (kind, offset) = { let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; (handle.kind.clone(), handle.offset) }; let content: Vec = match &kind { HandleKind::Root => "config\nstatus\nsticky-keys\n".as_bytes().to_vec(), HandleKind::Config => { format!( "sticky_keys={}\nslow_keys={}\nslow_key_delay_ms={}\nbounce_keys={}\nbounce_key_delay_ms={}\nrepeat_keys={}\nrepeat_delay_ms={}\nrepeat_interval_ms={}\n", self.config.sticky_keys, self.config.slow_keys, self.config.slow_key_delay_ms, self.config.bounce_keys, self.config.bounce_key_delay_ms, self.config.repeat_keys, self.config.repeat_delay_ms, self.config.repeat_interval_ms, ) .into_bytes() } HandleKind::Status => { let sticky_count = self.active_sticky_modifiers().len(); format!( "sticky_keys_active={}\nslow_keys_active={}\nbounce_keys_active={}\nactive_sticky_count={}\n", self.config.sticky_keys, self.config.slow_keys, self.config.bounce_keys, sticky_count, ) .into_bytes() } HandleKind::StickyKeys => { let mut out = String::new(); for (code, state) in self.active_sticky_modifiers() { let label = match code { 0x1D | 0x61 | 0x64 => "ctrl", 0x2A | 0x36 => "shift", 0x38 => "alt", 0x5B | 0x5C => "meta", _ => "unknown", }; let state_str = match state { StickyKeyState::Latched => "latched", StickyKeyState::Locked => "locked", StickyKeyState::Off => "off", }; out.push_str(&format!("{} {}\n", label, state_str)); } out.into_bytes() } }; let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?; if offset >= content.len() { return Ok(Some(0)); } let remaining = &content[offset..]; let to_copy = remaining.len().min(buf.len()); buf[..to_copy].copy_from_slice(&remaining[..to_copy]); handle.offset += to_copy; Ok(Some(to_copy)) } fn write(&mut self, id: usize, buf: &[u8]) -> Result> { let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; match &handle.kind { HandleKind::Config => { let input = String::from_utf8_lossy(buf); for line in input.lines() { let line = line.trim(); if let Some((key, value)) = line.split_once('=') { match key.trim() { "sticky_keys" => self.config.sticky_keys = value.trim() == "true", "slow_keys" => self.config.slow_keys = value.trim() == "true", "slow_key_delay_ms" => { if let Ok(v) = value.trim().parse() { self.config.slow_key_delay_ms = v; } } "bounce_keys" => self.config.bounce_keys = value.trim() == "true", "bounce_key_delay_ms" => { if let Ok(v) = value.trim().parse() { self.config.bounce_key_delay_ms = v; } } "repeat_keys" => self.config.repeat_keys = value.trim() == "true", _ => {} } } } Ok(Some(buf.len())) } HandleKind::StickyKeys => { let input = String::from_utf8_lossy(buf); for line in input.lines() { let mut fields = line.split_whitespace(); let Some("key") = fields.next() else { continue; }; let Some(code) = fields.next().and_then(|field| field.parse::().ok()) else { continue; }; let Some(pressed) = fields.next().and_then(|field| field.parse::().ok()) else { continue; }; let now_ms = fields .next() .and_then(|field| field.parse::().ok()) .unwrap_or(0); let _ = self.filter_key(code, pressed, now_ms); } Ok(Some(buf.len())) } _ => Err(Error::new(EINVAL)), } } fn fstat(&mut self, id: usize, stat: &mut Stat) -> Result> { let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; match &handle.kind { HandleKind::Root => stat.st_mode = MODE_DIR | 0o555, _ => stat.st_mode = MODE_FILE | 0o644, } Ok(Some(0)) } fn close(&mut self, id: usize) -> Result> { self.handles.remove(&id); Ok(Some(0)) } }