1/* GTK - The GIMP Toolkit
2 * gtkprintoperation-portal.c: Print Operation Details for sandboxed apps
3 * Copyright (C) 2016, Red Hat, Inc.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include "config.h"
20
21#include <string.h>
22
23#include <sys/types.h>
24#include <sys/stat.h>
25#include <fcntl.h>
26
27#include <cairo-pdf.h>
28#include <cairo-ps.h>
29
30#include <gio/gunixfdlist.h>
31
32#include "gtkprintoperation-private.h"
33#include "gtkprintoperation-portal.h"
34#include "gtkprintsettings.h"
35#include "gtkpagesetup.h"
36#include "gtkprintbackendprivate.h"
37#include "gtkshow.h"
38#include "gtkintl.h"
39#include "gtkwindowprivate.h"
40#include "gtkprivate.h"
41
42
43typedef struct {
44 GtkPrintOperation *op;
45 GDBusProxy *proxy;
46 guint response_signal_id;
47 gboolean do_print;
48 GtkPrintOperationResult result;
49 GtkPrintOperationPrintFunc print_cb;
50 GtkWindow *parent;
51 GMainLoop *loop;
52 guint32 token;
53 GDestroyNotify destroy;
54 GVariant *settings;
55 GVariant *setup;
56 GVariant *options;
57 char *prepare_print_handle;
58} PortalData;
59
60static void
61portal_data_free (gpointer data)
62{
63 PortalData *portal = data;
64
65 if (portal->parent)
66 gtk_window_unexport_handle (window: portal->parent);
67 g_object_unref (object: portal->op);
68 g_object_unref (object: portal->proxy);
69 if (portal->loop)
70 g_main_loop_unref (loop: portal->loop);
71 if (portal->settings)
72 g_variant_unref (value: portal->settings);
73 if (portal->setup)
74 g_variant_unref (value: portal->setup);
75 if (portal->options)
76 g_variant_unref (value: portal->options);
77 g_free (mem: portal->prepare_print_handle);
78 g_free (mem: portal);
79}
80
81typedef struct {
82 GDBusProxy *proxy;
83 GtkPrintJob *job;
84 guint32 token;
85 cairo_surface_t *surface;
86 GMainLoop *loop;
87 gboolean file_written;
88} GtkPrintOperationPortal;
89
90static void
91op_portal_free (GtkPrintOperationPortal *op_portal)
92{
93 g_clear_object (&op_portal->proxy);
94 g_clear_object (&op_portal->job);
95 if (op_portal->loop)
96 g_main_loop_unref (loop: op_portal->loop);
97 g_free (mem: op_portal);
98}
99
100static void
101portal_start_page (GtkPrintOperation *op,
102 GtkPrintContext *print_context,
103 GtkPageSetup *page_setup)
104{
105 GtkPrintOperationPortal *op_portal = op->priv->platform_data;
106 GtkPaperSize *paper_size;
107 cairo_surface_type_t type;
108 double w, h;
109
110 paper_size = gtk_page_setup_get_paper_size (setup: page_setup);
111
112 w = gtk_paper_size_get_width (size: paper_size, unit: GTK_UNIT_POINTS);
113 h = gtk_paper_size_get_height (size: paper_size, unit: GTK_UNIT_POINTS);
114
115 type = cairo_surface_get_type (surface: op_portal->surface);
116
117 if ((op->priv->manual_number_up < 2) ||
118 (op->priv->page_position % op->priv->manual_number_up == 0))
119 {
120 if (type == CAIRO_SURFACE_TYPE_PS)
121 {
122 cairo_ps_surface_set_size (surface: op_portal->surface, width_in_points: w, height_in_points: h);
123 cairo_ps_surface_dsc_begin_page_setup (surface: op_portal->surface);
124 switch (gtk_page_setup_get_orientation (setup: page_setup))
125 {
126 case GTK_PAGE_ORIENTATION_PORTRAIT:
127 case GTK_PAGE_ORIENTATION_REVERSE_PORTRAIT:
128 cairo_ps_surface_dsc_comment (surface: op_portal->surface, comment: "%%PageOrientation: Portrait");
129 break;
130
131 case GTK_PAGE_ORIENTATION_LANDSCAPE:
132 case GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE:
133 cairo_ps_surface_dsc_comment (surface: op_portal->surface, comment: "%%PageOrientation: Landscape");
134 break;
135
136 default:
137 break;
138 }
139 }
140 else if (type == CAIRO_SURFACE_TYPE_PDF)
141 {
142 if (!op->priv->manual_orientation)
143 {
144 w = gtk_page_setup_get_paper_width (setup: page_setup, unit: GTK_UNIT_POINTS);
145 h = gtk_page_setup_get_paper_height (setup: page_setup, unit: GTK_UNIT_POINTS);
146 }
147 cairo_pdf_surface_set_size (surface: op_portal->surface, width_in_points: w, height_in_points: h);
148 }
149 }
150}
151
152static void
153portal_end_page (GtkPrintOperation *op,
154 GtkPrintContext *print_context)
155{
156 cairo_t *cr;
157
158 cr = gtk_print_context_get_cairo_context (context: print_context);
159
160 if ((op->priv->manual_number_up < 2) ||
161 ((op->priv->page_position + 1) % op->priv->manual_number_up == 0) ||
162 (op->priv->page_position == op->priv->nr_of_pages_to_print - 1))
163 cairo_show_page (cr);
164}
165
166static void
167print_file_done (GObject *source,
168 GAsyncResult *result,
169 gpointer data)
170{
171 GtkPrintOperation *op = data;
172 GtkPrintOperationPortal *op_portal = op->priv->platform_data;
173 GError *error = NULL;
174 GVariant *ret;
175
176 ret = g_dbus_proxy_call_finish (proxy: op_portal->proxy,
177 res: result,
178 error: &error);
179 if (ret == NULL)
180 {
181 if (op->priv->error == NULL)
182 op->priv->error = g_error_copy (error);
183 g_warning ("Print file failed: %s", error->message);
184 g_error_free (error);
185 }
186 else
187 g_variant_unref (value: ret);
188
189 if (op_portal->loop)
190 g_main_loop_quit (loop: op_portal->loop);
191
192 g_object_unref (object: op);
193}
194
195static void
196portal_job_complete (GtkPrintJob *job,
197 gpointer data,
198 const GError *error)
199{
200 GtkPrintOperation *op = data;
201 GtkPrintOperationPortal *op_portal = op->priv->platform_data;
202 GtkPrintSettings *settings;
203 const char *uri;
204 char *filename;
205 int fd, idx;
206 GVariantBuilder opt_builder;
207 GUnixFDList *fd_list;
208
209 if (error != NULL && op->priv->error == NULL)
210 {
211 g_warning ("Print job failed: %s", error->message);
212 op->priv->error = g_error_copy (error);
213 return;
214 }
215
216 op_portal->file_written = TRUE;
217
218 settings = gtk_print_job_get_settings (job);
219 uri = gtk_print_settings_get (settings, GTK_PRINT_SETTINGS_OUTPUT_URI);
220 filename = g_filename_from_uri (uri, NULL, NULL);
221
222 fd = open (file: filename, O_RDONLY|O_CLOEXEC);
223 fd_list = g_unix_fd_list_new ();
224 idx = g_unix_fd_list_append (list: fd_list, fd, NULL);
225 close (fd: fd);
226
227 g_free (mem: filename);
228
229 g_variant_builder_init (builder: &opt_builder, G_VARIANT_TYPE_VARDICT);
230 g_variant_builder_add (builder: &opt_builder, format_string: "{sv}", "token", g_variant_new_uint32 (value: op_portal->token));
231
232 g_dbus_proxy_call_with_unix_fd_list (proxy: op_portal->proxy,
233 method_name: "Print",
234 parameters: g_variant_new (format_string: "(ssh@a{sv})",
235 "", /* window */
236 _("Print"), /* title */
237 idx,
238 g_variant_builder_end (builder: &opt_builder)),
239 flags: G_DBUS_CALL_FLAGS_NONE,
240 timeout_msec: -1,
241 fd_list,
242 NULL,
243 callback: print_file_done,
244 user_data: op);
245 g_object_unref (object: fd_list);
246}
247
248static void
249portal_end_run (GtkPrintOperation *op,
250 gboolean wait,
251 gboolean cancelled)
252{
253 GtkPrintOperationPortal *op_portal = op->priv->platform_data;
254
255 cairo_surface_finish (surface: op_portal->surface);
256
257 if (cancelled)
258 return;
259
260 if (wait)
261 op_portal->loop = g_main_loop_new (NULL, FALSE);
262
263 /* TODO: Check for error */
264 if (op_portal->job != NULL)
265 {
266 g_object_ref (op);
267 gtk_print_job_send (job: op_portal->job, callback: portal_job_complete, user_data: op, NULL);
268 }
269
270 if (wait)
271 {
272 g_object_ref (op);
273 if (!op_portal->file_written)
274 g_main_loop_run (loop: op_portal->loop);
275 g_object_unref (object: op);
276 }
277}
278
279static void
280finish_print (PortalData *portal,
281 GtkPrinter *printer,
282 GtkPageSetup *page_setup,
283 GtkPrintSettings *settings)
284{
285 GtkPrintOperation *op = portal->op;
286 GtkPrintOperationPrivate *priv = op->priv;
287 GtkPrintJob *job;
288 GtkPrintOperationPortal *op_portal;
289 cairo_t *cr;
290
291 if (portal->do_print)
292 {
293 gtk_print_operation_set_print_settings (op, print_settings: settings);
294 priv->print_context = _gtk_print_context_new (op);
295
296 _gtk_print_context_set_hard_margins (context: priv->print_context, top: 0, bottom: 0, left: 0, right: 0);
297
298 gtk_print_operation_set_default_page_setup (op, default_page_setup: page_setup);
299 _gtk_print_context_set_page_setup (context: priv->print_context, page_setup);
300
301 op_portal = g_new0 (GtkPrintOperationPortal, 1);
302 priv->platform_data = op_portal;
303 priv->free_platform_data = (GDestroyNotify) op_portal_free;
304
305 priv->start_page = portal_start_page;
306 priv->end_page = portal_end_page;
307 priv->end_run = portal_end_run;
308
309 job = gtk_print_job_new (title: priv->job_name, printer, settings, page_setup);
310 op_portal->job = job;
311
312 op_portal->proxy = g_object_ref (portal->proxy);
313 op_portal->token = portal->token;
314
315 op_portal->surface = gtk_print_job_get_surface (job, error: &priv->error);
316 if (op_portal->surface == NULL)
317 {
318 portal->result = GTK_PRINT_OPERATION_RESULT_ERROR;
319 portal->do_print = FALSE;
320 goto out;
321 }
322
323 cr = cairo_create (target: op_portal->surface);
324 gtk_print_context_set_cairo_context (context: priv->print_context, cr, dpi_x: 72, dpi_y: 72);
325 cairo_destroy (cr);
326
327 priv->print_pages = gtk_print_job_get_pages (job);
328 priv->page_ranges = gtk_print_job_get_page_ranges (job, n_ranges: &priv->num_page_ranges);
329 priv->manual_num_copies = gtk_print_job_get_num_copies (job);
330 priv->manual_collation = gtk_print_job_get_collate (job);
331 priv->manual_reverse = gtk_print_job_get_reverse (job);
332 priv->manual_page_set = gtk_print_job_get_page_set (job);
333 priv->manual_scale = gtk_print_job_get_scale (job);
334 priv->manual_orientation = gtk_print_job_get_rotate (job);
335 priv->manual_number_up = gtk_print_job_get_n_up (job);
336 priv->manual_number_up_layout = gtk_print_job_get_n_up_layout (job);
337 }
338
339out:
340 if (portal->print_cb)
341 portal->print_cb (op, portal->parent, portal->do_print, portal->result);
342
343 if (portal->destroy)
344 portal->destroy (portal);
345}
346
347static GtkPrinter *
348find_file_printer (void)
349{
350 GList *backends, *l, *printers;
351 GtkPrinter *printer;
352
353 printer = NULL;
354
355 backends = gtk_print_backend_load_modules ();
356 for (l = backends; l; l = l->next)
357 {
358 GtkPrintBackend *backend = l->data;
359 if (strcmp (G_OBJECT_TYPE_NAME (backend), s2: "GtkPrintBackendFile") == 0)
360 {
361 printers = gtk_print_backend_get_printer_list (print_backend: backend);
362 printer = printers->data;
363 g_list_free (list: printers);
364 break;
365 }
366 }
367 g_list_free (list: backends);
368
369 return printer;
370}
371
372static void
373prepare_print_response (GDBusConnection *connection,
374 const char *sender_name,
375 const char *object_path,
376 const char *interface_name,
377 const char *signal_name,
378 GVariant *parameters,
379 gpointer data)
380{
381 PortalData *portal = data;
382 guint32 response;
383 GVariant *options = NULL;
384
385 if (portal->response_signal_id != 0)
386 {
387 g_dbus_connection_signal_unsubscribe (connection,
388 subscription_id: portal->response_signal_id);
389 portal->response_signal_id = 0;
390 }
391
392 g_variant_get (value: parameters, format_string: "(u@a{sv})", &response, &options);
393
394 portal->do_print = (response == 0);
395
396 if (portal->do_print)
397 {
398 GVariant *v;
399 GtkPrintSettings *settings;
400 GtkPageSetup *page_setup;
401 GtkPrinter *printer;
402 char *filename;
403 char *uri;
404 int fd;
405
406 portal->result = GTK_PRINT_OPERATION_RESULT_APPLY;
407
408 v = g_variant_lookup_value (dictionary: options, key: "settings", G_VARIANT_TYPE_VARDICT);
409 settings = gtk_print_settings_new_from_gvariant (variant: v);
410 g_variant_unref (value: v);
411
412 v = g_variant_lookup_value (dictionary: options, key: "page-setup", G_VARIANT_TYPE_VARDICT);
413 page_setup = gtk_page_setup_new_from_gvariant (variant: v);
414 g_variant_unref (value: v);
415
416 g_variant_lookup (dictionary: options, key: "token", format_string: "u", &portal->token);
417
418 printer = find_file_printer ();
419
420 fd = g_file_open_tmp (tmpl: "gtkprintXXXXXX", name_used: &filename, NULL);
421 uri = g_filename_to_uri (filename, NULL, NULL);
422 gtk_print_settings_set (settings, GTK_PRINT_SETTINGS_OUTPUT_URI, value: uri);
423 g_free (mem: uri);
424 close (fd: fd);
425
426 finish_print (portal, printer, page_setup, settings);
427 g_free (mem: filename);
428 }
429 else
430 {
431 portal->result = GTK_PRINT_OPERATION_RESULT_CANCEL;
432
433 if (portal->print_cb)
434 portal->print_cb (portal->op, portal->parent, portal->do_print, portal->result);
435
436 if (portal->destroy)
437 portal->destroy (portal);
438 }
439
440 if (options)
441 g_variant_unref (value: options);
442
443 if (portal->loop)
444 g_main_loop_quit (loop: portal->loop);
445}
446
447static void
448prepare_print_called (GObject *source,
449 GAsyncResult *result,
450 gpointer data)
451{
452 PortalData *portal = data;
453 GError *error = NULL;
454 const char *handle = NULL;
455 GVariant *ret;
456
457 ret = g_dbus_proxy_call_finish (proxy: portal->proxy, res: result, error: &error);
458 if (ret == NULL)
459 {
460 if (portal->op->priv->error == NULL)
461 portal->op->priv->error = g_error_copy (error);
462 g_error_free (error);
463 if (portal->loop)
464 g_main_loop_quit (loop: portal->loop);
465 return;
466 }
467 else
468 g_variant_get (value: ret, format_string: "(&o)", &handle);
469
470 if (strcmp (s1: portal->prepare_print_handle, s2: handle) != 0)
471 {
472 g_free (mem: portal->prepare_print_handle);
473 portal->prepare_print_handle = g_strdup (str: handle);
474 g_dbus_connection_signal_unsubscribe (connection: g_dbus_proxy_get_connection (G_DBUS_PROXY (portal->proxy)),
475 subscription_id: portal->response_signal_id);
476 portal->response_signal_id =
477 g_dbus_connection_signal_subscribe (connection: g_dbus_proxy_get_connection (G_DBUS_PROXY (portal->proxy)),
478 PORTAL_BUS_NAME,
479 PORTAL_REQUEST_INTERFACE,
480 member: "Response",
481 object_path: handle,
482 NULL,
483 flags: G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
484 callback: prepare_print_response,
485 user_data: portal, NULL);
486 }
487
488 g_variant_unref (value: ret);
489}
490
491static PortalData *
492create_portal_data (GtkPrintOperation *op,
493 GtkWindow *parent,
494 GtkPrintOperationPrintFunc print_cb)
495{
496 GDBusProxy *proxy;
497 PortalData *portal;
498 guint signal_id;
499 GError *error = NULL;
500
501 signal_id = g_signal_lookup (name: "create-custom-widget", GTK_TYPE_PRINT_OPERATION);
502 if (g_signal_has_handler_pending (instance: op, signal_id, detail: 0, TRUE))
503 g_warning ("GtkPrintOperation::create-custom-widget not supported with portal");
504
505 proxy = g_dbus_proxy_new_for_bus_sync (bus_type: G_BUS_TYPE_SESSION,
506 flags: G_DBUS_PROXY_FLAGS_NONE,
507 NULL,
508 PORTAL_BUS_NAME,
509 PORTAL_OBJECT_PATH,
510 PORTAL_PRINT_INTERFACE,
511 NULL,
512 error: &error);
513
514 if (proxy == NULL)
515 {
516 if (op->priv->error == NULL)
517 op->priv->error = g_error_copy (error);
518 g_error_free (error);
519 return NULL;
520 }
521
522 portal = g_new0 (PortalData, 1);
523 portal->proxy = proxy;
524 portal->op = g_object_ref (op);
525 portal->parent = parent;
526 portal->result = GTK_PRINT_OPERATION_RESULT_CANCEL;
527 portal->print_cb = print_cb;
528
529 if (print_cb) /* async case */
530 {
531 portal->loop = NULL;
532 portal->destroy = portal_data_free;
533 }
534 else
535 {
536 portal->loop = g_main_loop_new (NULL, FALSE);
537 portal->destroy = NULL;
538 }
539
540 return portal;
541}
542
543static void
544window_handle_exported (GtkWindow *window,
545 const char *handle_str,
546 gpointer user_data)
547{
548 PortalData *portal = user_data;
549
550 g_dbus_proxy_call (proxy: portal->proxy,
551 method_name: "PreparePrint",
552 parameters: g_variant_new (format_string: "(ss@a{sv}@a{sv}@a{sv})",
553 handle_str,
554 _("Print"), /* title */
555 portal->settings,
556 portal->setup,
557 portal->options),
558 flags: G_DBUS_CALL_FLAGS_NONE,
559 timeout_msec: -1,
560 NULL,
561 callback: prepare_print_called,
562 user_data: portal);
563}
564
565static void
566call_prepare_print (GtkPrintOperation *op,
567 PortalData *portal)
568{
569 GtkPrintOperationPrivate *priv = op->priv;
570 GVariantBuilder opt_builder;
571 char *token;
572
573 portal->prepare_print_handle =
574 gtk_get_portal_request_path (connection: g_dbus_proxy_get_connection (proxy: portal->proxy), token: &token);
575
576 portal->response_signal_id =
577 g_dbus_connection_signal_subscribe (connection: g_dbus_proxy_get_connection (G_DBUS_PROXY (portal->proxy)),
578 PORTAL_BUS_NAME,
579 PORTAL_REQUEST_INTERFACE,
580 member: "Response",
581 object_path: portal->prepare_print_handle,
582 NULL,
583 flags: G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
584 callback: prepare_print_response,
585 user_data: portal, NULL);
586
587 g_variant_builder_init (builder: &opt_builder, G_VARIANT_TYPE_VARDICT);
588 g_variant_builder_add (builder: &opt_builder, format_string: "{sv}", "handle_token", g_variant_new_string (string: token));
589 g_free (mem: token);
590 portal->options = g_variant_builder_end (builder: &opt_builder);
591
592 if (priv->print_settings)
593 portal->settings = gtk_print_settings_to_gvariant (settings: priv->print_settings);
594 else
595 {
596 GVariantBuilder builder;
597 g_variant_builder_init (builder: &builder, G_VARIANT_TYPE_VARDICT);
598 portal->settings = g_variant_builder_end (builder: &builder);
599 }
600
601 if (priv->default_page_setup)
602 portal->setup = gtk_page_setup_to_gvariant (setup: priv->default_page_setup);
603 else
604 {
605 GtkPageSetup *page_setup = gtk_page_setup_new ();
606 portal->setup = gtk_page_setup_to_gvariant (setup: page_setup);
607 g_object_unref (object: page_setup);
608 }
609
610 g_variant_ref_sink (value: portal->options);
611 g_variant_ref_sink (value: portal->settings);
612 g_variant_ref_sink (value: portal->setup);
613
614 if (portal->parent != NULL &&
615 gtk_widget_is_visible (GTK_WIDGET (portal->parent)) &&
616 gtk_window_export_handle (window: portal->parent, callback: window_handle_exported, user_data: portal))
617 return;
618
619 g_dbus_proxy_call (proxy: portal->proxy,
620 method_name: "PreparePrint",
621 parameters: g_variant_new (format_string: "(ss@a{sv}@a{sv}@a{sv})",
622 "",
623 _("Print"), /* title */
624 portal->settings,
625 portal->setup,
626 portal->options),
627 flags: G_DBUS_CALL_FLAGS_NONE,
628 timeout_msec: -1,
629 NULL,
630 callback: prepare_print_called,
631 user_data: portal);
632}
633
634GtkPrintOperationResult
635gtk_print_operation_portal_run_dialog (GtkPrintOperation *op,
636 gboolean show_dialog,
637 GtkWindow *parent,
638 gboolean *do_print)
639{
640 PortalData *portal;
641 GtkPrintOperationResult result;
642
643 portal = create_portal_data (op, parent, NULL);
644 if (portal == NULL)
645 return GTK_PRINT_OPERATION_RESULT_ERROR;
646
647 call_prepare_print (op, portal);
648
649 g_main_loop_run (loop: portal->loop);
650
651 *do_print = portal->do_print;
652 result = portal->result;
653
654 portal_data_free (data: portal);
655
656 return result;
657}
658
659void
660gtk_print_operation_portal_run_dialog_async (GtkPrintOperation *op,
661 gboolean show_dialog,
662 GtkWindow *parent,
663 GtkPrintOperationPrintFunc print_cb)
664{
665 PortalData *portal;
666
667 portal = create_portal_data (op, parent, print_cb);
668 if (portal == NULL)
669 return;
670
671 call_prepare_print (op, portal);
672}
673
674void
675gtk_print_operation_portal_launch_preview (GtkPrintOperation *op,
676 cairo_surface_t *surface,
677 GtkWindow *parent,
678 const char *filename)
679{
680 char *uri;
681
682 uri = g_filename_to_uri (filename, NULL, NULL);
683 gtk_show_uri (parent, uri, GDK_CURRENT_TIME);
684 g_free (mem: uri);
685}
686

source code of gtk/gtk/gtkprintoperation-portal.c