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