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