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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
// =============================================================================
// ACCESSIBILITY PREFERENCES -- System accessibility settings
// =============================================================================
//
// Components and the renderer query these to adapt behavior:
// - Reduce Motion: disable non-essential animations
// - Reduce Transparency: replace glass materials with opaque surfaces
// - Increase Contrast: make borders visible, minimum alpha 0.5
thread_local! {
/// Thread-local accessibility preferences.
/// Defaults to no restrictions (all false).
static ACCESSIBILITY_PREFS: std::cell::RefCell<AccessibilityPreferences> =
std::cell::RefCell::new(AccessibilityPreferences::default());
}
/// System accessibility preferences that components and the renderer must honor.
///
/// These map to macOS System Settings > Accessibility:
/// - `reduce_motion`: Disables non-essential animations (spring, bounce, etc.)
/// - `reduce_transparency`: Replaces glass/transparent materials with opaque surfaces
/// - `increase_contrast`: Makes all borders visible, minimum alpha 0.5 for all elements
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct AccessibilityPreferences {
/// User prefers reduced motion. Animations should be instant or very short.
pub reduce_motion: bool,
/// User prefers reduced transparency. Glass materials should be opaque.
pub reduce_transparency: bool,
/// User prefers increased contrast. Borders must be visible, min alpha 0.5.
pub increase_contrast: bool,
}
impl AccessibilityPreferences {
/// Detect system accessibility preferences (macOS).
///
/// On non-macOS platforms, returns defaults (all false).
/// In a production implementation, this would query the OS APIs.
pub fn detect_from_system() -> Self {
#[cfg(target_os = "macos")]
{
// Try to read macOS accessibility preferences via defaults command
let reduce_motion = std::process::Command::new("defaults")
.args(["read", "-g", "com.apple.universalaccess", "reduceMotion"])
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim() == "1")
.unwrap_or(false);
let reduce_transparency = std::process::Command::new("defaults")
.args([
"read",
"-g",
"com.apple.universalaccess",
"reduceTransparency",
])
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim() == "1")
.unwrap_or(false);
let increase_contrast = std::process::Command::new("defaults")
.args([
"read",
"-g",
"com.apple.universalaccess",
"increaseContrast",
])
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim() == "1")
.unwrap_or(false);
Self {
reduce_motion,
reduce_transparency,
increase_contrast,
}
}
#[cfg(target_os = "linux")]
{
// Reduced motion: check GTK_A11Y env var or GNOME gsettings
let reduce_motion = std::env::var("GTK_A11Y")
.map(|v| v.to_lowercase().contains("reduce-motion"))
.unwrap_or(false)
|| {
// Try gsettings for GNOME desktop animation preference
std::process::Command::new("gsettings")
.args(["get", "org.gnome.desktop.interface", "enable-animations"])
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim() == "'false'" || s.trim() == "false")
.unwrap_or(false)
};
// Reduced transparency is not widely supported on Linux desktops
let reduce_transparency = false;
// Increased contrast: check GTK_THEME for high-contrast variants
let increase_contrast = std::env::var("GTK_THEME")
.map(|v| v.to_lowercase().contains("highcontrast"))
.unwrap_or(false);
Self {
reduce_motion,
reduce_transparency,
increase_contrast,
}
}
#[cfg(target_os = "windows")]
{
use std::process::Command;
// Helper: run `reg query` and return the value string if found
fn reg_query(key: &str, value_name: &str) -> Option<String> {
Command::new("reg")
.args(["query", key, "/v", value_name])
.output()
.ok()
.and_then(|o| {
if o.status.success() {
String::from_utf8(o.stdout).ok()
} else {
None
}
})
.and_then(|s| {
// Output format: " ValueName REG_SZ <value>"
// or REG_DWORD lines; parse the last token on the last non-empty line
s.lines()
.last()?
.split_whitespace()
.last()
.map(String::from)
})
}
// Reduced motion: EffectsAnimationEfficiency = 1 means reduced
let reduce_motion = reg_query(
"HKCU\\Control Panel\\Accessibility\\EffectsAnimationEfficiency",
"EffectsAnimationEfficiency",
)
.map(|v| v == "1")
.unwrap_or(false);
// Reduced transparency: EnableTransparency = 0 means reduced
let reduce_transparency = reg_query(
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
"EnableTransparency",
)
.map(|v| v == "0")
.unwrap_or(false);
// Increased contrast: HighContrast = 1 means enabled
let increase_contrast = reg_query(
"HKCU\\Control Panel\\Accessibility\\HighContrast",
"HighContrast",
)
.map(|v| v == "1")
.unwrap_or(false);
Self {
reduce_motion,
reduce_transparency,
increase_contrast,
}
}
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
{
Self::default()
}
}
/// Apply a minimum alpha constraint for increase-contrast mode.
pub fn min_alpha(&self, requested: f32) -> f32 {
if self.increase_contrast {
requested.max(0.5)
} else {
requested
}
}
/// Returns true if glass effects should be replaced with opaque surfaces.
pub fn should_disable_glass(&self) -> bool {
self.reduce_transparency
}
/// Returns true if animations should be instant.
pub fn should_reduce_motion(&self) -> bool {
self.reduce_motion
}
/// Returns true if borders should be made visible.
pub fn should_increase_contrast(&self) -> bool {
self.increase_contrast
}
}
/// Get the current accessibility preferences for this thread.
pub fn accessibility_preferences() -> AccessibilityPreferences {
ACCESSIBILITY_PREFS.with(|p| *p.borrow())
}
/// Set the accessibility preferences for this thread.
///
/// The native renderer should call this on startup and when system
/// preferences change (via `detect_from_system()`).
pub fn set_accessibility_preferences(prefs: AccessibilityPreferences) {
ACCESSIBILITY_PREFS.with(|p| {
*p.borrow_mut() = prefs;
});
}