1 /*
2  * Copyright (C) 2018, 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 "screenshot.h"
23 #include "portal-private.h"
24 
25 typedef struct {
26   XdpPortal *portal;
27   XdpParent *parent;
28   char *parent_handle;
29   gboolean color;
30   gboolean interactive;
31   guint signal_id;
32   GTask *task;
33   char *request_path;
34   guint cancelled_id;
35 } ScreenshotCall;
36 
37 static void
screenshot_call_free(ScreenshotCall * call)38 screenshot_call_free (ScreenshotCall *call)
39 {
40   if (call->parent)
41     {
42       call->parent->parent_unexport (call->parent);
43       xdp_parent_free (call->parent);
44     }
45   g_free (call->parent_handle);
46 
47   if (call->signal_id)
48     g_dbus_connection_signal_unsubscribe (call->portal->bus, call->signal_id);
49 
50   if (call->cancelled_id)
51     g_signal_handler_disconnect (g_task_get_cancellable (call->task), call->cancelled_id);
52 
53   g_free (call->request_path);
54 
55   g_object_unref (call->portal);
56   g_object_unref (call->task);
57 
58   g_free (call);
59 }
60 
61 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)62 response_received (GDBusConnection *bus,
63                    const char *sender_name,
64                    const char *object_path,
65                    const char *interface_name,
66                    const char *signal_name,
67                    GVariant *parameters,
68                    gpointer data)
69 {
70   ScreenshotCall *call = data;
71   guint32 response;
72   g_autoptr(GVariant) ret = NULL;
73 
74   if (call->cancelled_id)
75     {
76       g_signal_handler_disconnect (g_task_get_cancellable (call->task), call->cancelled_id);
77       call->cancelled_id = 0;
78     }
79 
80   g_variant_get (parameters, "(u@a{sv})", &response, &ret);
81 
82   if (response == 0)
83     {
84       if (call->color)
85         {
86           g_autoptr(GVariant) color = NULL;
87           g_variant_lookup (ret, "color", "@(ddd)", &color);
88           if (color)
89             g_task_return_pointer (call->task, g_variant_ref (color), (GDestroyNotify) g_variant_unref);
90           else
91             g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_FAILED, "Color not received");
92         }
93       else
94         {
95           const char *uri;
96           g_variant_lookup (ret, "uri", "&s", &uri);
97           if (uri)
98             g_task_return_pointer (call->task, g_strdup (uri), g_free);
99           else
100             g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_FAILED, "Screenshot not received");
101        }
102     }
103   else if (response == 1)
104     g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Screenshot canceled");
105   else
106     g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_FAILED, "Screenshot failed");
107 
108   screenshot_call_free (call);
109 }
110 
111 static void take_screenshot (ScreenshotCall *call);
112 
113 static void
parent_exported(XdpParent * parent,const char * handle,gpointer data)114 parent_exported (XdpParent *parent,
115                  const char *handle,
116                  gpointer data)
117 {
118   ScreenshotCall *call = data;
119   call->parent_handle = g_strdup (handle);
120   take_screenshot (call);
121 }
122 
123 static void
cancelled_cb(GCancellable * cancellable,gpointer data)124 cancelled_cb (GCancellable *cancellable,
125               gpointer data)
126 {
127   ScreenshotCall *call = data;
128 
129   g_dbus_connection_call (call->portal->bus,
130                           PORTAL_BUS_NAME,
131                           call->request_path,
132                           REQUEST_INTERFACE,
133                           "Close",
134                           NULL,
135                           NULL,
136                           G_DBUS_CALL_FLAGS_NONE,
137                           -1,
138                           NULL, NULL, NULL);
139 
140   g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Screenshot portal call canceled by caller");
141 
142   screenshot_call_free (call);
143 }
144 
145 static void
call_returned(GObject * object,GAsyncResult * result,gpointer data)146 call_returned (GObject *object,
147                GAsyncResult *result,
148                gpointer data)
149 {
150   ScreenshotCall *call = data;
151   GError *error = NULL;
152   g_autoptr(GVariant) ret = NULL;
153 
154   ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
155   if (error)
156     {
157       g_task_return_error (call->task, error);
158       screenshot_call_free (call);
159     }
160 }
161 
162 static void
take_screenshot(ScreenshotCall * call)163 take_screenshot (ScreenshotCall *call)
164 {
165   GVariantBuilder options;
166   g_autofree char *token = NULL;
167   GCancellable *cancellable;
168 
169   if (call->parent_handle == NULL)
170     {
171       call->parent->parent_export (call->parent, parent_exported, call);
172       return;
173     }
174 
175   token = g_strdup_printf ("portal%d", g_random_int_range (0, G_MAXINT));
176   call->request_path = g_strconcat (REQUEST_PATH_PREFIX, call->portal->sender, "/", token, NULL);
177   call->signal_id = g_dbus_connection_signal_subscribe (call->portal->bus,
178                                                         PORTAL_BUS_NAME,
179                                                         REQUEST_INTERFACE,
180                                                         "Response",
181                                                         call->request_path,
182                                                         NULL,
183                                                         G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
184                                                         response_received,
185                                                         call,
186                                                         NULL);
187 
188   cancellable = g_task_get_cancellable (call->task);
189   if (cancellable)
190     call->cancelled_id = g_signal_connect (cancellable, "cancelled", G_CALLBACK (cancelled_cb), call);
191 
192   g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
193   g_variant_builder_add (&options, "{sv}", "handle_token", g_variant_new_string (token));
194   if (!call->color)
195     g_variant_builder_add (&options, "{sv}", "interactive", g_variant_new_boolean (call->interactive));
196 
197   g_dbus_connection_call (call->portal->bus,
198                           PORTAL_BUS_NAME,
199                           PORTAL_OBJECT_PATH,
200                           "org.freedesktop.portal.Screenshot",
201                           call->color ? "PickColor" : "Screenshot",
202                           g_variant_new ("(sa{sv})", call->parent_handle, &options),
203                           NULL,
204                           G_DBUS_CALL_FLAGS_NONE,
205                           -1,
206                           NULL,
207                           call_returned,
208                           call);
209 }
210 
211 /**
212  * xdp_portal_take_screenshot:
213  * @portal: a [class@Portal]
214  * @parent: (nullable): parent window information
215  * @flags: options for this call
216  * @cancellable: (nullable): optional [class@Gio.Cancellable]
217  * @callback: (scope async): a callback to call when the request is done
218  * @data: (closure): data to pass to @callback
219  *
220  * Takes a screenshot.
221  *
222  * When the request is done, @callback will be called. You can then
223  * call [method@Portal.take_screenshot_finish] to get the results.
224  */
225 void
xdp_portal_take_screenshot(XdpPortal * portal,XdpParent * parent,XdpScreenshotFlags flags,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer data)226 xdp_portal_take_screenshot (XdpPortal *portal,
227                             XdpParent *parent,
228                             XdpScreenshotFlags flags,
229                             GCancellable *cancellable,
230                             GAsyncReadyCallback  callback,
231                             gpointer data)
232 {
233   ScreenshotCall *call;
234 
235   g_return_if_fail (XDP_IS_PORTAL (portal));
236   g_return_if_fail ((flags & ~(XDP_SCREENSHOT_FLAG_INTERACTIVE)) == 0);
237 
238   call = g_new0 (ScreenshotCall, 1);
239   call->color = FALSE;
240   call->portal = g_object_ref (portal);
241   if (parent)
242     call->parent = xdp_parent_copy (parent);
243   else
244     call->parent_handle = g_strdup ("");
245   call->interactive = (flags & XDP_SCREENSHOT_FLAG_INTERACTIVE) != 0;
246   call->task = g_task_new (portal, cancellable, callback, data);
247   g_task_set_source_tag (call->task, xdp_portal_take_screenshot);
248 
249   take_screenshot (call);
250 }
251 
252 /**
253  * xdp_portal_take_screenshot_finish:
254  * @portal: a [class@Portal]
255  * @result: a [iface@Gio.AsyncResult]
256  * @error: return location for an error
257  *
258  * Finishes a screenshot request, and returns
259  * the result in the form of a URI pointing to an image file.
260  *
261  * Returns: (transfer full) (nullable): URI pointing to an image file
262  */
263 char *
xdp_portal_take_screenshot_finish(XdpPortal * portal,GAsyncResult * result,GError ** error)264 xdp_portal_take_screenshot_finish (XdpPortal *portal,
265                                    GAsyncResult *result,
266                                    GError **error)
267 {
268   g_return_val_if_fail (XDP_IS_PORTAL (portal), NULL);
269   g_return_val_if_fail (g_task_is_valid (result, portal), NULL);
270   g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == xdp_portal_take_screenshot, NULL);
271 
272   return g_task_propagate_pointer (G_TASK (result), error);
273 }
274 
275 /**
276  * xdp_portal_pick_color:
277  * @portal: a [class@Portal]
278  * @parent: (nullable): parent window information
279  * @cancellable: (nullable): optional [class@Gio.Cancellable]
280  * @callback: (scope async): a callback to call when the request is done
281  * @data: (closure): data to pass to @callback
282  *
283  * Lets the user pick a color from the screen.
284  *
285  * When the request is done, @callback will be called. You can then
286  * call [method@Portal.pick_color_finish] to get the results.
287  */
288 void
xdp_portal_pick_color(XdpPortal * portal,XdpParent * parent,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer data)289 xdp_portal_pick_color (XdpPortal *portal,
290                        XdpParent *parent,
291                        GCancellable *cancellable,
292                        GAsyncReadyCallback  callback,
293                        gpointer data)
294 {
295   ScreenshotCall *call;
296 
297   g_return_if_fail (XDP_IS_PORTAL (portal));
298 
299   call = g_new0 (ScreenshotCall, 1);
300   call->color = TRUE;
301   call->portal = g_object_ref (portal);
302   if (parent)
303     call->parent = xdp_parent_copy (parent);
304   else
305     call->parent_handle = g_strdup ("");
306   call->task = g_task_new (portal, cancellable, callback, data);
307   g_task_set_source_tag (call->task, xdp_portal_pick_color);
308 
309   take_screenshot (call);
310 }
311 
312 /**
313  * xdp_portal_pick_color_finish:
314  * @portal: a [class@Portal]
315  * @result: a [iface@Gio.AsyncResult]
316  * @error: return location for an error
317  *
318  * Finishes a pick-color request, and returns
319  * the result in the form of a GVariant of the form (ddd), containing
320  * red, green and blue components in the range [0,1].
321  *
322  * Returns: (transfer full): GVariant containing the color
323  */
324 GVariant *
xdp_portal_pick_color_finish(XdpPortal * portal,GAsyncResult * result,GError ** error)325 xdp_portal_pick_color_finish (XdpPortal *portal,
326                               GAsyncResult *result,
327                               GError **error)
328 {
329   GVariant *ret;
330 
331   g_return_val_if_fail (XDP_IS_PORTAL (portal), NULL);
332   g_return_val_if_fail (g_task_is_valid (result, portal), NULL);
333   g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == xdp_portal_pick_color, NULL);
334 
335   ret = (GVariant *) g_task_propagate_pointer (G_TASK (result), error);
336   return ret ? g_variant_ref (ret) : NULL;
337 }
338