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