ashpd/desktop/
wallpaper.rs

1//! Set a wallpaper on lockscreen, background or both.
2//!
3//! Wrapper of the DBus interface: [`org.freedesktop.portal.Wallpaper`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Wallpaper.html).
4//!
5//! # Examples
6//!
7//! ## Sets a wallpaper from a file:
8//!
9//! ```rust,no_run
10//! use std::{fs::File, os::fd::AsFd};
11//!
12//! use ashpd::desktop::wallpaper::{SetOn, WallpaperRequest};
13//!
14//! async fn run() -> ashpd::Result<()> {
15//!     let file = File::open("/home/bilelmoussaoui/adwaita-day.jpg").unwrap();
16//!     WallpaperRequest::default()
17//!         .set_on(SetOn::Both)
18//!         .show_preview(true)
19//!         .build_file(&file.as_fd())
20//!         .await?;
21//!     Ok(())
22//! }
23//! ```
24//!
25//! ## Sets a wallpaper from a URI:
26//!
27//! ```rust,no_run
28//! use ashpd::desktop::wallpaper::{SetOn, WallpaperRequest};
29//!
30//! async fn run() -> ashpd::Result<()> {
31//!     let uri =
32//!         url::Url::parse("file:///home/bilelmoussaoui/Downloads/adwaita-night.jpg").unwrap();
33//!     WallpaperRequest::default()
34//!         .set_on(SetOn::Both)
35//!         .show_preview(true)
36//!         .build_uri(&uri)
37//!         .await?;
38//!     Ok(())
39//! }
40//! ```
41
42use std::{fmt, os::fd::AsFd, str::FromStr};
43
44use serde::{self, Deserialize, Serialize};
45use zbus::zvariant::{Fd, SerializeDict, Type};
46
47use super::Request;
48use crate::{desktop::HandleToken, proxy::Proxy, Error, WindowIdentifier};
49
50#[cfg_attr(feature = "glib", derive(glib::Enum))]
51#[cfg_attr(feature = "glib", enum_type(name = "AshpdSetOn"))]
52#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, Type)]
53#[zvariant(signature = "s")]
54#[serde(rename_all = "lowercase")]
55/// Where to set the wallpaper on.
56pub enum SetOn {
57    /// Set the wallpaper only on the lock-screen.
58    Lockscreen,
59    /// Set the wallpaper only on the background.
60    Background,
61    /// Set the wallpaper on both lock-screen and background.
62    Both,
63}
64
65impl fmt::Display for SetOn {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        match self {
68            Self::Lockscreen => write!(f, "Lockscreen"),
69            Self::Background => write!(f, "Background"),
70            Self::Both => write!(f, "Both"),
71        }
72    }
73}
74
75impl AsRef<str> for SetOn {
76    fn as_ref(&self) -> &str {
77        match self {
78            Self::Lockscreen => "Lockscreen",
79            Self::Background => "Background",
80            Self::Both => "Both",
81        }
82    }
83}
84
85impl From<SetOn> for &'static str {
86    fn from(s: SetOn) -> Self {
87        match s {
88            SetOn::Lockscreen => "Lockscreen",
89            SetOn::Background => "Background",
90            SetOn::Both => "Both",
91        }
92    }
93}
94
95impl FromStr for SetOn {
96    type Err = Error;
97
98    fn from_str(s: &str) -> Result<Self, Self::Err> {
99        match s {
100            "Lockscreen" => Ok(SetOn::Lockscreen),
101            "Background" => Ok(SetOn::Background),
102            "Both" => Ok(SetOn::Both),
103            _ => Err(Error::ParseError("Failed to parse SetOn, invalid value")),
104        }
105    }
106}
107
108#[derive(SerializeDict, Type, Debug, Default)]
109#[zvariant(signature = "dict")]
110struct WallpaperOptions {
111    handle_token: HandleToken,
112    #[zvariant(rename = "show-preview")]
113    show_preview: Option<bool>,
114    #[zvariant(rename = "set-on")]
115    set_on: Option<SetOn>,
116}
117
118struct WallpaperProxy<'a>(Proxy<'a>);
119
120impl<'a> WallpaperProxy<'a> {
121    pub async fn new() -> Result<WallpaperProxy<'a>, Error> {
122        let proxy = Proxy::new_desktop("org.freedesktop.portal.Wallpaper").await?;
123        Ok(Self(proxy))
124    }
125
126    pub async fn set_wallpaper_file(
127        &self,
128        identifier: Option<&WindowIdentifier>,
129        file: &impl AsFd,
130        options: WallpaperOptions,
131    ) -> Result<Request<()>, Error> {
132        let identifier = identifier.map(|i| i.to_string()).unwrap_or_default();
133        self.0
134            .empty_request(
135                &options.handle_token,
136                "SetWallpaperFile",
137                &(&identifier, Fd::from(file), &options),
138            )
139            .await
140    }
141
142    pub async fn set_wallpaper_uri(
143        &self,
144        identifier: Option<&WindowIdentifier>,
145        uri: &url::Url,
146        options: WallpaperOptions,
147    ) -> Result<Request<()>, Error> {
148        let identifier = identifier.map(|i| i.to_string()).unwrap_or_default();
149        self.0
150            .empty_request(
151                &options.handle_token,
152                "SetWallpaperURI",
153                &(&identifier, uri, &options),
154            )
155            .await
156    }
157}
158
159impl<'a> std::ops::Deref for WallpaperProxy<'a> {
160    type Target = zbus::Proxy<'a>;
161
162    fn deref(&self) -> &Self::Target {
163        &self.0
164    }
165}
166
167#[derive(Debug, Default)]
168#[doc(alias = "xdp_portal_set_wallpaper")]
169#[doc(alias = "org.freedesktop.portal.Wallpaper")]
170/// A [builder-pattern] type to set the wallpaper.
171///
172/// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html
173pub struct WallpaperRequest {
174    identifier: Option<WindowIdentifier>,
175    options: WallpaperOptions,
176}
177
178impl WallpaperRequest {
179    #[must_use]
180    /// Sets a window identifier.
181    pub fn identifier(mut self, identifier: impl Into<Option<WindowIdentifier>>) -> Self {
182        self.identifier = identifier.into();
183        self
184    }
185
186    /// Whether to show a preview of the picture.
187    /// **Note** the portal may decide to show a preview even if this option is
188    /// not set.
189    #[must_use]
190    pub fn show_preview(mut self, show_preview: impl Into<Option<bool>>) -> Self {
191        self.options.show_preview = show_preview.into();
192        self
193    }
194
195    /// Sets where to set the wallpaper on.
196    #[must_use]
197    pub fn set_on(mut self, set_on: impl Into<Option<SetOn>>) -> Self {
198        self.options.set_on = set_on.into();
199        self
200    }
201
202    /// Build using a URI.
203    pub async fn build_uri(self, uri: &url::Url) -> Result<Request<()>, Error> {
204        let proxy = WallpaperProxy::new().await?;
205        proxy
206            .set_wallpaper_uri(self.identifier.as_ref(), uri, self.options)
207            .await
208    }
209
210    /// Build using a file.
211    pub async fn build_file(self, file: &impl AsFd) -> Result<Request<()>, Error> {
212        let proxy = WallpaperProxy::new().await?;
213        proxy
214            .set_wallpaper_file(self.identifier.as_ref(), file, self.options)
215            .await
216    }
217}
218#[cfg(test)]
219mod tests {
220    use super::SetOn;
221
222    #[test]
223    fn serialize_deserialize() {
224        let set_on = SetOn::Both;
225        let string = serde_json::to_string(&set_on).unwrap();
226        assert_eq!(string, "\"both\"");
227
228        let decoded = serde_json::from_str(&string).unwrap();
229        assert_eq!(set_on, decoded);
230    }
231}