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 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}