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 gpui::{BackgroundExecutor, WindowAppearance};
11
12pub enum Event {
13 WindowAppearance(WindowAppearance),
14 #[cfg_attr(feature = "x11", allow(dead_code))]
15 CursorTheme(String),
16 #[cfg_attr(feature = "x11", allow(dead_code))]
17 CursorSize(u32),
18 ButtonLayout(String),
19}
20
21pub struct XDPEventSource {
22 channel: Channel<Event>,
23}
24
25impl XDPEventSource {
26 pub fn new(executor: &BackgroundExecutor) -> Self {
27 let (sender, channel) = calloop::channel::channel();
28
29 let background = executor.clone();
30
31 executor
32 .spawn(async move {
33 let settings = Settings::new().await?;
34
35 if let Ok(initial_appearance) = settings.color_scheme().await {
36 sender.send(Event::WindowAppearance(
37 window_appearance_from_color_scheme(initial_appearance),
38 ))?;
39 }
40 if let Ok(initial_theme) = settings
41 .read::<String>("org.gnome.desktop.interface", "cursor-theme")
42 .await
43 {
44 sender.send(Event::CursorTheme(initial_theme))?;
45 }
46
47 // If u32 is used here, it throws invalid type error
48 if let Ok(initial_size) = settings
49 .read::<i32>("org.gnome.desktop.interface", "cursor-size")
50 .await
51 {
52 sender.send(Event::CursorSize(initial_size as u32))?;
53 }
54
55 if let Ok(initial_layout) = settings
56 .read::<String>("org.gnome.desktop.wm.preferences", "button-layout")
57 .await
58 {
59 sender.send(Event::ButtonLayout(initial_layout))?;
60 }
61
62 if let Ok(mut cursor_theme_changed) = settings
63 .receive_setting_changed_with_args(
64 "org.gnome.desktop.interface",
65 "cursor-theme",
66 )
67 .await
68 {
69 let sender = sender.clone();
70 background
71 .spawn(async move {
72 while let Some(theme) = cursor_theme_changed.next().await {
73 let theme = theme?;
74 sender.send(Event::CursorTheme(theme))?;
75 }
76 anyhow::Ok(())
77 })
78 .detach();
79 }
80
81 if let Ok(mut cursor_size_changed) = settings
82 .receive_setting_changed_with_args::<i32>(
83 "org.gnome.desktop.interface",
84 "cursor-size",
85 )
86 .await
87 {
88 let sender = sender.clone();
89 background
90 .spawn(async move {
91 while let Some(size) = cursor_size_changed.next().await {
92 let size = size?;
93 sender.send(Event::CursorSize(size as u32))?;
94 }
95 anyhow::Ok(())
96 })
97 .detach();
98 }
99
100 if let Ok(mut button_layout_changed) = settings
101 .receive_setting_changed_with_args(
102 "org.gnome.desktop.wm.preferences",
103 "button-layout",
104 )
105 .await
106 {
107 let sender = sender.clone();
108 background
109 .spawn(async move {
110 while let Some(layout) = button_layout_changed.next().await {
111 let layout = layout?;
112 sender.send(Event::ButtonLayout(layout))?;
113 }
114 anyhow::Ok(())
115 })
116 .detach();
117 }
118
119 let mut appearance_changed = settings.receive_color_scheme_changed().await?;
120 while let Some(scheme) = appearance_changed.next().await {
121 sender.send(Event::WindowAppearance(
122 window_appearance_from_color_scheme(scheme),
123 ))?;
124 }
125
126 anyhow::Ok(())
127 })
128 .detach();
129
130 Self { channel }
131 }
132}
133
134impl EventSource for XDPEventSource {
135 type Event = Event;
136 type Metadata = ();
137 type Ret = ();
138 type Error = anyhow::Error;
139
140 fn process_events<F>(
141 &mut self,
142 readiness: Readiness,
143 token: Token,
144 mut callback: F,
145 ) -> Result<PostAction, Self::Error>
146 where
147 F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
148 {
149 self.channel.process_events(readiness, token, |evt, _| {
150 if let calloop::channel::Event::Msg(msg) = evt {
151 (callback)(msg, &mut ())
152 }
153 })?;
154
155 Ok(PostAction::Continue)
156 }
157
158 fn register(
159 &mut self,
160 poll: &mut Poll,
161 token_factory: &mut TokenFactory,
162 ) -> calloop::Result<()> {
163 self.channel.register(poll, token_factory)?;
164
165 Ok(())
166 }
167
168 fn reregister(
169 &mut self,
170 poll: &mut Poll,
171 token_factory: &mut TokenFactory,
172 ) -> calloop::Result<()> {
173 self.channel.reregister(poll, token_factory)?;
174
175 Ok(())
176 }
177
178 fn unregister(&mut self, poll: &mut Poll) -> calloop::Result<()> {
179 self.channel.unregister(poll)?;
180
181 Ok(())
182 }
183}
184
185fn window_appearance_from_color_scheme(cs: ColorScheme) -> WindowAppearance {
186 match cs {
187 ColorScheme::PreferDark => WindowAppearance::Dark,
188 ColorScheme::PreferLight => WindowAppearance::Light,
189 ColorScheme::NoPreference => WindowAppearance::Light,
190 }
191}