cranpose_services/
system_bars.rs1use std::sync::{Mutex, PoisonError, atomic::Ordering};
5
6use cranpose_core::DisposableEffect;
7use portable_atomic::AtomicU64;
8
9use crate::host_controller;
10
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
14pub enum SystemBarIcons {
15 #[default]
17 FollowSystem,
18 Dark,
20 Light,
22}
23
24static REQUESTS: Mutex<Vec<(u64, SystemBarIcons)>> = Mutex::new(Vec::new());
26static NEXT_REQUEST: AtomicU64 = AtomicU64::new(0);
27
28pub fn system_bar_icons() -> SystemBarIcons {
31 REQUESTS
32 .lock()
33 .unwrap_or_else(PoisonError::into_inner)
34 .last()
35 .map_or(SystemBarIcons::FollowSystem, |&(_, icons)| icons)
36}
37
38pub(crate) fn show_current_system_bar_icons() {
39 if let Some(host) = host_controller() {
40 host.set_system_bar_icons(system_bar_icons());
41 }
42}
43
44fn request(icons: SystemBarIcons) -> u64 {
45 let id = NEXT_REQUEST.fetch_add(1, Ordering::Relaxed);
46 REQUESTS
47 .lock()
48 .unwrap_or_else(PoisonError::into_inner)
49 .push((id, icons));
50 show_current_system_bar_icons();
51 id
52}
53
54fn withdraw(id: u64) {
55 REQUESTS
56 .lock()
57 .unwrap_or_else(PoisonError::into_inner)
58 .retain(|&(request, _)| request != id);
59 show_current_system_bar_icons();
60}
61
62#[expect(non_snake_case, reason = "names follow Compose's casing")]
69#[track_caller]
70pub fn SystemBarAppearance(icons: SystemBarIcons) {
71 DisposableEffect(icons, move |scope| {
72 let id = request(icons);
73 scope.on_dispose(move || withdraw(id))
74 });
75}
76
77#[cfg(test)]
78#[path = "tests/system_bars_tests.rs"]
79mod tests;