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