702ec7efac
Phase S1 (Critical Correctness): - sem_open/sem_close: global refcounting via BTreeMap + AtomicUsize - sem_close: decrements refcount, munmaps only at zero - sem_open: reuses existing mapping, O_EXCL returns EEXIST - sem_unlink: marks entry for removal before shm_unlink - va_list parsing: reads mode_t and value from stack after oflag - All 11 sem_* functions verified in libc.so T Phase S2-S4 (Designed, documented): - eventfd() function, signalfd read path, EINTR handling - name canonicalization, cancellation safety - Full plan in local/docs/RELIBC-AGAINST-GLIBC-ASSESSMENT.md Reference: glibc 2.41 cloned to local/reference/glibc/ Boot verified: greeter ready on VT 3 with refcounted semaphores
295 lines
10 KiB
Rust
295 lines
10 KiB
Rust
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<usize, Handle>,
|
|
config: AccessibilityConfig,
|
|
sticky_modifiers: HashMap<u16, StickyKeyState>,
|
|
key_history: HashMap<u16, KeyRecord>,
|
|
}
|
|
|
|
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<Option<usize>> {
|
|
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<Option<usize>> {
|
|
let (kind, offset) = {
|
|
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
|
|
(handle.kind.clone(), handle.offset)
|
|
};
|
|
|
|
let content: Vec<u8> = 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<Option<usize>> {
|
|
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::<u16>().ok())
|
|
else {
|
|
continue;
|
|
};
|
|
let Some(pressed) = fields.next().and_then(|field| field.parse::<bool>().ok())
|
|
else {
|
|
continue;
|
|
};
|
|
let now_ms = fields
|
|
.next()
|
|
.and_then(|field| field.parse::<u64>().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<Option<usize>> {
|
|
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<Option<usize>> {
|
|
self.handles.remove(&id);
|
|
Ok(Some(0))
|
|
}
|
|
}
|