1//! Provides a [calloop] event source from [XDG Desktop Portal] events
2//!
3//! This module uses the [ashpd] crate
4
5use ashpd::desktop::settings::{ColorScheme, Settings};
6use calloop::channel::Channel;
7use calloop::{EventSource, Poll, PostAction, Readiness, Token, TokenFactory};
8use smol::stream::StreamExt;
9
10use crate::{BackgroundExecutor, WindowAppearance};
11
12pub enum Event {
13 WindowAppearance(WindowAppearance),
14 CursorTheme(String),
15 CursorSize(u32),
16}
17
18pub struct XDPEventSource {
19 channel: Channel<Event>,
20}
21
22impl XDPEventSource {
23 pub fn new(executor: &BackgroundExecutor) -> Self {
24 let (sender, channel) = calloop::channel::channel();
25
26 let background = executor.clone();
27
28 executor
29 .spawn(async move {
30 let settings = Settings::new().await?;
31
32 if let Ok(mut cursor_theme_changed) = settings
33 .receive_setting_changed_with_args(
34 "org.gnome.desktop.interface",
35 "cursor-theme",
36 )
37 .await
38 {
39 let sender = sender.clone();
40 background
41 .spawn(async move {
42 while let Some(theme) = cursor_theme_changed.next().await {
43 let theme = theme?;
44 sender.send(Event::CursorTheme(theme))?;
45 }
46 anyhow::Ok(())
47 })
48 .detach();
49 }
50
51 if let Ok(mut cursor_size_changed) = settings
52 .receive_setting_changed_with_args::<u32>(
53 "org.gnome.desktop.interface",
54 "cursor-size",
55 )
56 .await
57 {
58 let sender = sender.clone();
59 background
60 .spawn(async move {
61 while let Some(size) = cursor_size_changed.next().await {
62 let size = size?;
63 sender.send(Event::CursorSize(size))?;
64 }
65 anyhow::Ok(())
66 })
67 .detach();
68 }
69
70 let mut appearance_changed = settings.receive_color_scheme_changed().await?;
71 while let Some(scheme) = appearance_changed.next().await {
72 sender.send(Event::WindowAppearance(WindowAppearance::from_native(
73 scheme,
74 )))?;
75 }
76
77 anyhow::Ok(())
78 })
79 .detach();
80
81 Self { channel }
82 }
83}
84
85impl EventSource for XDPEventSource {
86 type Event = Event;
87 type Metadata = ();
88 type Ret = ();
89 type Error = anyhow::Error;
90
91 fn process_events<F>(
92 &mut self,
93 readiness: Readiness,
94 token: Token,
95 mut callback: F,
96 ) -> Result<PostAction, Self::Error>
97 where
98 F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
99 {
100 self.channel.process_events(readiness, token, |evt, _| {
101 if let calloop::channel::Event::Msg(msg) = evt {
102 (callback)(msg, &mut ())
103 }
104 })?;
105
106 Ok(PostAction::Continue)
107 }
108
109 fn register(
110 &mut self,
111 poll: &mut Poll,
112 token_factory: &mut TokenFactory,
113 ) -> calloop::Result<()> {
114 self.channel.register(poll, token_factory)?;
115
116 Ok(())
117 }
118
119 fn reregister(
120 &mut self,
121 poll: &mut Poll,
122 token_factory: &mut TokenFactory,
123 ) -> calloop::Result<()> {
124 self.channel.reregister(poll, token_factory)?;
125
126 Ok(())
127 }
128
129 fn unregister(&mut self, poll: &mut Poll) -> calloop::Result<()> {
130 self.channel.unregister(poll)?;
131
132 Ok(())
133 }
134}
135
136impl WindowAppearance {
137 fn from_native(cs: ColorScheme) -> WindowAppearance {
138 match cs {
139 ColorScheme::PreferDark => WindowAppearance::Dark,
140 ColorScheme::PreferLight => WindowAppearance::Light,
141 ColorScheme::NoPreference => WindowAppearance::Light,
142 }
143 }
144
145 #[cfg_attr(target_os = "linux", allow(dead_code))]
146 fn set_native(&mut self, cs: ColorScheme) {
147 *self = Self::from_native(cs);
148 }
149}
150
151pub fn window_appearance(executor: &BackgroundExecutor) -> Result<WindowAppearance, anyhow::Error> {
152 executor.block(async {
153 let settings = Settings::new().await?;
154
155 let scheme = settings.color_scheme().await?;
156
157 let appearance = WindowAppearance::from_native(scheme);
158
159 Ok(appearance)
160 })
161}
162
163pub fn should_auto_hide_scrollbars(executor: &BackgroundExecutor) -> Result<bool, anyhow::Error> {
164 executor.block(async {
165 let settings = Settings::new().await?;
166 let auto_hide = settings
167 .read::<bool>("org.gnome.desktop.interface", "overlay-scrolling")
168 .await?;
169
170 Ok(auto_hide)
171 })
172}
173
174pub async fn cursor_settings() -> Result<(String, Option<u32>), anyhow::Error> {
175 let settings = Settings::new().await?;
176 let cursor_theme = settings
177 .read::<String>("org.gnome.desktop.interface", "cursor-theme")
178 .await?;
179 let cursor_size = settings
180 .read::<u32>("org.gnome.desktop.interface", "cursor-size")
181 .await
182 .ok();
183
184 Ok((cursor_theme, cursor_size))
185}