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(initial_appearance) = settings.color_scheme().await {
33 sender.send(Event::WindowAppearance(WindowAppearance::from_native(
34 initial_appearance,
35 )))?;
36 }
37 if let Ok(initial_theme) = settings
38 .read::<String>("org.gnome.desktop.interface", "cursor-theme")
39 .await
40 {
41 sender.send(Event::CursorTheme(initial_theme))?;
42 }
43 if let Ok(initial_size) = settings
44 .read::<u32>("org.gnome.desktop.interface", "cursor-size")
45 .await
46 {
47 sender.send(Event::CursorSize(initial_size))?;
48 }
49
50 if let Ok(mut cursor_theme_changed) = settings
51 .receive_setting_changed_with_args(
52 "org.gnome.desktop.interface",
53 "cursor-theme",
54 )
55 .await
56 {
57 let sender = sender.clone();
58 background
59 .spawn(async move {
60 while let Some(theme) = cursor_theme_changed.next().await {
61 let theme = theme?;
62 sender.send(Event::CursorTheme(theme))?;
63 }
64 anyhow::Ok(())
65 })
66 .detach();
67 }
68
69 if let Ok(mut cursor_size_changed) = settings
70 .receive_setting_changed_with_args::<u32>(
71 "org.gnome.desktop.interface",
72 "cursor-size",
73 )
74 .await
75 {
76 let sender = sender.clone();
77 background
78 .spawn(async move {
79 while let Some(size) = cursor_size_changed.next().await {
80 let size = size?;
81 sender.send(Event::CursorSize(size))?;
82 }
83 anyhow::Ok(())
84 })
85 .detach();
86 }
87
88 let mut appearance_changed = settings.receive_color_scheme_changed().await?;
89 while let Some(scheme) = appearance_changed.next().await {
90 sender.send(Event::WindowAppearance(WindowAppearance::from_native(
91 scheme,
92 )))?;
93 }
94
95 anyhow::Ok(())
96 })
97 .detach();
98
99 Self { channel }
100 }
101}
102
103impl EventSource for XDPEventSource {
104 type Event = Event;
105 type Metadata = ();
106 type Ret = ();
107 type Error = anyhow::Error;
108
109 fn process_events<F>(
110 &mut self,
111 readiness: Readiness,
112 token: Token,
113 mut callback: F,
114 ) -> Result<PostAction, Self::Error>
115 where
116 F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
117 {
118 self.channel.process_events(readiness, token, |evt, _| {
119 if let calloop::channel::Event::Msg(msg) = evt {
120 (callback)(msg, &mut ())
121 }
122 })?;
123
124 Ok(PostAction::Continue)
125 }
126
127 fn register(
128 &mut self,
129 poll: &mut Poll,
130 token_factory: &mut TokenFactory,
131 ) -> calloop::Result<()> {
132 self.channel.register(poll, token_factory)?;
133
134 Ok(())
135 }
136
137 fn reregister(
138 &mut self,
139 poll: &mut Poll,
140 token_factory: &mut TokenFactory,
141 ) -> calloop::Result<()> {
142 self.channel.reregister(poll, token_factory)?;
143
144 Ok(())
145 }
146
147 fn unregister(&mut self, poll: &mut Poll) -> calloop::Result<()> {
148 self.channel.unregister(poll)?;
149
150 Ok(())
151 }
152}
153
154impl WindowAppearance {
155 fn from_native(cs: ColorScheme) -> WindowAppearance {
156 match cs {
157 ColorScheme::PreferDark => WindowAppearance::Dark,
158 ColorScheme::PreferLight => WindowAppearance::Light,
159 ColorScheme::NoPreference => WindowAppearance::Light,
160 }
161 }
162
163 #[cfg_attr(target_os = "linux", allow(dead_code))]
164 fn set_native(&mut self, cs: ColorScheme) {
165 *self = Self::from_native(cs);
166 }
167}