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 smol::stream::StreamExt;
10use util::ResultExt;
11
12use crate::{BackgroundExecutor, WindowAppearance};
13
14pub enum Event {
15 WindowAppearance(WindowAppearance),
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 Self::spawn_observer(executor, Self::appearance_observer(sender.clone()));
27
28 Self { channel }
29 }
30
31 fn spawn_observer(
32 executor: &BackgroundExecutor,
33 to_spawn: impl Future<Output = Result<(), anyhow::Error>> + Send + 'static,
34 ) {
35 executor
36 .spawn(async move {
37 to_spawn.await.log_err();
38 })
39 .detach()
40 }
41
42 async fn appearance_observer(sender: Sender<Event>) -> Result<(), anyhow::Error> {
43 let settings = Settings::new().await?;
44
45 // We observe the color change during the execution of the application
46 let mut stream = settings.receive_color_scheme_changed().await?;
47 while let Some(scheme) = stream.next().await {
48 sender.send(Event::WindowAppearance(WindowAppearance::from_native(
49 scheme,
50 )))?;
51 }
52
53 Ok(())
54 }
55}
56
57impl EventSource for XDPEventSource {
58 type Event = Event;
59 type Metadata = ();
60 type Ret = ();
61 type Error = anyhow::Error;
62
63 fn process_events<F>(
64 &mut self,
65 readiness: Readiness,
66 token: Token,
67 mut callback: F,
68 ) -> Result<PostAction, Self::Error>
69 where
70 F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
71 {
72 self.channel.process_events(readiness, token, |evt, _| {
73 if let calloop::channel::Event::Msg(msg) = evt {
74 (callback)(msg, &mut ())
75 }
76 })?;
77
78 Ok(PostAction::Continue)
79 }
80
81 fn register(
82 &mut self,
83 poll: &mut Poll,
84 token_factory: &mut TokenFactory,
85 ) -> calloop::Result<()> {
86 self.channel.register(poll, token_factory)?;
87
88 Ok(())
89 }
90
91 fn reregister(
92 &mut self,
93 poll: &mut Poll,
94 token_factory: &mut TokenFactory,
95 ) -> calloop::Result<()> {
96 self.channel.reregister(poll, token_factory)?;
97
98 Ok(())
99 }
100
101 fn unregister(&mut self, poll: &mut Poll) -> calloop::Result<()> {
102 self.channel.unregister(poll)?;
103
104 Ok(())
105 }
106}
107
108impl WindowAppearance {
109 fn from_native(cs: ColorScheme) -> WindowAppearance {
110 match cs {
111 ColorScheme::PreferDark => WindowAppearance::Dark,
112 ColorScheme::PreferLight => WindowAppearance::Light,
113 ColorScheme::NoPreference => WindowAppearance::Light,
114 }
115 }
116
117 #[cfg_attr(target_os = "linux", allow(dead_code))]
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}