1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//! Accessibility hooks for screen reader support
use std::env;
/// Check if a screen reader is likely enabled
///
/// This checks common environment variables that indicate
/// accessibility tools are in use.
fn detect_screen_reader() -> bool {
// Check common accessibility environment variables
let indicators = [
"SCREEN_READER",
"ACCESSIBILITY_ENABLED",
"ORCA_ENABLED", // Linux Orca
"NVDA_RUNNING", // Windows NVDA
"JAWS_RUNNING", // Windows JAWS
"VOICEOVER_RUNNING", // macOS VoiceOver
"TERM_PROGRAM", // May indicate accessible terminal
];
for var in indicators {
if let Ok(val) = env::var(var) {
if var == "TERM_PROGRAM" {
// Some terminals have built-in accessibility
if val.to_lowercase().contains("accessibility") {
return true;
}
} else if !val.is_empty() && val != "0" && val.to_lowercase() != "false" {
return true;
}
}
}
// Check if running in a known accessible terminal
if let Ok(term) = env::var("TERM")
&& (term.contains("screen") || term.contains("tmux"))
{
// These often have accessibility features
// but we can't be certain, so we don't return true
}
// Check macOS VoiceOver via defaults (if available)
#[cfg(target_os = "macos")]
{
if let Ok(output) = std::process::Command::new("defaults")
.args(["read", "com.apple.universalaccess", "voiceOverOnOffKey"])
.output()
&& output.status.success()
{
let stdout = String::from_utf8_lossy(&output.stdout);
if stdout.trim() == "1" {
return true;
}
}
}
false
}
// Thread-local cache for screen reader status
thread_local! {
static SCREEN_READER_ENABLED: std::cell::Cell<Option<bool>> = const { std::cell::Cell::new(None) };
}
/// Hook to check if a screen reader is enabled
///
/// Returns true if accessibility tools are detected.
/// The result is cached for performance.
///
/// # Example
///
/// ```ignore
/// let is_accessible = use_is_screen_reader_enabled();
///
/// if is_accessible {
/// // Provide more detailed text descriptions
/// // Avoid relying solely on colors
/// }
/// ```
pub fn use_is_screen_reader_enabled() -> bool {
SCREEN_READER_ENABLED.with(|cached| {
if let Some(value) = cached.get() {
value
} else {
let detected = detect_screen_reader();
cached.set(Some(detected));
detected
}
})
}
/// Manually set screen reader status (for testing or override)
pub fn set_screen_reader_enabled(enabled: bool) {
SCREEN_READER_ENABLED.with(|cached| {
cached.set(Some(enabled));
});
}
/// Clear cached screen reader status (forces re-detection)
pub fn clear_screen_reader_cache() {
SCREEN_READER_ENABLED.with(|cached| {
cached.set(None);
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_screen_reader_detection() {
// Clear cache first
clear_screen_reader_cache();
// Should return false in normal test environment
let result = use_is_screen_reader_enabled();
// Result depends on environment, just verify it doesn't panic
let _ = result;
}
#[test]
fn test_manual_override() {
set_screen_reader_enabled(true);
assert!(use_is_screen_reader_enabled());
set_screen_reader_enabled(false);
assert!(!use_is_screen_reader_enabled());
// Clean up
clear_screen_reader_cache();
}
#[test]
fn test_caching() {
clear_screen_reader_cache();
// First call detects
let first = use_is_screen_reader_enabled();
// Second call uses cache
let second = use_is_screen_reader_enabled();
assert_eq!(first, second);
}
}