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 "print.h"
23 #include "portal-private.h"
24
25 #define GNU_SOURCE 1
26
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <fcntl.h>
30
31 #include <glib/gstdio.h>
32 #include <gio/gunixfdlist.h>
33
34 #ifndef O_PATH
35 #define O_PATH 0
36 #endif
37
38 typedef struct {
39 XdpPortal *portal;
40 XdpParent *parent;
41 char *parent_handle;
42 char *title;
43 gboolean is_prepare;
44 GVariant *settings;
45 GVariant *page_setup;
46 guint token;
47 char *file;
48 guint signal_id;
49 GTask *task;
50 char *request_path;
51 guint cancelled_id;
52 } PrintCall;
53
54 static void
print_call_free(PrintCall * call)55 print_call_free (PrintCall *call)
56 {
57 if (call->parent)
58 {
59 call->parent->parent_unexport (call->parent);
60 xdp_parent_free (call->parent);
61 }
62 g_free (call->parent_handle);
63
64 if (call->signal_id)
65 g_dbus_connection_signal_unsubscribe (call->portal->bus, call->signal_id);
66
67 if (call->cancelled_id)
68 g_signal_handler_disconnect (g_task_get_cancellable (call->task), call->cancelled_id);
69
70 g_free (call->request_path);
71
72 g_object_unref (call->portal);
73 g_object_unref (call->task);
74
75 g_free (call->title);
76 if (call->settings)
77 g_variant_unref (call->settings);
78 if (call->page_setup)
79 g_variant_unref (call->page_setup);
80 g_free (call->file);
81
82 g_free (call);
83 }
84
85 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)86 response_received (GDBusConnection *bus,
87 const char *sender_name,
88 const char *object_path,
89 const char *interface_name,
90 const char *signal_name,
91 GVariant *parameters,
92 gpointer data)
93 {
94 PrintCall *call = data;
95 guint32 response;
96 g_autoptr(GVariant) ret = NULL;
97
98 if (call->cancelled_id)
99 {
100 g_signal_handler_disconnect (g_task_get_cancellable (call->task), call->cancelled_id);
101 call->cancelled_id = 0;
102 }
103
104 g_variant_get (parameters, "(u@a{sv})", &response, &ret);
105
106 if (response == 0)
107 {
108 if (call->is_prepare)
109 g_task_return_pointer (call->task, g_variant_ref (ret), (GDestroyNotify)g_variant_unref);
110 else
111 g_task_return_boolean (call->task, TRUE);
112 }
113 else if (response == 1)
114 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Print canceled");
115 else
116 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_FAILED, "Print failed");
117
118 print_call_free (call);
119 }
120
121 static void do_print (PrintCall *call);
122
123 static void
parent_exported(XdpParent * parent,const char * handle,gpointer data)124 parent_exported (XdpParent *parent,
125 const char *handle,
126 gpointer data)
127 {
128 PrintCall *call = data;
129 call->parent_handle = g_strdup (handle);
130 do_print (call);
131 }
132
133 static void
cancelled_cb(GCancellable * cancellable,gpointer data)134 cancelled_cb (GCancellable *cancellable,
135 gpointer data)
136 {
137 PrintCall *call = data;
138
139 g_dbus_connection_call (call->portal->bus,
140 PORTAL_BUS_NAME,
141 call->request_path,
142 REQUEST_INTERFACE,
143 "Close",
144 NULL,
145 NULL,
146 G_DBUS_CALL_FLAGS_NONE,
147 -1,
148 NULL, NULL, NULL);
149
150 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Print call canceled by caller");
151
152 print_call_free (call);
153 }
154
155 static void
call_returned(GObject * object,GAsyncResult * result,gpointer data)156 call_returned (GObject *object,
157 GAsyncResult *result,
158 gpointer data)
159 {
160 PrintCall *call = data;
161 GError *error = NULL;
162 g_autoptr(GVariant) ret = NULL;
163
164 if (call->is_prepare)
165 ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
166 else
167 ret = g_dbus_connection_call_with_unix_fd_list_finish (G_DBUS_CONNECTION (object), NULL, result, &error);
168 if (error)
169 {
170 g_task_return_error (call->task, error);
171 print_call_free (call);
172 }
173 }
174
175 static void
do_print(PrintCall * call)176 do_print (PrintCall *call)
177 {
178 GVariantBuilder options;
179 g_autofree char *token = NULL;
180 GCancellable *cancellable;
181
182 if (call->parent_handle == NULL)
183 {
184 call->parent->parent_export (call->parent, parent_exported, call);
185 return;
186 }
187
188 token = g_strdup_printf ("portal%d", g_random_int_range (0, G_MAXINT));
189 call->request_path = g_strconcat (REQUEST_PATH_PREFIX, call->portal->sender, "/", token, NULL);
190 call->signal_id = g_dbus_connection_signal_subscribe (call->portal->bus,
191 PORTAL_BUS_NAME,
192 REQUEST_INTERFACE,
193 "Response",
194 call->request_path,
195 NULL,
196 G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
197 response_received,
198 call,
199 NULL);
200
201 cancellable = g_task_get_cancellable (call->task);
202 if (cancellable)
203 call->cancelled_id = g_signal_connect (cancellable, "cancelled", G_CALLBACK (cancelled_cb), call);
204
205 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
206 g_variant_builder_add (&options, "{sv}", "handle_token", g_variant_new_string (token));
207 if (!call->is_prepare)
208 g_variant_builder_add (&options, "{sv}", "token", g_variant_new_uint32 (call->token));
209
210 if (call->is_prepare)
211 g_dbus_connection_call (call->portal->bus,
212 PORTAL_BUS_NAME,
213 PORTAL_OBJECT_PATH,
214 "org.freedesktop.portal.Print",
215 "PreparePrint",
216 g_variant_new ("(ss@a{sv}@a{sv}a{sv})",
217 call->parent_handle,
218 call->title,
219 call->settings ? call->settings : g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0),
220 call->page_setup ? call->page_setup : g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0),
221 &options),
222 NULL,
223 G_DBUS_CALL_FLAGS_NONE,
224 -1,
225 NULL,
226 call_returned,
227 call);
228 else
229 {
230 g_autoptr(GUnixFDList) fd_list = NULL;
231 int fd, fd_in;
232
233 fd = g_open (call->file, O_PATH | O_CLOEXEC);
234 if (fd == -1)
235 {
236 g_warning ("Failed to open '%s'", call->file);
237 return;
238 }
239
240 fd_list = g_unix_fd_list_new_from_array (&fd, 1);
241 fd = -1;
242 fd_in = 0;
243
244 g_dbus_connection_call_with_unix_fd_list (call->portal->bus,
245 PORTAL_BUS_NAME,
246 PORTAL_OBJECT_PATH,
247 "org.freedesktop.portal.Print",
248 "Print",
249 g_variant_new ("(ssha{sv})",
250 call->parent_handle,
251 call->title,
252 fd_in,
253 &options),
254 NULL,
255 G_DBUS_CALL_FLAGS_NONE,
256 -1,
257 fd_list,
258 cancellable,
259 call_returned,
260 call);
261 }
262 }
263
264 /**
265 * xdp_portal_prepare_print:
266 * @portal: a [class@Portal]
267 * @parent: (nullable): parent window information
268 * @title: tile for the print dialog
269 * @settings: (nullable): Serialized print settings
270 * @page_setup: (nullable): Serialized page setup
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 * Presents a print dialog to the user and returns print settings and page setup.
277 *
278 * When the request is done, @callback will be called. You can then
279 * call [method@Portal.prepare_print_finish] to get the results.
280 */
281 void
xdp_portal_prepare_print(XdpPortal * portal,XdpParent * parent,const char * title,GVariant * settings,GVariant * page_setup,XdpPrintFlags flags,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer data)282 xdp_portal_prepare_print (XdpPortal *portal,
283 XdpParent *parent,
284 const char *title,
285 GVariant *settings,
286 GVariant *page_setup,
287 XdpPrintFlags flags,
288 GCancellable *cancellable,
289 GAsyncReadyCallback callback,
290 gpointer data)
291 {
292 PrintCall *call;
293
294 g_return_if_fail (XDP_IS_PORTAL (portal));
295 g_return_if_fail (flags == XDP_PRINT_FLAG_NONE);
296
297 call = g_new0 (PrintCall, 1);
298 call->portal = g_object_ref (portal);
299 if (parent)
300 call->parent = xdp_parent_copy (parent);
301 else
302 call->parent_handle = g_strdup ("");
303 call->title = g_strdup (title);
304 call->is_prepare = TRUE;
305 call->settings = settings ? g_variant_ref (settings) : NULL;
306 call->page_setup = page_setup ? g_variant_ref (page_setup) : NULL;
307 call->task = g_task_new (portal, cancellable, callback, data);
308 g_task_set_source_tag (call->task, xdp_portal_prepare_print);
309
310 do_print (call);
311 }
312
313 /**
314 * xdp_portal_prepare_print_finish:
315 * @portal: a [class@Portal]
316 * @result: a [iface@Gio.AsyncResult]
317 * @error: return location for an error
318 *
319 * Finishes the prepare-print request, and returns [struct@GLib.Variant] dictionary
320 * with the following information:
321 *
322 * - settings `a{sv}`: print settings as set up by the user in the print dialog
323 * - page-setup `a{sv}: page setup as set up by the user in the print dialog
324 * - token u: a token that can by used in a [method@Portal.print_file] call to
325 * avoid the print dialog
326 *
327 * Returns: (transfer full): a [struct@GLib.Variant] dictionary with print information
328 */
329 GVariant *
xdp_portal_prepare_print_finish(XdpPortal * portal,GAsyncResult * result,GError ** error)330 xdp_portal_prepare_print_finish (XdpPortal *portal,
331 GAsyncResult *result,
332 GError **error)
333 {
334 g_return_val_if_fail (XDP_IS_PORTAL (portal), NULL);
335 g_return_val_if_fail (g_task_is_valid (result, portal), NULL);
336 g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == xdp_portal_prepare_print, NULL);
337
338 return g_task_propagate_pointer (G_TASK (result), error);
339 }
340
341 /**
342 * xdp_portal_print_file:
343 * @portal: a [class@Portal]
344 * @parent: (nullable): parent window information
345 * @title: tile for the print dialog
346 * @token: token that was returned by a previous [method@Portal.prepare_print] call, or 0
347 * @file: path of the document to print
348 * @flags: options for this call
349 * @cancellable: (nullable): optional [class@Gio.Cancellable]
350 * @callback: (scope async): a callback to call when the request is done
351 * @data: (closure): data to pass to @callback
352 *
353 * Prints a file.
354 *
355 * If a valid token is present in the @options, then this call will print
356 * with the settings from the Print call that the token refers to. If
357 * no token is present, then a print dialog will be presented to the user.
358 *
359 * When the request is done, @callback will be called. You can then
360 * call [method@Portal.print_file_finish] to get the results.
361 */
362 void
xdp_portal_print_file(XdpPortal * portal,XdpParent * parent,const char * title,guint token,const char * file,XdpPrintFlags flags,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer data)363 xdp_portal_print_file (XdpPortal *portal,
364 XdpParent *parent,
365 const char *title,
366 guint token,
367 const char *file,
368 XdpPrintFlags flags,
369 GCancellable *cancellable,
370 GAsyncReadyCallback callback,
371 gpointer data)
372 {
373 PrintCall *call;
374
375 g_return_if_fail (XDP_IS_PORTAL (portal));
376 g_return_if_fail (flags == XDP_PRINT_FLAG_NONE);
377
378 call = g_new0 (PrintCall, 1);
379 call->portal = g_object_ref (portal);
380 if (parent)
381 call->parent = xdp_parent_copy (parent);
382 else
383 call->parent_handle = g_strdup ("");
384 call->title = g_strdup (title);
385 call->is_prepare = FALSE;
386 call->token = token;
387 call->file = g_strdup (file);
388 call->task = g_task_new (portal, cancellable, callback, data);
389 g_task_set_source_tag (call->task, xdp_portal_print_file);
390
391 do_print (call);
392 }
393
394 /**
395 * xdp_portal_print_file_finish:
396 * @portal: a [class@Portal]
397 * @result: a [iface@Gio.AsyncResult]
398 * @error: return location for an error
399 *
400 * Finishes the print request.
401 *
402 * Returns: `TRUE` if the request was successful
403 */
404 gboolean
xdp_portal_print_file_finish(XdpPortal * portal,GAsyncResult * result,GError ** error)405 xdp_portal_print_file_finish (XdpPortal *portal,
406 GAsyncResult *result,
407 GError **error)
408 {
409 g_return_val_if_fail (XDP_IS_PORTAL (portal), FALSE);
410 g_return_val_if_fail (g_task_is_valid (result, portal), FALSE);
411 g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == xdp_portal_print_file, FALSE);
412
413 return g_task_propagate_boolean (G_TASK (result), error);
414 }
415