1 /*
2 * Copyright (C) 2019, Matthias Clasen
3 *
4 * This file is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as
6 * published by the Free Software Foundation, version 3.0 of the
7 * License.
8 *
9 * This file is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * SPDX-License-Identifier: LGPL-3.0-only
18 */
19
20 #include "config.h"
21
22 #include "wallpaper.h"
23
24 #define GNU_SOURCE 1
25
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29
30 #include <glib/gstdio.h>
31 #include <gio/gunixfdlist.h>
32
33 #include "portal-private.h"
34
35 typedef struct {
36 XdpPortal *portal;
37 XdpParent *parent;
38 char *parent_handle;
39 char *uri;
40 gboolean show_preview;
41 XdpWallpaperFlags target;
42 guint signal_id;
43 GTask *task;
44 char *request_path;
45 guint cancelled_id;
46 } WallpaperCall;
47
48 static void
wallpaper_call_free(WallpaperCall * call)49 wallpaper_call_free (WallpaperCall *call)
50 {
51 if (call->parent)
52 {
53 call->parent->parent_unexport (call->parent);
54 xdp_parent_free (call->parent);
55 }
56 g_free (call->parent_handle);
57
58 if (call->signal_id)
59 g_dbus_connection_signal_unsubscribe (call->portal->bus, call->signal_id);
60
61 if (call->cancelled_id)
62 g_signal_handler_disconnect (g_task_get_cancellable (call->task), call->cancelled_id);
63
64 g_free (call->request_path);
65
66 g_object_unref (call->portal);
67 g_object_unref (call->task);
68 g_free (call->uri);
69
70 g_free (call);
71 }
72
73 static void
response_received(GDBusConnection * bus,const char * sender_name,const char * object_path,const char * interface_name,const char * signal_name,GVariant * parameters,gpointer data)74 response_received (GDBusConnection *bus,
75 const char *sender_name,
76 const char *object_path,
77 const char *interface_name,
78 const char *signal_name,
79 GVariant *parameters,
80 gpointer data)
81 {
82 WallpaperCall *call = data;
83 guint32 response;
84 g_autoptr(GVariant) ret = NULL;
85
86 g_variant_get (parameters, "(u@a{sv})", &response, &ret);
87
88 if (response == 0)
89 g_task_return_boolean (call->task, TRUE);
90 else if (response == 1)
91 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "SetWallpaper canceled");
92 else
93 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_FAILED, "SetWallpaper failed");
94
95 wallpaper_call_free (call);
96 }
97
98 static void set_wallpaper (WallpaperCall *call);
99
100 static void
parent_exported(XdpParent * parent,const char * handle,gpointer data)101 parent_exported (XdpParent *parent,
102 const char *handle,
103 gpointer data)
104 {
105 WallpaperCall *call = data;
106 call->parent_handle = g_strdup (handle);
107 set_wallpaper (call);
108 }
109
110 static void
cancelled_cb(GCancellable * cancellable,gpointer data)111 cancelled_cb (GCancellable *cancellable,
112 gpointer data)
113 {
114 WallpaperCall *call = data;
115
116 g_dbus_connection_call (call->portal->bus,
117 PORTAL_BUS_NAME,
118 call->request_path,
119 REQUEST_INTERFACE,
120 "Close",
121 NULL,
122 NULL,
123 G_DBUS_CALL_FLAGS_NONE,
124 -1,
125 NULL, NULL, NULL);
126
127 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "SetWallpaper call canceled by caller");
128
129 wallpaper_call_free (call);
130 }
131
132 static void
call_returned(GObject * object,GAsyncResult * result,gpointer data)133 call_returned (GObject *object,
134 GAsyncResult *result,
135 gpointer data)
136 {
137 WallpaperCall *call = data;
138 GError *error = NULL;
139 g_autoptr(GVariant) ret = NULL;
140 g_autoptr(GFile) file = NULL;
141
142 file = g_file_new_for_uri (call->uri);
143 if (g_file_is_native (file))
144 ret = g_dbus_connection_call_with_unix_fd_list_finish (G_DBUS_CONNECTION (object), NULL, result, &error);
145 else
146 ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
147
148 if (error)
149 {
150 g_task_return_error (call->task, error);
151 wallpaper_call_free (call);
152 }
153 }
154
155 #ifndef O_PATH
156 #define O_PATH 0
157 #endif
158
159 static const char *
target_to_string(XdpWallpaperFlags target)160 target_to_string (XdpWallpaperFlags target)
161 {
162 if (((target & XDP_WALLPAPER_FLAG_BACKGROUND) != 0) &&
163 ((target & XDP_WALLPAPER_FLAG_LOCKSCREEN) != 0))
164 return "both";
165 else if ((target & XDP_WALLPAPER_FLAG_BACKGROUND) != 0)
166 return "background";
167 else if ((target & XDP_WALLPAPER_FLAG_LOCKSCREEN) != 0)
168 return "lockscreen";
169 else
170 {
171 g_warning ("Unknown XdpWallpaperTarget value");
172 return "both";
173 }
174 }
175
176 static void
set_wallpaper(WallpaperCall * call)177 set_wallpaper (WallpaperCall *call)
178 {
179 GVariantBuilder options;
180 g_autofree char *token = NULL;
181 g_autoptr(GFile) file = NULL;
182 GCancellable *cancellable;
183
184 if (call->parent_handle == NULL)
185 {
186 call->parent->parent_export (call->parent, parent_exported, call);
187 return;
188 }
189
190 token = g_strdup_printf ("portal%d", g_random_int_range (0, G_MAXINT));
191 call->request_path = g_strconcat (REQUEST_PATH_PREFIX, call->portal->sender, "/", token, NULL);
192 call->signal_id = g_dbus_connection_signal_subscribe (call->portal->bus,
193 PORTAL_BUS_NAME,
194 REQUEST_INTERFACE,
195 "Response",
196 call->request_path,
197 NULL,
198 G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
199 response_received,
200 call,
201 NULL);
202
203 cancellable = g_task_get_cancellable (call->task);
204 if (cancellable)
205 call->cancelled_id = g_signal_connect (cancellable, "cancelled", G_CALLBACK (cancelled_cb), call);
206
207 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
208 g_variant_builder_add (&options, "{sv}", "handle_token", g_variant_new_string (token));
209 g_variant_builder_add (&options, "{sv}", "show-preview", g_variant_new_boolean (call->show_preview));
210 g_variant_builder_add (&options, "{sv}", "set-on", g_variant_new_string (target_to_string (call->target)));
211
212 file = g_file_new_for_uri (call->uri);
213
214 if (g_file_is_native (file))
215 {
216 g_autoptr(GUnixFDList) fd_list = NULL;
217 g_autofree char *path = NULL;
218 int fd, fd_in;
219
220 path = g_file_get_path (file);
221
222 fd = g_open (path, O_PATH | O_CLOEXEC);
223 if (fd == -1)
224 {
225 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to open '%s'", call->uri);
226 wallpaper_call_free (call);
227
228 return;
229 }
230
231 fd_list = g_unix_fd_list_new_from_array (&fd, 1);
232 fd = -1;
233 fd_in = 0;
234
235 g_dbus_connection_call_with_unix_fd_list (call->portal->bus,
236 PORTAL_BUS_NAME,
237 PORTAL_OBJECT_PATH,
238 "org.freedesktop.portal.Wallpaper",
239 "SetWallpaperFile",
240 g_variant_new ("(sha{sv})", call->parent_handle, fd_in, &options),
241 G_VARIANT_TYPE ("(o)"),
242 G_DBUS_CALL_FLAGS_NONE,
243 -1,
244 fd_list,
245 NULL,
246 call_returned,
247 call);
248 }
249 else
250 {
251 g_dbus_connection_call (call->portal->bus,
252 PORTAL_BUS_NAME,
253 PORTAL_OBJECT_PATH,
254 "org.freedesktop.portal.Wallpaper",
255 "SetWallpaperURI",
256 g_variant_new ("(ssa{sv})", call->parent_handle, call->uri, &options),
257 G_VARIANT_TYPE ("(o)"),
258 G_DBUS_CALL_FLAGS_NONE,
259 -1,
260 NULL,
261 call_returned,
262 call);
263 }
264 }
265
266 /**
267 * xdp_portal_set_wallpaper:
268 * @portal: a [class@Portal]
269 * @parent: parent window information
270 * @uri: the URI to use
271 * @flags: options for this call
272 * @cancellable: (nullable): optional [class@Gio.Cancellable]
273 * @callback: (scope async): a callback to call when the request is done
274 * @data: (closure): data to pass to @callback
275 *
276 * Sets a desktop background image, given by a uri.
277 */
278 void
xdp_portal_set_wallpaper(XdpPortal * portal,XdpParent * parent,const char * uri,XdpWallpaperFlags flags,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer data)279 xdp_portal_set_wallpaper (XdpPortal *portal,
280 XdpParent *parent,
281 const char *uri,
282 XdpWallpaperFlags flags,
283 GCancellable *cancellable,
284 GAsyncReadyCallback callback,
285 gpointer data)
286 {
287 WallpaperCall *call = NULL;
288
289 g_return_if_fail (XDP_IS_PORTAL (portal));
290 g_return_if_fail ((flags & ~(XDP_WALLPAPER_FLAG_BACKGROUND |
291 XDP_WALLPAPER_FLAG_LOCKSCREEN |
292 XDP_WALLPAPER_FLAG_PREVIEW)) == 0);
293
294 call = g_new0 (WallpaperCall, 1);
295 call->portal = g_object_ref (portal);
296 if (parent)
297 call->parent = xdp_parent_copy (parent);
298 else
299 call->parent_handle = g_strdup ("");
300 call->uri = g_strdup (uri);
301 call->show_preview = (flags & XDP_WALLPAPER_FLAG_PREVIEW) != 0;
302 call->target = flags & (XDP_WALLPAPER_FLAG_BACKGROUND | XDP_WALLPAPER_FLAG_LOCKSCREEN);
303 call->task = g_task_new (portal, cancellable, callback, data);
304 g_task_set_source_tag (call->task, xdp_portal_set_wallpaper);
305
306 set_wallpaper (call);
307 }
308
309 /**
310 * xdp_portal_set_wallpaper_finish:
311 * @portal: a [class@Portal]
312 * @result: a [iface@Gio.AsyncResult]
313 * @error: return location for an error
314 *
315 * Finishes the open-uri request, and returns
316 * the result in the form of a boolean.
317 *
318 * Returns: `TRUE` if the call succeeded
319 */
320 gboolean
xdp_portal_set_wallpaper_finish(XdpPortal * portal,GAsyncResult * result,GError ** error)321 xdp_portal_set_wallpaper_finish (XdpPortal *portal,
322 GAsyncResult *result,
323 GError **error)
324 {
325 g_return_val_if_fail (XDP_IS_PORTAL (portal), FALSE);
326 g_return_val_if_fail (g_task_is_valid (result, portal), FALSE);
327 g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == xdp_portal_set_wallpaper, FALSE);
328
329 return g_task_propagate_boolean (G_TASK (result), error);
330 }
331