use super::platform::{LinuxBackend, LoggingBackend, MacOSBackend, NullBackend, WindowsBackend};
use super::types::{BackendType, ScreenReader, ScreenReaderConfig};
use crate::utils::accessibility::Priority;
use crate::utils::lock::write_or_recover;
use std::sync::RwLock;
pub struct ScreenReaderBackend {
inner: Box<dyn ScreenReader>,
config: ScreenReaderConfig,
last_announcement: RwLock<Option<std::time::Instant>>,
}
impl ScreenReaderBackend {
pub fn new(backend_type: BackendType) -> Self {
let resolved_type = if backend_type == BackendType::Auto {
BackendType::detect()
} else {
backend_type
};
let inner: Box<dyn ScreenReader> = match resolved_type {
BackendType::MacOS => Box::new(MacOSBackend::new()),
BackendType::Windows => Box::new(WindowsBackend::new()),
BackendType::Linux => Box::new(LinuxBackend::new()),
BackendType::Logging => Box::new(LoggingBackend::new()),
BackendType::None | BackendType::Auto => Box::new(NullBackend),
};
Self {
inner,
config: ScreenReaderConfig {
backend_type: resolved_type,
..Default::default()
},
last_announcement: RwLock::new(None),
}
}
pub fn with_config(config: ScreenReaderConfig) -> Self {
let mut backend = Self::new(config.backend_type);
backend.config = config;
backend
}
pub fn backend_type(&self) -> BackendType {
self.config.backend_type
}
pub fn announce(&self, message: impl AsRef<str>, priority: Priority) {
let message = message.as_ref();
if self.config.debounce_ms > 0 {
let now = std::time::Instant::now();
let mut last = write_or_recover(&self.last_announcement);
if let Some(last_time) = *last {
let elapsed = now.duration_since(last_time).as_millis() as u64;
if elapsed < self.config.debounce_ms && priority == Priority::Polite {
return;
}
}
*last = Some(now);
}
if self.config.log_announcements {
let priority_str = match priority {
Priority::Polite => "polite",
Priority::Assertive => "assertive",
};
eprintln!("[a11y:{}] {}", priority_str, message);
}
self.inner.announce(message, priority);
}
pub fn is_available(&self) -> bool {
self.inner.is_available()
}
pub fn active_screen_reader(&self) -> Option<String> {
self.inner.active_screen_reader()
}
pub fn announce_focus(&self, label: &str, role: &str) {
if self.config.announce_roles {
self.inner.announce_focus(label, role);
} else {
self.inner.announce(label, Priority::Polite);
}
}
pub fn stop(&self) {
self.inner.stop();
}
}