1//! Provides a [calloop] event source from [XDG Desktop Portal] events
2//!
3//! This module uses the [ashpd] crate and handles many async loop
4use std::future::Future;
5
6use ashpd::desktop::settings::{ColorScheme, Settings};
7use calloop::channel::{Channel, Sender};
8use calloop::{EventSource, Poll, PostAction, Readiness, Token, TokenFactory};
9use parking_lot::Mutex;
10use smol::stream::StreamExt;
11use util::ResultExt;
12
13use crate::{BackgroundExecutor, WindowAppearance};
14
15pub enum Event {
16 WindowAppearance(WindowAppearance),
17}
18
19pub struct XDPEventSource {
20 channel: Channel<Event>,
21}
22
23impl XDPEventSource {
24 pub fn new(executor: &BackgroundExecutor) -> Self {
25 let (sender, channel) = calloop::channel::channel();
26
27 Self::spawn_observer(executor, Self::appearance_observer(sender.clone()));
28
29 Self { channel }
30 }
31
32 fn spawn_observer(
33 executor: &BackgroundExecutor,
34 to_spawn: impl Future<Output = Result<(), anyhow::Error>> + Send + 'static,
35 ) {
36 executor
37 .spawn(async move {
38 to_spawn.await.log_err();
39 })
40 .detach()
41 }
42
43 async fn appearance_observer(sender: Sender<Event>) -> Result<(), anyhow::Error> {
44 let settings = Settings::new().await?;
45
46 // We observe the color change during the execution of the application
47 let mut stream = settings.receive_color_scheme_changed().await?;
48 while let Some(scheme) = stream.next().await {
49 sender.send(Event::WindowAppearance(WindowAppearance::from_native(
50 scheme,
51 )))?;
52 }
53
54 Ok(())
55 }
56}
57
58impl EventSource for XDPEventSource {
59 type Event = Event;
60 type Metadata = ();
61 type Ret = ();
62 type Error = anyhow::Error;
63
64 fn process_events<F>(
65 &mut self,
66 readiness: Readiness,
67 token: Token,
68 mut callback: F,
69 ) -> Result<PostAction, Self::Error>
70 where
71 F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
72 {
73 self.channel.process_events(readiness, token, |evt, _| {
74 if let calloop::channel::Event::Msg(msg) = evt {
75 (callback)(msg, &mut ())
76 }
77 })?;
78
79 Ok(PostAction::Continue)
80 }
81
82 fn register(
83 &mut self,
84 poll: &mut Poll,
85 token_factory: &mut TokenFactory,
86 ) -> calloop::Result<()> {
87 self.channel.register(poll, token_factory)?;
88
89 Ok(())
90 }
91
92 fn reregister(
93 &mut self,
94 poll: &mut Poll,
95 token_factory: &mut TokenFactory,
96 ) -> calloop::Result<()> {
97 self.channel.reregister(poll, token_factory)?;
98
99 Ok(())
100 }
101
102 fn unregister(&mut self, poll: &mut Poll) -> calloop::Result<()> {
103 self.channel.unregister(poll)?;
104
105 Ok(())
106 }
107}
108
109impl WindowAppearance {
110 fn from_native(cs: ColorScheme) -> WindowAppearance {
111 match cs {
112 ColorScheme::PreferDark => WindowAppearance::Dark,
113 ColorScheme::PreferLight => WindowAppearance::Light,
114 ColorScheme::NoPreference => WindowAppearance::Light,
115 }
116 }
117
118 fn set_native(&mut self, cs: ColorScheme) {
119 *self = Self::from_native(cs);
120 }
121}
122
123pub fn window_appearance(executor: &BackgroundExecutor) -> Result<WindowAppearance, anyhow::Error> {
124 executor.block(async {
125 let settings = Settings::new().await?;
126
127 let scheme = settings.color_scheme().await?;
128
129 let appearance = WindowAppearance::from_native(scheme);
130
131 Ok(appearance)
132 })
133}
134
135pub fn should_auto_hide_scrollbars(executor: &BackgroundExecutor) -> Result<bool, anyhow::Error> {
136 executor.block(async {
137 let settings = Settings::new().await?;
138 let auto_hide = settings
139 .read::<bool>("org.gnome.desktop.interface", "overlay-scrolling")
140 .await?;
141
142 Ok(auto_hide)
143 })
144}