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