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 std::sync::Arc;
  6
  7use anyhow::anyhow;
  8use ashpd::desktop::settings::{ColorScheme, Settings};
  9use calloop::channel::{Channel, Sender};
 10use calloop::{EventSource, Poll, PostAction, Readiness, Token, TokenFactory};
 11use mio::Waker;
 12use smol::stream::StreamExt;
 13
 14use crate::{BackgroundExecutor, WindowAppearance};
 15
 16pub enum Event {
 17    WindowAppearance(WindowAppearance),
 18    CursorTheme(String),
 19    CursorSize(u32),
 20}
 21
 22pub struct XDPEventSource {
 23    channel: Channel<Event>,
 24}
 25
 26impl XDPEventSource {
 27    pub fn new(executor: &BackgroundExecutor, waker: Option<Arc<Waker>>) -> Self {
 28        let (sender, channel) = calloop::channel::channel();
 29
 30        let background = executor.clone();
 31
 32        executor
 33            .spawn(async move {
 34                fn send_event<T>(
 35                    sender: &Sender<T>,
 36                    waker: &Option<Arc<Waker>>,
 37                    event: T,
 38                ) -> Result<(), std::sync::mpsc::SendError<T>> {
 39                    sender.send(event)?;
 40                    if let Some(waker) = waker {
 41                        waker.wake().ok();
 42                    };
 43                    Ok(())
 44                }
 45
 46                let settings = Settings::new().await?;
 47
 48                if let Ok(initial_appearance) = settings.color_scheme().await {
 49                    send_event(
 50                        &sender,
 51                        &waker,
 52                        Event::WindowAppearance(WindowAppearance::from_native(initial_appearance)),
 53                    )?;
 54                }
 55                if let Ok(initial_theme) = settings
 56                    .read::<String>("org.gnome.desktop.interface", "cursor-theme")
 57                    .await
 58                {
 59                    send_event(&sender, &waker, Event::CursorTheme(initial_theme))?;
 60                }
 61                if let Ok(initial_size) = settings
 62                    .read::<u32>("org.gnome.desktop.interface", "cursor-size")
 63                    .await
 64                {
 65                    send_event(&sender, &waker, Event::CursorSize(initial_size))?;
 66                }
 67
 68                if let Ok(mut cursor_theme_changed) = settings
 69                    .receive_setting_changed_with_args(
 70                        "org.gnome.desktop.interface",
 71                        "cursor-theme",
 72                    )
 73                    .await
 74                {
 75                    let sender = sender.clone();
 76                    let waker = waker.clone();
 77                    background
 78                        .spawn(async move {
 79                            while let Some(theme) = cursor_theme_changed.next().await {
 80                                let theme = theme?;
 81                                send_event(&sender, &waker, Event::CursorTheme(theme))?;
 82                            }
 83                            anyhow::Ok(())
 84                        })
 85                        .detach();
 86                }
 87
 88                if let Ok(mut cursor_size_changed) = settings
 89                    .receive_setting_changed_with_args::<u32>(
 90                        "org.gnome.desktop.interface",
 91                        "cursor-size",
 92                    )
 93                    .await
 94                {
 95                    let sender = sender.clone();
 96                    let waker = waker.clone();
 97                    background
 98                        .spawn(async move {
 99                            while let Some(size) = cursor_size_changed.next().await {
100                                let size = size?;
101                                send_event(&sender, &waker, Event::CursorSize(size))?;
102                            }
103                            anyhow::Ok(())
104                        })
105                        .detach();
106                }
107
108                let mut appearance_changed = settings.receive_color_scheme_changed().await?;
109                while let Some(scheme) = appearance_changed.next().await {
110                    send_event(
111                        &sender,
112                        &waker,
113                        Event::WindowAppearance(WindowAppearance::from_native(scheme)),
114                    )?;
115                }
116
117                anyhow::Ok(())
118            })
119            .detach();
120
121        Self { channel }
122    }
123
124    pub fn try_recv(&self) -> anyhow::Result<Event> {
125        self.channel
126            .try_recv()
127            .map_err(|error| anyhow!("{}", error))
128    }
129}
130
131impl EventSource for XDPEventSource {
132    type Event = Event;
133    type Metadata = ();
134    type Ret = ();
135    type Error = anyhow::Error;
136
137    fn process_events<F>(
138        &mut self,
139        readiness: Readiness,
140        token: Token,
141        mut callback: F,
142    ) -> Result<PostAction, Self::Error>
143    where
144        F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
145    {
146        self.channel.process_events(readiness, token, |evt, _| {
147            if let calloop::channel::Event::Msg(msg) = evt {
148                (callback)(msg, &mut ())
149            }
150        })?;
151
152        Ok(PostAction::Continue)
153    }
154
155    fn register(
156        &mut self,
157        poll: &mut Poll,
158        token_factory: &mut TokenFactory,
159    ) -> calloop::Result<()> {
160        self.channel.register(poll, token_factory)?;
161
162        Ok(())
163    }
164
165    fn reregister(
166        &mut self,
167        poll: &mut Poll,
168        token_factory: &mut TokenFactory,
169    ) -> calloop::Result<()> {
170        self.channel.reregister(poll, token_factory)?;
171
172        Ok(())
173    }
174
175    fn unregister(&mut self, poll: &mut Poll) -> calloop::Result<()> {
176        self.channel.unregister(poll)?;
177
178        Ok(())
179    }
180}
181
182impl WindowAppearance {
183    fn from_native(cs: ColorScheme) -> WindowAppearance {
184        match cs {
185            ColorScheme::PreferDark => WindowAppearance::Dark,
186            ColorScheme::PreferLight => WindowAppearance::Light,
187            ColorScheme::NoPreference => WindowAppearance::Light,
188        }
189    }
190
191    #[cfg_attr(target_os = "linux", allow(dead_code))]
192    fn set_native(&mut self, cs: ColorScheme) {
193        *self = Self::from_native(cs);
194    }
195}