1use std::sync::atomic::{AtomicU8, Ordering};
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
26pub enum TargetPlatform {
27 Android,
29 Fuchsia,
31 Ios,
33 Linux,
35 MacOs,
37 Windows,
39}
40
41impl TargetPlatform {
42 pub const ALL: [TargetPlatform; 6] = [
44 TargetPlatform::Android,
45 TargetPlatform::Fuchsia,
46 TargetPlatform::Ios,
47 TargetPlatform::Linux,
48 TargetPlatform::MacOs,
49 TargetPlatform::Windows,
50 ];
51
52 pub const fn of_build() -> Self {
56 if cfg!(target_os = "ios") {
57 TargetPlatform::Ios
58 } else if cfg!(target_os = "macos") {
59 TargetPlatform::MacOs
60 } else if cfg!(target_os = "windows") {
61 TargetPlatform::Windows
62 } else if cfg!(target_os = "fuchsia") {
63 TargetPlatform::Fuchsia
64 } else if cfg!(any(
65 target_os = "linux",
66 target_os = "freebsd",
67 target_os = "openbsd",
68 target_os = "netbsd",
69 target_os = "dragonfly"
70 )) {
71 TargetPlatform::Linux
72 } else {
73 TargetPlatform::Android
74 }
75 }
76
77 pub fn from_browser(platform: &str, user_agent: &str, max_touch_points: i32) -> Self {
84 let lower = platform.to_ascii_lowercase();
85 if platform.starts_with("Mac") {
86 if max_touch_points > 2 {
87 TargetPlatform::Ios
88 } else {
89 TargetPlatform::MacOs
90 }
91 } else if lower.contains("iphone") || lower.contains("ipad") || lower.contains("ipod") {
92 TargetPlatform::Ios
93 } else if user_agent.contains("Android") {
94 TargetPlatform::Android
95 } else if platform.starts_with("Linux") {
96 TargetPlatform::Linux
97 } else if platform.starts_with("Win") {
98 TargetPlatform::Windows
99 } else {
100 TargetPlatform::Android
101 }
102 }
103
104 pub const fn is_apple(self) -> bool {
106 matches!(self, TargetPlatform::Ios | TargetPlatform::MacOs)
107 }
108
109 pub const fn is_mobile(self) -> bool {
111 matches!(
112 self,
113 TargetPlatform::Android | TargetPlatform::Fuchsia | TargetPlatform::Ios
114 )
115 }
116
117 pub const fn is_desktop(self) -> bool {
119 !self.is_mobile()
120 }
121
122 pub const fn name(self) -> &'static str {
124 match self {
125 TargetPlatform::Android => "Android",
126 TargetPlatform::Fuchsia => "Fuchsia",
127 TargetPlatform::Ios => "iOS",
128 TargetPlatform::Linux => "Linux",
129 TargetPlatform::MacOs => "macOS",
130 TargetPlatform::Windows => "Windows",
131 }
132 }
133
134 const fn code(self) -> u8 {
135 match self {
136 TargetPlatform::Android => 1,
137 TargetPlatform::Fuchsia => 2,
138 TargetPlatform::Ios => 3,
139 TargetPlatform::Linux => 4,
140 TargetPlatform::MacOs => 5,
141 TargetPlatform::Windows => 6,
142 }
143 }
144
145 const fn from_code(code: u8) -> Option<Self> {
146 match code {
147 1 => Some(TargetPlatform::Android),
148 2 => Some(TargetPlatform::Fuchsia),
149 3 => Some(TargetPlatform::Ios),
150 4 => Some(TargetPlatform::Linux),
151 5 => Some(TargetPlatform::MacOs),
152 6 => Some(TargetPlatform::Windows),
153 _ => None,
154 }
155 }
156}
157
158impl std::fmt::Display for TargetPlatform {
159 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160 f.write_str(self.name())
161 }
162}
163
164pub const IS_WEB: bool = cfg!(target_arch = "wasm32");
166
167static OVERRIDE: AtomicU8 = AtomicU8::new(0);
169static DETECTED: AtomicU8 = AtomicU8::new(0);
172
173pub fn default_target_platform() -> TargetPlatform {
180 debug_default_target_platform_override()
181 .or_else(|| TargetPlatform::from_code(DETECTED.load(Ordering::Relaxed)))
182 .unwrap_or(TargetPlatform::of_build())
183}
184
185pub fn debug_default_target_platform_override() -> Option<TargetPlatform> {
187 TargetPlatform::from_code(OVERRIDE.load(Ordering::Relaxed))
188}
189
190pub fn set_debug_default_target_platform_override(platform: Option<TargetPlatform>) {
200 if !cfg!(debug_assertions) {
201 panic!(
202 "the default target platform can only be overridden in a debug build; \
203 set `Theme::platform` instead"
204 );
205 }
206 OVERRIDE.store(platform.map_or(0, TargetPlatform::code), Ordering::Relaxed);
207}
208
209#[doc(hidden)]
212pub fn __set_detected_target_platform(platform: TargetPlatform) {
213 DETECTED.store(platform.code(), Ordering::Relaxed);
214}
215
216pub struct Platform;
219
220impl Platform {
221 pub const fn operating_system() -> &'static str {
225 std::env::consts::OS
226 }
227
228 pub const fn is_android() -> bool {
230 cfg!(target_os = "android")
231 }
232
233 pub const fn is_ios() -> bool {
235 cfg!(target_os = "ios")
236 }
237
238 pub const fn is_linux() -> bool {
240 cfg!(target_os = "linux")
241 }
242
243 pub const fn is_macos() -> bool {
245 cfg!(target_os = "macos")
246 }
247
248 pub const fn is_windows() -> bool {
250 cfg!(target_os = "windows")
251 }
252
253 pub const fn is_fuchsia() -> bool {
255 cfg!(target_os = "fuchsia")
256 }
257
258 pub const fn is_web() -> bool {
260 IS_WEB
261 }
262
263 pub fn number_of_processors() -> usize {
265 std::thread::available_parallelism().map_or(1, |n| n.get())
266 }
267
268 pub const fn path_separator() -> char {
270 std::path::MAIN_SEPARATOR
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 #[test]
280 fn the_default_is_the_platform_built_for() {
281 let expected = if cfg!(target_os = "windows") {
282 TargetPlatform::Windows
283 } else if cfg!(target_os = "macos") {
284 TargetPlatform::MacOs
285 } else if cfg!(target_os = "linux") {
286 TargetPlatform::Linux
287 } else {
288 TargetPlatform::of_build()
289 };
290 assert_eq!(TargetPlatform::of_build(), expected);
291 assert_eq!(Platform::operating_system(), std::env::consts::OS);
292 assert_eq!(Platform::is_web(), IS_WEB);
293 }
294
295 #[test]
298 fn the_debug_override_wins_until_it_is_cleared() {
299 let native = default_target_platform();
300 for platform in TargetPlatform::ALL {
301 set_debug_default_target_platform_override(Some(platform));
302 assert_eq!(default_target_platform(), platform);
303 assert_eq!(debug_default_target_platform_override(), Some(platform));
304 }
305 set_debug_default_target_platform_override(None);
306 assert_eq!(default_target_platform(), native);
307 assert_eq!(debug_default_target_platform_override(), None);
308 }
309
310 #[test]
312 fn a_browser_reports_the_system_it_runs_on() {
313 let cases = [
314 (
316 "Win32",
317 "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
318 0,
319 TargetPlatform::Windows,
320 ),
321 (
323 "MacIntel",
324 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
325 0,
326 TargetPlatform::MacOs,
327 ),
328 (
330 "MacIntel",
331 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
332 5,
333 TargetPlatform::Ios,
334 ),
335 (
337 "iPhone",
338 "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)",
339 5,
340 TargetPlatform::Ios,
341 ),
342 (
344 "Linux armv8l",
345 "Mozilla/5.0 (Linux; Android 14; Pixel 8)",
346 5,
347 TargetPlatform::Android,
348 ),
349 (
351 "Linux x86_64",
352 "Mozilla/5.0 (X11; Linux x86_64; rv:120.0)",
353 0,
354 TargetPlatform::Linux,
355 ),
356 ("", "", 0, TargetPlatform::Android),
358 ];
359 for (platform, agent, touch, expected) in cases {
360 assert_eq!(
361 TargetPlatform::from_browser(platform, agent, touch),
362 expected,
363 "{platform:?} / {agent:?} / {touch}"
364 );
365 }
366 }
367
368 #[test]
371 fn platforms_fall_into_families() {
372 let apple: Vec<_> = TargetPlatform::ALL
373 .into_iter()
374 .filter(|p| p.is_apple())
375 .collect();
376 assert_eq!(apple, [TargetPlatform::Ios, TargetPlatform::MacOs]);
377 let mobile: Vec<_> = TargetPlatform::ALL
378 .into_iter()
379 .filter(|p| p.is_mobile())
380 .collect();
381 assert_eq!(
382 mobile,
383 [
384 TargetPlatform::Android,
385 TargetPlatform::Fuchsia,
386 TargetPlatform::Ios
387 ]
388 );
389 for platform in TargetPlatform::ALL {
390 assert_ne!(platform.is_mobile(), platform.is_desktop());
391 assert_eq!(TargetPlatform::from_code(platform.code()), Some(platform));
392 }
393 assert_eq!(TargetPlatform::from_code(0), None);
394 assert_eq!(TargetPlatform::Ios.to_string(), "iOS");
395 assert_eq!(TargetPlatform::MacOs.name(), "macOS");
396 }
397}