xdg_desktop_portal.rs

  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}