xdg_desktop_portal.rs

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