Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/widget/gtk/nsPrintDialogGTK.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* This Source Code Form is subject to the terms of the Mozilla Public
3
 * License, v. 2.0. If a copy of the MPL was not distributed with this
4
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6
#include <gtk/gtk.h>
7
#include <gtk/gtkunixprint.h>
8
#include <stdlib.h>
9
10
#include "mozilla/ArrayUtils.h"
11
12
#include "mozcontainer.h"
13
#include "nsIPrintSettings.h"
14
#include "nsIWidget.h"
15
#include "nsPrintDialogGTK.h"
16
#include "nsPrintSettingsGTK.h"
17
#include "nsString.h"
18
#include "nsReadableUtils.h"
19
#include "nsIFile.h"
20
#include "nsIStringBundle.h"
21
#include "nsIPrintSettingsService.h"
22
#include "nsIDOMWindow.h"
23
#include "nsPIDOMWindow.h"
24
#include "nsIBaseWindow.h"
25
#include "nsIDocShellTreeItem.h"
26
#include "nsIDocShell.h"
27
#include "nsIGIOService.h"
28
#include "WidgetUtils.h"
29
#include "nsIObserverService.h"
30
31
// for gdk_x11_window_get_xid
32
#include <gdk/gdkx.h>
33
#include <sys/types.h>
34
#include <sys/stat.h>
35
#include <fcntl.h>
36
#include <gio/gunixfdlist.h>
37
38
// for dlsym
39
#include <dlfcn.h>
40
#include "MainThreadUtils.h"
41
42
using namespace mozilla;
43
using namespace mozilla::widget;
44
45
static const char header_footer_tags[][4] =  {"", "&T", "&U", "&D", "&P", "&PT"};
46
47
0
#define CUSTOM_VALUE_INDEX gint(ArrayLength(header_footer_tags))
48
49
static GtkWindow *
50
get_gtk_window_for_nsiwidget(nsIWidget *widget)
51
0
{
52
0
  return GTK_WINDOW(widget->GetNativeData(NS_NATIVE_SHELLWIDGET));
53
0
}
54
55
static void
56
ShowCustomDialog(GtkComboBox *changed_box, gpointer user_data)
57
0
{
58
0
  if (gtk_combo_box_get_active(changed_box) != CUSTOM_VALUE_INDEX) {
59
0
    g_object_set_data(G_OBJECT(changed_box), "previous-active", GINT_TO_POINTER(gtk_combo_box_get_active(changed_box)));
60
0
    return;
61
0
  }
62
0
63
0
  GtkWindow* printDialog = GTK_WINDOW(user_data);
64
0
  nsCOMPtr<nsIStringBundleService> bundleSvc =
65
0
       do_GetService(NS_STRINGBUNDLE_CONTRACTID);
66
0
67
0
  nsCOMPtr<nsIStringBundle> printBundle;
68
0
  bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties", getter_AddRefs(printBundle));
69
0
  nsAutoString intlString;
70
0
71
0
  printBundle->GetStringFromName("headerFooterCustom", intlString);
72
0
  GtkWidget* prompt_dialog = gtk_dialog_new_with_buttons(NS_ConvertUTF16toUTF8(intlString).get(), printDialog,
73
0
                                                         (GtkDialogFlags)(GTK_DIALOG_MODAL),
74
0
                                                         GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
75
0
                                                         GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
76
0
                                                         nullptr);
77
0
  gtk_dialog_set_default_response(GTK_DIALOG(prompt_dialog), GTK_RESPONSE_ACCEPT);
78
0
  gtk_dialog_set_alternative_button_order(GTK_DIALOG(prompt_dialog),
79
0
                                          GTK_RESPONSE_ACCEPT,
80
0
                                          GTK_RESPONSE_REJECT,
81
0
                                          -1);
82
0
83
0
  printBundle->GetStringFromName("customHeaderFooterPrompt", intlString);
84
0
  GtkWidget* custom_label = gtk_label_new(NS_ConvertUTF16toUTF8(intlString).get());
85
0
  GtkWidget* custom_entry = gtk_entry_new();
86
0
  GtkWidget* question_icon = gtk_image_new_from_stock(GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG);
87
0
88
0
  // To be convenient, prefill the textbox with the existing value, if any, and select it all so they can easily
89
0
  // both edit it and type in a new one.
90
0
  const char* current_text = (const char*) g_object_get_data(G_OBJECT(changed_box), "custom-text");
91
0
  if (current_text) {
92
0
    gtk_entry_set_text(GTK_ENTRY(custom_entry), current_text);
93
0
    gtk_editable_select_region(GTK_EDITABLE(custom_entry), 0, -1);
94
0
  }
95
0
  gtk_entry_set_activates_default(GTK_ENTRY(custom_entry), TRUE);
96
0
97
0
  GtkWidget* custom_vbox = gtk_vbox_new(TRUE, 2);
98
0
  gtk_box_pack_start(GTK_BOX(custom_vbox), custom_label, FALSE, FALSE, 0);
99
0
  gtk_box_pack_start(GTK_BOX(custom_vbox), custom_entry, FALSE, FALSE, 5); // Make entry 5px underneath label
100
0
  GtkWidget* custom_hbox = gtk_hbox_new(FALSE, 2);
101
0
  gtk_box_pack_start(GTK_BOX(custom_hbox), question_icon, FALSE, FALSE, 0);
102
0
  gtk_box_pack_start(GTK_BOX(custom_hbox), custom_vbox, FALSE, FALSE, 10); // Make question icon 10px away from content
103
0
  gtk_container_set_border_width(GTK_CONTAINER(custom_hbox), 2);
104
0
  gtk_widget_show_all(custom_hbox);
105
0
106
0
  gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(prompt_dialog))),
107
0
                     custom_hbox, FALSE, FALSE, 0);
108
0
  gint diag_response = gtk_dialog_run(GTK_DIALOG(prompt_dialog));
109
0
110
0
  if (diag_response == GTK_RESPONSE_ACCEPT) {
111
0
    const gchar* response_text = gtk_entry_get_text(GTK_ENTRY(custom_entry));
112
0
    g_object_set_data_full(G_OBJECT(changed_box), "custom-text", strdup(response_text), (GDestroyNotify) free);
113
0
    g_object_set_data(G_OBJECT(changed_box), "previous-active", GINT_TO_POINTER(CUSTOM_VALUE_INDEX));
114
0
  } else {
115
0
    // Go back to the previous index
116
0
    gint previous_active = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(changed_box), "previous-active"));
117
0
    gtk_combo_box_set_active(changed_box, previous_active);
118
0
  }
119
0
120
0
  gtk_widget_destroy(prompt_dialog);
121
0
}
122
123
class nsPrintDialogWidgetGTK {
124
  public:
125
    nsPrintDialogWidgetGTK(nsPIDOMWindowOuter *aParent,
126
                           nsIPrintSettings *aPrintSettings);
127
0
    ~nsPrintDialogWidgetGTK() { gtk_widget_destroy(dialog); }
128
    NS_ConvertUTF16toUTF8 GetUTF8FromBundle(const char* aKey);
129
    gint Run();
130
131
    nsresult ImportSettings(nsIPrintSettings *aNSSettings);
132
    nsresult ExportSettings(nsIPrintSettings *aNSSettings);
133
134
  private:
135
    GtkWidget* dialog;
136
    GtkWidget* radio_as_laid_out;
137
    GtkWidget* radio_selected_frame;
138
    GtkWidget* radio_separate_frames;
139
    GtkWidget* shrink_to_fit_toggle;
140
    GtkWidget* print_bg_colors_toggle;
141
    GtkWidget* print_bg_images_toggle;
142
    GtkWidget* selection_only_toggle;
143
    GtkWidget* header_dropdown[3];  // {left, center, right}
144
    GtkWidget* footer_dropdown[3];
145
146
    nsCOMPtr<nsIStringBundle> printBundle;
147
148
    bool useNativeSelection;
149
150
    GtkWidget* ConstructHeaderFooterDropdown(const char16_t *currentString);
151
    const char* OptionWidgetToString(GtkWidget *dropdown);
152
153
    /* Code to copy between GTK and NS print settings structures.
154
     * In the following,
155
     *   "Import" means to copy from NS to GTK
156
     *   "Export" means to copy from GTK to NS
157
     */
158
    void ExportFramePrinting(nsIPrintSettings *aNS, GtkPrintSettings *aSettings);
159
    void ExportHeaderFooter(nsIPrintSettings *aNS);
160
};
161
162
nsPrintDialogWidgetGTK::nsPrintDialogWidgetGTK(nsPIDOMWindowOuter *aParent,
163
                                               nsIPrintSettings *aSettings)
164
0
{
165
0
  nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aParent);
166
0
  NS_ASSERTION(widget, "Need a widget for dialog to be modal.");
167
0
  GtkWindow* gtkParent = get_gtk_window_for_nsiwidget(widget);
168
0
  NS_ASSERTION(gtkParent, "Need a GTK window for dialog to be modal.");
169
0
170
0
  nsCOMPtr<nsIStringBundleService> bundleSvc = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
171
0
  bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties", getter_AddRefs(printBundle));
172
0
173
0
  dialog = gtk_print_unix_dialog_new(GetUTF8FromBundle("printTitleGTK").get(), gtkParent);
174
0
175
0
  gtk_print_unix_dialog_set_manual_capabilities(GTK_PRINT_UNIX_DIALOG(dialog),
176
0
                    GtkPrintCapabilities(
177
0
                        GTK_PRINT_CAPABILITY_PAGE_SET
178
0
                      | GTK_PRINT_CAPABILITY_COPIES
179
0
                      | GTK_PRINT_CAPABILITY_COLLATE
180
0
                      | GTK_PRINT_CAPABILITY_REVERSE
181
0
                      | GTK_PRINT_CAPABILITY_SCALE
182
0
                      | GTK_PRINT_CAPABILITY_GENERATE_PDF
183
0
                    )
184
0
                  );
185
0
186
0
  // The vast majority of magic numbers in this widget construction are padding. e.g. for
187
0
  // the set_border_width below, 12px matches that of just about every other window.
188
0
  GtkWidget* custom_options_tab = gtk_vbox_new(FALSE, 0);
189
0
  gtk_container_set_border_width(GTK_CONTAINER(custom_options_tab), 12);
190
0
  GtkWidget* tab_label = gtk_label_new(GetUTF8FromBundle("optionsTabLabelGTK").get());
191
0
192
0
  int16_t frameUIFlag;
193
0
  aSettings->GetHowToEnableFrameUI(&frameUIFlag);
194
0
  radio_as_laid_out = gtk_radio_button_new_with_mnemonic(nullptr, GetUTF8FromBundle("asLaidOut").get());
195
0
  if (frameUIFlag == nsIPrintSettings::kFrameEnableNone)
196
0
    gtk_widget_set_sensitive(radio_as_laid_out, FALSE);
197
0
198
0
  radio_selected_frame = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(radio_as_laid_out),
199
0
                                                                        GetUTF8FromBundle("selectedFrame").get());
200
0
  if (frameUIFlag == nsIPrintSettings::kFrameEnableNone ||
201
0
      frameUIFlag == nsIPrintSettings::kFrameEnableAsIsAndEach)
202
0
    gtk_widget_set_sensitive(radio_selected_frame, FALSE);
203
0
204
0
  radio_separate_frames = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(radio_as_laid_out),
205
0
                                                                         GetUTF8FromBundle("separateFrames").get());
206
0
  if (frameUIFlag == nsIPrintSettings::kFrameEnableNone)
207
0
    gtk_widget_set_sensitive(radio_separate_frames, FALSE);
208
0
209
0
  // "Print Frames" options label, bold and center-aligned
210
0
  GtkWidget* print_frames_label = gtk_label_new(nullptr);
211
0
  char* pangoMarkup = g_markup_printf_escaped("<b>%s</b>", GetUTF8FromBundle("printFramesTitleGTK").get());
212
0
  gtk_label_set_markup(GTK_LABEL(print_frames_label), pangoMarkup);
213
0
  g_free(pangoMarkup);
214
0
  gtk_misc_set_alignment(GTK_MISC(print_frames_label), 0, 0);
215
0
216
0
  // Align the radio buttons slightly so they appear to fall under the aforementioned label as per the GNOME HIG
217
0
  GtkWidget* frames_radio_container = gtk_alignment_new(0, 0, 0, 0);
218
0
  gtk_alignment_set_padding(GTK_ALIGNMENT(frames_radio_container), 8, 0, 12, 0);
219
0
220
0
  // Radio buttons for the print frames options
221
0
  GtkWidget* frames_radio_list = gtk_vbox_new(TRUE, 2);
222
0
  gtk_box_pack_start(GTK_BOX(frames_radio_list), radio_as_laid_out, FALSE, FALSE, 0);
223
0
  gtk_box_pack_start(GTK_BOX(frames_radio_list), radio_selected_frame, FALSE, FALSE, 0);
224
0
  gtk_box_pack_start(GTK_BOX(frames_radio_list), radio_separate_frames, FALSE, FALSE, 0);
225
0
  gtk_container_add(GTK_CONTAINER(frames_radio_container), frames_radio_list);
226
0
227
0
  // Check buttons for shrink-to-fit and print selection
228
0
  GtkWidget* check_buttons_container = gtk_vbox_new(TRUE, 2);
229
0
  shrink_to_fit_toggle = gtk_check_button_new_with_mnemonic(GetUTF8FromBundle("shrinkToFit").get());
230
0
  gtk_box_pack_start(GTK_BOX(check_buttons_container), shrink_to_fit_toggle, FALSE, FALSE, 0);
231
0
232
0
  // GTK+2.18 and above allow us to add a "Selection" option to the main settings screen,
233
0
  // rather than adding an option on a custom tab like we must do on older versions.
234
0
  bool canSelectText;
235
0
  aSettings->GetPrintOptions(nsIPrintSettings::kEnableSelectionRB, &canSelectText);
236
0
  if (gtk_major_version > 2 ||
237
0
      (gtk_major_version == 2 && gtk_minor_version >= 18)) {
238
0
    useNativeSelection = true;
239
0
    g_object_set(dialog,
240
0
                 "support-selection", TRUE,
241
0
                 "has-selection", canSelectText,
242
0
                 "embed-page-setup", TRUE,
243
0
                 nullptr);
244
0
  } else {
245
0
    useNativeSelection = false;
246
0
    selection_only_toggle = gtk_check_button_new_with_mnemonic(GetUTF8FromBundle("selectionOnly").get());
247
0
    gtk_widget_set_sensitive(selection_only_toggle, canSelectText);
248
0
    gtk_box_pack_start(GTK_BOX(check_buttons_container), selection_only_toggle, FALSE, FALSE, 0);
249
0
  }
250
0
251
0
  // Check buttons for printing background
252
0
  GtkWidget* appearance_buttons_container = gtk_vbox_new(TRUE, 2);
253
0
  print_bg_colors_toggle = gtk_check_button_new_with_mnemonic(GetUTF8FromBundle("printBGColors").get());
254
0
  print_bg_images_toggle = gtk_check_button_new_with_mnemonic(GetUTF8FromBundle("printBGImages").get());
255
0
  gtk_box_pack_start(GTK_BOX(appearance_buttons_container), print_bg_colors_toggle, FALSE, FALSE, 0);
256
0
  gtk_box_pack_start(GTK_BOX(appearance_buttons_container), print_bg_images_toggle, FALSE, FALSE, 0);
257
0
258
0
  // "Appearance" options label, bold and center-aligned
259
0
  GtkWidget* appearance_label = gtk_label_new(nullptr);
260
0
  pangoMarkup = g_markup_printf_escaped("<b>%s</b>", GetUTF8FromBundle("printBGOptions").get());
261
0
  gtk_label_set_markup(GTK_LABEL(appearance_label), pangoMarkup);
262
0
  g_free(pangoMarkup);
263
0
  gtk_misc_set_alignment(GTK_MISC(appearance_label), 0, 0);
264
0
265
0
  GtkWidget* appearance_container = gtk_alignment_new(0, 0, 0, 0);
266
0
  gtk_alignment_set_padding(GTK_ALIGNMENT(appearance_container), 8, 0, 12, 0);
267
0
  gtk_container_add(GTK_CONTAINER(appearance_container), appearance_buttons_container);
268
0
269
0
  GtkWidget* appearance_vertical_squasher = gtk_vbox_new(FALSE, 0);
270
0
  gtk_box_pack_start(GTK_BOX(appearance_vertical_squasher), appearance_label, FALSE, FALSE, 0);
271
0
  gtk_box_pack_start(GTK_BOX(appearance_vertical_squasher), appearance_container, FALSE, FALSE, 0);
272
0
273
0
  // "Header & Footer" options label, bold and center-aligned
274
0
  GtkWidget* header_footer_label = gtk_label_new(nullptr);
275
0
  pangoMarkup = g_markup_printf_escaped("<b>%s</b>", GetUTF8FromBundle("headerFooter").get());
276
0
  gtk_label_set_markup(GTK_LABEL(header_footer_label), pangoMarkup);
277
0
  g_free(pangoMarkup);
278
0
  gtk_misc_set_alignment(GTK_MISC(header_footer_label), 0, 0);
279
0
280
0
  GtkWidget* header_footer_container = gtk_alignment_new(0, 0, 0, 0);
281
0
  gtk_alignment_set_padding(GTK_ALIGNMENT(header_footer_container), 8, 0, 12, 0);
282
0
283
0
284
0
  // --- Table for making the header and footer options ---
285
0
  GtkWidget* header_footer_table = gtk_table_new(3, 3, FALSE); // 3x3 table
286
0
  nsString header_footer_str[3];
287
0
288
0
  aSettings->GetHeaderStrLeft(header_footer_str[0]);
289
0
  aSettings->GetHeaderStrCenter(header_footer_str[1]);
290
0
  aSettings->GetHeaderStrRight(header_footer_str[2]);
291
0
292
0
  for (unsigned int i = 0; i < ArrayLength(header_dropdown); i++) {
293
0
    header_dropdown[i] = ConstructHeaderFooterDropdown(header_footer_str[i].get());
294
0
    // Those 4 magic numbers in the middle provide the position in the table.
295
0
    // The last two numbers mean 2 px padding on every side.
296
0
    gtk_table_attach(GTK_TABLE(header_footer_table), header_dropdown[i], i, (i + 1),
297
0
                     0, 1, (GtkAttachOptions) 0, (GtkAttachOptions) 0, 2, 2);
298
0
  }
299
0
300
0
  const char labelKeys[][7] = {"left", "center", "right"};
301
0
  for (unsigned int i = 0; i < ArrayLength(labelKeys); i++) {
302
0
    gtk_table_attach(GTK_TABLE(header_footer_table),
303
0
                     gtk_label_new(GetUTF8FromBundle(labelKeys[i]).get()),
304
0
                     i, (i + 1), 1, 2, (GtkAttachOptions) 0, (GtkAttachOptions) 0, 2, 2);
305
0
  }
306
0
307
0
  aSettings->GetFooterStrLeft(header_footer_str[0]);
308
0
  aSettings->GetFooterStrCenter(header_footer_str[1]);
309
0
  aSettings->GetFooterStrRight(header_footer_str[2]);
310
0
311
0
  for (unsigned int i = 0; i < ArrayLength(footer_dropdown); i++) {
312
0
    footer_dropdown[i] = ConstructHeaderFooterDropdown(header_footer_str[i].get());
313
0
    gtk_table_attach(GTK_TABLE(header_footer_table), footer_dropdown[i], i, (i + 1),
314
0
                     2, 3, (GtkAttachOptions) 0, (GtkAttachOptions) 0, 2, 2);
315
0
  }
316
0
  // ---
317
0
318
0
  gtk_container_add(GTK_CONTAINER(header_footer_container), header_footer_table);
319
0
320
0
  GtkWidget* header_footer_vertical_squasher = gtk_vbox_new(FALSE, 0);
321
0
  gtk_box_pack_start(GTK_BOX(header_footer_vertical_squasher), header_footer_label, FALSE, FALSE, 0);
322
0
  gtk_box_pack_start(GTK_BOX(header_footer_vertical_squasher), header_footer_container, FALSE, FALSE, 0);
323
0
324
0
  // Construction of everything
325
0
  gtk_box_pack_start(GTK_BOX(custom_options_tab), print_frames_label, FALSE, FALSE, 0);
326
0
  gtk_box_pack_start(GTK_BOX(custom_options_tab), frames_radio_container, FALSE, FALSE, 0);
327
0
  gtk_box_pack_start(GTK_BOX(custom_options_tab), check_buttons_container, FALSE, FALSE, 10); // 10px padding
328
0
  gtk_box_pack_start(GTK_BOX(custom_options_tab), appearance_vertical_squasher, FALSE, FALSE, 10);
329
0
  gtk_box_pack_start(GTK_BOX(custom_options_tab), header_footer_vertical_squasher, FALSE, FALSE, 0);
330
0
331
0
  gtk_print_unix_dialog_add_custom_tab(GTK_PRINT_UNIX_DIALOG(dialog), custom_options_tab, tab_label);
332
0
  gtk_widget_show_all(custom_options_tab);
333
0
}
334
335
NS_ConvertUTF16toUTF8
336
nsPrintDialogWidgetGTK::GetUTF8FromBundle(const char *aKey)
337
0
{
338
0
  nsAutoString intlString;
339
0
  printBundle->GetStringFromName(aKey, intlString);
340
0
  return NS_ConvertUTF16toUTF8(intlString);  // Return the actual object so we don't lose reference
341
0
}
342
343
const char*
344
nsPrintDialogWidgetGTK::OptionWidgetToString(GtkWidget *dropdown)
345
0
{
346
0
  gint index = gtk_combo_box_get_active(GTK_COMBO_BOX(dropdown));
347
0
348
0
  NS_ASSERTION(index <= CUSTOM_VALUE_INDEX, "Index of dropdown is higher than expected!");
349
0
350
0
  if (index == CUSTOM_VALUE_INDEX)
351
0
    return (const char*) g_object_get_data(G_OBJECT(dropdown), "custom-text");
352
0
  else
353
0
    return header_footer_tags[index];
354
0
}
355
356
gint
357
nsPrintDialogWidgetGTK::Run()
358
0
{
359
0
  const gint response = gtk_dialog_run(GTK_DIALOG(dialog));
360
0
  gtk_widget_hide(dialog);
361
0
  return response;
362
0
}
363
364
void
365
nsPrintDialogWidgetGTK::ExportFramePrinting(nsIPrintSettings *aNS, GtkPrintSettings *aSettings)
366
0
{
367
0
  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio_as_laid_out)))
368
0
    aNS->SetPrintFrameType(nsIPrintSettings::kFramesAsIs);
369
0
  else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio_selected_frame)))
370
0
    aNS->SetPrintFrameType(nsIPrintSettings::kSelectedFrame);
371
0
  else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio_separate_frames)))
372
0
    aNS->SetPrintFrameType(nsIPrintSettings::kEachFrameSep);
373
0
  else
374
0
    aNS->SetPrintFrameType(nsIPrintSettings::kNoFrames);
375
0
}
376
377
void
378
nsPrintDialogWidgetGTK::ExportHeaderFooter(nsIPrintSettings *aNS)
379
0
{
380
0
  const char* header_footer_str;
381
0
  header_footer_str = OptionWidgetToString(header_dropdown[0]);
382
0
  aNS->SetHeaderStrLeft(NS_ConvertUTF8toUTF16(header_footer_str));
383
0
384
0
  header_footer_str = OptionWidgetToString(header_dropdown[1]);
385
0
  aNS->SetHeaderStrCenter(NS_ConvertUTF8toUTF16(header_footer_str));
386
0
387
0
  header_footer_str = OptionWidgetToString(header_dropdown[2]);
388
0
  aNS->SetHeaderStrRight(NS_ConvertUTF8toUTF16(header_footer_str));
389
0
390
0
  header_footer_str = OptionWidgetToString(footer_dropdown[0]);
391
0
  aNS->SetFooterStrLeft(NS_ConvertUTF8toUTF16(header_footer_str));
392
0
393
0
  header_footer_str = OptionWidgetToString(footer_dropdown[1]);
394
0
  aNS->SetFooterStrCenter(NS_ConvertUTF8toUTF16(header_footer_str));
395
0
396
0
  header_footer_str = OptionWidgetToString(footer_dropdown[2]);
397
0
  aNS->SetFooterStrRight(NS_ConvertUTF8toUTF16(header_footer_str));
398
0
}
399
400
nsresult
401
nsPrintDialogWidgetGTK::ImportSettings(nsIPrintSettings *aNSSettings)
402
0
{
403
0
  MOZ_ASSERT(aNSSettings, "aSettings must not be null");
404
0
  NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);
405
0
406
0
  nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings));
407
0
  if (!aNSSettingsGTK)
408
0
    return NS_ERROR_FAILURE;
409
0
410
0
  GtkPrintSettings* settings = aNSSettingsGTK->GetGtkPrintSettings();
411
0
  GtkPageSetup* setup = aNSSettingsGTK->GetGtkPageSetup();
412
0
413
0
  bool geckoBool;
414
0
  aNSSettings->GetShrinkToFit(&geckoBool);
415
0
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(shrink_to_fit_toggle), geckoBool);
416
0
417
0
  aNSSettings->GetPrintBGColors(&geckoBool);
418
0
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(print_bg_colors_toggle), geckoBool);
419
0
420
0
  aNSSettings->GetPrintBGImages(&geckoBool);
421
0
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(print_bg_images_toggle), geckoBool);
422
0
423
0
  gtk_print_unix_dialog_set_settings(GTK_PRINT_UNIX_DIALOG(dialog), settings);
424
0
  gtk_print_unix_dialog_set_page_setup(GTK_PRINT_UNIX_DIALOG(dialog), setup);
425
0
426
0
  return NS_OK;
427
0
}
428
429
nsresult
430
nsPrintDialogWidgetGTK::ExportSettings(nsIPrintSettings *aNSSettings)
431
0
{
432
0
  MOZ_ASSERT(aNSSettings, "aSettings must not be null");
433
0
  NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);
434
0
435
0
  GtkPrintSettings* settings = gtk_print_unix_dialog_get_settings(GTK_PRINT_UNIX_DIALOG(dialog));
436
0
  GtkPageSetup* setup = gtk_print_unix_dialog_get_page_setup(GTK_PRINT_UNIX_DIALOG(dialog));
437
0
  GtkPrinter* printer = gtk_print_unix_dialog_get_selected_printer(GTK_PRINT_UNIX_DIALOG(dialog));
438
0
  if (settings && setup && printer) {
439
0
    ExportFramePrinting(aNSSettings, settings);
440
0
    ExportHeaderFooter(aNSSettings);
441
0
442
0
    aNSSettings->SetOutputFormat(nsIPrintSettings::kOutputFormatNative);
443
0
444
0
    // Print-to-file is true by default. This must be turned off or else printing won't occur!
445
0
    // (We manually copy the spool file when this flag is set, because we love our embedders)
446
0
    // Even if it is print-to-file in GTK's case, GTK does The Right Thing when we send the job.
447
0
    aNSSettings->SetPrintToFile(false);
448
0
449
0
    aNSSettings->SetShrinkToFit(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(shrink_to_fit_toggle)));
450
0
451
0
    aNSSettings->SetPrintBGColors(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(print_bg_colors_toggle)));
452
0
    aNSSettings->SetPrintBGImages(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(print_bg_images_toggle)));
453
0
454
0
    // Try to save native settings in the session object
455
0
    nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings));
456
0
    if (aNSSettingsGTK) {
457
0
      aNSSettingsGTK->SetGtkPrintSettings(settings);
458
0
      aNSSettingsGTK->SetGtkPageSetup(setup);
459
0
      aNSSettingsGTK->SetGtkPrinter(printer);
460
0
      bool printSelectionOnly;
461
0
      if (useNativeSelection) {
462
0
        _GtkPrintPages pageSetting = (_GtkPrintPages)gtk_print_settings_get_print_pages(settings);
463
0
        printSelectionOnly = (pageSetting == _GTK_PRINT_PAGES_SELECTION);
464
0
      } else {
465
0
        printSelectionOnly = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(selection_only_toggle));
466
0
      }
467
0
      aNSSettingsGTK->SetForcePrintSelectionOnly(printSelectionOnly);
468
0
    }
469
0
  }
470
0
471
0
  if (settings)
472
0
    g_object_unref(settings);
473
0
  return NS_OK;
474
0
}
475
476
GtkWidget*
477
nsPrintDialogWidgetGTK::ConstructHeaderFooterDropdown(const char16_t *currentString)
478
0
{
479
0
  GtkWidget* dropdown = gtk_combo_box_text_new();
480
0
  const char hf_options[][22] = {"headerFooterBlank", "headerFooterTitle",
481
0
                                 "headerFooterURL", "headerFooterDate",
482
0
                                 "headerFooterPage", "headerFooterPageTotal",
483
0
                                 "headerFooterCustom"};
484
0
485
0
  for (unsigned int i = 0; i < ArrayLength(hf_options); i++) {
486
0
    gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(dropdown), nullptr,
487
0
                              GetUTF8FromBundle(hf_options[i]).get());
488
0
  }
489
0
490
0
  bool shouldBeCustom = true;
491
0
  NS_ConvertUTF16toUTF8 currentStringUTF8(currentString);
492
0
493
0
  for (unsigned int i = 0; i < ArrayLength(header_footer_tags); i++) {
494
0
    if (!strcmp(currentStringUTF8.get(), header_footer_tags[i])) {
495
0
      gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), i);
496
0
      g_object_set_data(G_OBJECT(dropdown), "previous-active", GINT_TO_POINTER(i));
497
0
      shouldBeCustom = false;
498
0
      break;
499
0
    }
500
0
  }
501
0
502
0
  if (shouldBeCustom) {
503
0
    gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), CUSTOM_VALUE_INDEX);
504
0
    g_object_set_data(G_OBJECT(dropdown), "previous-active", GINT_TO_POINTER(CUSTOM_VALUE_INDEX));
505
0
    char* custom_string = strdup(currentStringUTF8.get());
506
0
    g_object_set_data_full(G_OBJECT(dropdown), "custom-text", custom_string, (GDestroyNotify) free);
507
0
  }
508
0
509
0
  g_signal_connect(dropdown, "changed", (GCallback) ShowCustomDialog, dialog);
510
0
  return dropdown;
511
0
}
512
513
NS_IMPL_ISUPPORTS(nsPrintDialogServiceGTK, nsIPrintDialogService)
514
515
nsPrintDialogServiceGTK::nsPrintDialogServiceGTK()
516
0
{
517
0
}
518
519
nsPrintDialogServiceGTK::~nsPrintDialogServiceGTK()
520
0
{
521
0
}
522
523
NS_IMETHODIMP
524
nsPrintDialogServiceGTK::Init()
525
0
{
526
0
  return NS_OK;
527
0
}
528
529
// Used to obtain window handle. The portal use this handle
530
// to ensure that print dialog is modal.
531
typedef void (*WindowHandleExported) (GtkWindow  *window,
532
                                      const char *handle,
533
                                      gpointer    user_data);
534
535
typedef void (*GtkWindowHandleExported) (GtkWindow  *window,
536
                                         const char *handle,
537
                                         gpointer    user_data);
538
#ifdef MOZ_WAYLAND
539
typedef struct {
540
    GtkWindow *window;
541
    WindowHandleExported callback;
542
    gpointer user_data;
543
} WaylandWindowHandleExportedData;
544
545
static void
546
wayland_window_handle_exported (GdkWindow  *window,
547
                                const char *wayland_handle_str,
548
                                gpointer    user_data)
549
{
550
    WaylandWindowHandleExportedData *data =
551
        static_cast<WaylandWindowHandleExportedData*>(user_data);
552
    char *handle_str;
553
554
    handle_str = g_strdup_printf ("wayland:%s", wayland_handle_str);
555
    data->callback (data->window, handle_str, data->user_data);
556
    g_free (handle_str);
557
}
558
#endif
559
560
// Get window handle for the portal, taken from gtk/gtkwindow.c
561
// (currently not exported)
562
static gboolean
563
window_export_handle(GtkWindow               *window,
564
                     GtkWindowHandleExported  callback,
565
                     gpointer                 user_data)
566
0
{
567
0
  if (GDK_IS_X11_DISPLAY(gtk_widget_get_display(GTK_WIDGET(window))))
568
0
    {
569
0
      GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
570
0
      char *handle_str;
571
0
      guint32 xid = (guint32) gdk_x11_window_get_xid(gdk_window);
572
0
573
0
      handle_str = g_strdup_printf("x11:%x", xid);
574
0
      callback(window, handle_str, user_data);
575
0
      g_free(handle_str);
576
0
      return true;
577
0
    }
578
#ifdef MOZ_WAYLAND
579
  else
580
    {
581
      GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
582
      WaylandWindowHandleExportedData *data;
583
584
      data = g_new0(WaylandWindowHandleExportedData, 1);
585
      data->window = window;
586
      data->callback = callback;
587
      data->user_data = user_data;
588
589
      static auto s_gdk_wayland_window_export_handle =
590
        reinterpret_cast<gboolean (*)(GdkWindow*, GdkWaylandWindowExported,
591
                                      gpointer, GDestroyNotify)>
592
        (dlsym(RTLD_DEFAULT, "gdk_wayland_window_export_handle"));
593
      if (!s_gdk_wayland_window_export_handle ||
594
          !s_gdk_wayland_window_export_handle(gdk_window,
595
                                              wayland_window_handle_exported,
596
                                              data, g_free)) {
597
          g_free (data);
598
          return false;
599
        } else  {
600
          return true;
601
        }
602
    }
603
#endif
604
605
0
  g_warning("Couldn't export handle, unsupported windowing system");
606
0
607
0
  return false;
608
0
}
609
/**
610
 * Communication class with the GTK print portal handler
611
 *
612
 * To print document from flatpak we need to use print portal because
613
 * printers are not directly accessible in the sandboxed environment.
614
 *
615
 * At first we request portal to show the print dialog to let user choose
616
 * printer settings. We use DBUS interface for that (PreparePrint method).
617
 *
618
 * Next we force application to print to temporary file and after the writing
619
 * to the file is finished we pass its file descriptor to the portal.
620
 * Portal will pass duplicate of the file descriptor to the printer which
621
 * user selected before (by DBUS Print method).
622
 *
623
 * Since DBUS communication is done async while nsPrintDialogServiceGTK::Show
624
 * is expecting sync execution, we need to create a new GMainLoop during the
625
 * print portal dialog is running. The loop is stopped after the dialog
626
 * is closed.
627
 */
628
class nsFlatpakPrintPortal: public nsIObserver
629
{
630
  NS_DECL_ISUPPORTS
631
  NS_DECL_NSIOBSERVER
632
  public:
633
    explicit nsFlatpakPrintPortal(nsPrintSettingsGTK* aPrintSettings);
634
    nsresult PreparePrintRequest(GtkWindow* aWindow);
635
    static void OnWindowExportHandleDone(GtkWindow *aWindow,
636
                                         const char* aWindowHandleStr,
637
                                         gpointer aUserData);
638
    void PreparePrint(GtkWindow* aWindow, const char* aWindowHandleStr);
639
    static void OnPreparePrintResponse(GDBusConnection *connection,
640
                                       const char      *sender_name,
641
                                       const char      *object_path,
642
                                       const char      *interface_name,
643
                                       const char      *signal_name,
644
                                       GVariant        *parameters,
645
                                       gpointer         data);
646
    GtkPrintOperationResult GetResult();
647
  private:
648
    virtual ~nsFlatpakPrintPortal();
649
    void FinishPrintDialog(GVariant* parameters);
650
    nsCOMPtr<nsPrintSettingsGTK> mPrintAndPageSettings;
651
    GDBusProxy* mProxy;
652
    guint32 mToken;
653
    GMainLoop* mLoop;
654
    GtkPrintOperationResult mResult;
655
    guint mResponseSignalId;
656
    GtkWindow* mParentWindow;
657
};
658
659
NS_IMPL_ISUPPORTS(nsFlatpakPrintPortal, nsIObserver)
660
661
nsFlatpakPrintPortal::nsFlatpakPrintPortal(nsPrintSettingsGTK* aPrintSettings):
662
  mPrintAndPageSettings(aPrintSettings),
663
  mProxy(nullptr),
664
  mLoop(nullptr),
665
  mResponseSignalId(0),
666
  mParentWindow(nullptr)
667
0
{
668
0
}
669
670
/**
671
 * Creates GDBusProxy, query for window handle and create a new GMainLoop.
672
 *
673
 * The GMainLoop is to be run from GetResult() and be quitted during
674
 * FinishPrintDialog.
675
 *
676
 * @param aWindow toplevel application window which is used as parent of print
677
 *                dialog
678
 */
679
nsresult
680
nsFlatpakPrintPortal::PreparePrintRequest(GtkWindow* aWindow)
681
0
{
682
0
  MOZ_ASSERT(aWindow, "aWindow must not be null");
683
0
  MOZ_ASSERT(mPrintAndPageSettings, "mPrintAndPageSettings must not be null");
684
0
685
0
  GError* error = nullptr;
686
0
  mProxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
687
0
      G_DBUS_PROXY_FLAGS_NONE,
688
0
      nullptr,
689
0
      "org.freedesktop.portal.Desktop",
690
0
      "/org/freedesktop/portal/desktop",
691
0
      "org.freedesktop.portal.Print",
692
0
      nullptr,
693
0
      &error);
694
0
  if (mProxy == nullptr) {
695
0
    NS_WARNING(nsPrintfCString("Unable to create dbus proxy: %s", error->message).get());
696
0
    g_error_free(error);
697
0
    return NS_ERROR_FAILURE;
698
0
  }
699
0
700
0
  // The window handler is returned async, we will continue by PreparePrint method
701
0
  // when it is returned.
702
0
  if (!window_export_handle(aWindow,
703
0
        &nsFlatpakPrintPortal::OnWindowExportHandleDone, this)) {
704
0
    NS_WARNING("Unable to get window handle for creating modal print dialog.");
705
0
    return NS_ERROR_FAILURE;
706
0
  }
707
0
708
0
  mLoop = g_main_loop_new (NULL, FALSE);
709
0
  return NS_OK;
710
0
}
711
712
void
713
nsFlatpakPrintPortal::OnWindowExportHandleDone(GtkWindow* aWindow,
714
                                               const char* aWindowHandleStr,
715
                                               gpointer aUserData)
716
0
{
717
0
  nsFlatpakPrintPortal* printPortal = static_cast<nsFlatpakPrintPortal*>(aUserData);
718
0
  printPortal->PreparePrint(aWindow, aWindowHandleStr);
719
0
}
720
721
/**
722
 * Ask print portal to show the print dialog.
723
 *
724
 * Print and page settings and window handle are passed to the portal to prefill
725
 * last used settings.
726
 */
727
void
728
nsFlatpakPrintPortal::PreparePrint(GtkWindow* aWindow, const char* aWindowHandleStr)
729
0
{
730
0
  GtkPrintSettings* gtkSettings = mPrintAndPageSettings->GetGtkPrintSettings();
731
0
  GtkPageSetup* pageSetup = mPrintAndPageSettings->GetGtkPageSetup();
732
0
733
0
  // We need to remember GtkWindow to unexport window handle after it is
734
0
  // no longer needed by the portal dialog (apply only on non-X11 sessions).
735
0
  if (!GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
736
0
    mParentWindow = aWindow;
737
0
  }
738
0
739
0
  GVariantBuilder opt_builder;
740
0
  g_variant_builder_init(&opt_builder, G_VARIANT_TYPE_VARDICT);
741
0
  char* token = g_strdup_printf("mozilla%d", g_random_int_range (0, G_MAXINT));
742
0
  g_variant_builder_add(&opt_builder, "{sv}", "handle_token",
743
0
      g_variant_new_string(token));
744
0
  g_free(token);
745
0
  GVariant* options = g_variant_builder_end(&opt_builder);
746
0
  static auto s_gtk_print_settings_to_gvariant =
747
0
    reinterpret_cast<GVariant* (*)(GtkPrintSettings*)>
748
0
    (dlsym(RTLD_DEFAULT, "gtk_print_settings_to_gvariant"));
749
0
  static auto s_gtk_page_setup_to_gvariant =
750
0
    reinterpret_cast<GVariant* (*)(GtkPageSetup *)>
751
0
    (dlsym(RTLD_DEFAULT, "gtk_page_setup_to_gvariant"));
752
0
  if (!s_gtk_print_settings_to_gvariant || !s_gtk_page_setup_to_gvariant) {
753
0
    mResult = GTK_PRINT_OPERATION_RESULT_ERROR;
754
0
    FinishPrintDialog(nullptr);
755
0
    return;
756
0
  }
757
0
758
0
  // Get translated window title
759
0
  nsCOMPtr<nsIStringBundleService> bundleSvc =
760
0
       do_GetService(NS_STRINGBUNDLE_CONTRACTID);
761
0
  nsCOMPtr<nsIStringBundle> printBundle;
762
0
  bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties",
763
0
      getter_AddRefs(printBundle));
764
0
  nsAutoString intlPrintTitle;
765
0
  printBundle->GetStringFromName("printTitleGTK", intlPrintTitle);
766
0
767
0
  GError* error = nullptr;
768
0
  GVariant *ret = g_dbus_proxy_call_sync(mProxy,
769
0
      "PreparePrint",
770
0
      g_variant_new ("(ss@a{sv}@a{sv}@a{sv})",
771
0
        aWindowHandleStr,
772
0
        NS_ConvertUTF16toUTF8(intlPrintTitle).get(), // Title of the window
773
0
        s_gtk_print_settings_to_gvariant(gtkSettings),
774
0
        s_gtk_page_setup_to_gvariant(pageSetup),
775
0
        options),
776
0
      G_DBUS_CALL_FLAGS_NONE,
777
0
      -1,
778
0
      nullptr,
779
0
      &error);
780
0
  if (ret == nullptr) {
781
0
    NS_WARNING(nsPrintfCString("Unable to call dbus proxy: %s", error->message).get());
782
0
    g_error_free (error);
783
0
    mResult = GTK_PRINT_OPERATION_RESULT_ERROR;
784
0
    FinishPrintDialog(nullptr);
785
0
    return;
786
0
  }
787
0
788
0
  const char* handle = nullptr;
789
0
  g_variant_get (ret, "(&o)", &handle);
790
0
  if (strcmp (aWindowHandleStr, handle) != 0)
791
0
  {
792
0
    aWindowHandleStr = g_strdup (handle);
793
0
    if (mResponseSignalId) {
794
0
      g_dbus_connection_signal_unsubscribe(
795
0
          g_dbus_proxy_get_connection(G_DBUS_PROXY(mProxy)), mResponseSignalId);
796
0
    }
797
0
  }
798
0
  mResponseSignalId =
799
0
    g_dbus_connection_signal_subscribe(
800
0
        g_dbus_proxy_get_connection(G_DBUS_PROXY(mProxy)),
801
0
        "org.freedesktop.portal.Desktop",
802
0
        "org.freedesktop.portal.Request",
803
0
        "Response",
804
0
        aWindowHandleStr,
805
0
        NULL,
806
0
        G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
807
0
        &nsFlatpakPrintPortal::OnPreparePrintResponse,
808
0
        this, NULL);
809
0
810
0
}
811
812
void
813
nsFlatpakPrintPortal::OnPreparePrintResponse(GDBusConnection *connection,
814
                                             const char      *sender_name,
815
                                             const char      *object_path,
816
                                             const char      *interface_name,
817
                                             const char      *signal_name,
818
                                             GVariant        *parameters,
819
                                             gpointer         data)
820
0
{
821
0
  nsFlatpakPrintPortal* printPortal = static_cast<nsFlatpakPrintPortal*>(data);
822
0
  printPortal->FinishPrintDialog(parameters);
823
0
}
824
825
/**
826
 * When the dialog is accepted, read print and page settings and token.
827
 *
828
 * Token is later used for printing portal as print operation identifier.
829
 * Print and page settings are modified in-place and stored to
830
 * mPrintAndPageSettings.
831
 */
832
void
833
nsFlatpakPrintPortal::FinishPrintDialog(GVariant* parameters)
834
0
{
835
0
  // This ends GetResult() method
836
0
  if (mLoop) {
837
0
    g_main_loop_quit (mLoop);
838
0
    mLoop = nullptr;
839
0
  }
840
0
841
0
  if (!parameters) {
842
0
    // mResult should be already defined
843
0
    return;
844
0
  }
845
0
846
0
  guint32 response;
847
0
  GVariant *options;
848
0
849
0
  g_variant_get (parameters, "(u@a{sv})", &response, &options);
850
0
  mResult = GTK_PRINT_OPERATION_RESULT_CANCEL;
851
0
  if (response == 0) {
852
0
    GVariant *v;
853
0
854
0
    char *filename;
855
0
    char *uri;
856
0
    v = g_variant_lookup_value (options, "settings", G_VARIANT_TYPE_VARDICT);
857
0
    static auto s_gtk_print_settings_new_from_gvariant =
858
0
      reinterpret_cast<GtkPrintSettings* (*)(GVariant*)>
859
0
      (dlsym(RTLD_DEFAULT, "gtk_print_settings_new_from_gvariant"));
860
0
861
0
    GtkPrintSettings* printSettings = s_gtk_print_settings_new_from_gvariant(v);
862
0
    g_variant_unref (v);
863
0
864
0
    v = g_variant_lookup_value (options, "page-setup", G_VARIANT_TYPE_VARDICT);
865
0
    static auto s_gtk_page_setup_new_from_gvariant =
866
0
      reinterpret_cast<GtkPageSetup* (*)(GVariant*)>
867
0
      (dlsym(RTLD_DEFAULT, "gtk_page_setup_new_from_gvariant"));
868
0
    GtkPageSetup* pageSetup = s_gtk_page_setup_new_from_gvariant(v);
869
0
    g_variant_unref (v);
870
0
871
0
    g_variant_lookup (options, "token", "u", &mToken);
872
0
873
0
    // Force printing to file because only filedescriptor of the file
874
0
    // can be passed to portal
875
0
    int fd = g_file_open_tmp("gtkprintXXXXXX", &filename, NULL);
876
0
    uri = g_filename_to_uri(filename, NULL, NULL);
877
0
    gtk_print_settings_set(printSettings, GTK_PRINT_SETTINGS_OUTPUT_URI, uri);
878
0
    g_free (uri);
879
0
    close (fd);
880
0
881
0
    // Save native settings in the session object
882
0
    mPrintAndPageSettings->SetGtkPrintSettings(printSettings);
883
0
    mPrintAndPageSettings->SetGtkPageSetup(pageSetup);
884
0
885
0
    // Portal consumes PDF file
886
0
    mPrintAndPageSettings->SetOutputFormat(nsIPrintSettings::kOutputFormatPDF);
887
0
888
0
    // We need to set to print to file
889
0
    mPrintAndPageSettings->SetPrintToFile(true);
890
0
891
0
    mResult = GTK_PRINT_OPERATION_RESULT_APPLY;
892
0
  }
893
0
}
894
895
/**
896
 * Get result of the print dialog.
897
 *
898
 * This call blocks until FinishPrintDialog is called.
899
 *
900
 */
901
GtkPrintOperationResult
902
0
nsFlatpakPrintPortal::GetResult() {
903
0
  // If the mLoop has not been initialized we haven't go thru PreparePrint method
904
0
  if (!NS_IsMainThread() || !mLoop) {
905
0
    return GTK_PRINT_OPERATION_RESULT_ERROR;
906
0
  }
907
0
  // Calling g_main_loop_run stops current code until g_main_loop_quit is called
908
0
  g_main_loop_run(mLoop);
909
0
910
0
  // Free resources we've allocated in order to show print dialog.
911
#ifdef MOZ_WAYLAND
912
  if (mParentWindow) {
913
    GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(mParentWindow));
914
    static auto s_gdk_wayland_window_unexport_handle =
915
      reinterpret_cast<void (*)(GdkWindow*)>
916
      (dlsym(RTLD_DEFAULT, "gdk_wayland_window_unexport_handle"));
917
    if (s_gdk_wayland_window_unexport_handle) {
918
      s_gdk_wayland_window_unexport_handle(gdk_window);
919
    }
920
  }
921
#endif
922
  return mResult;
923
0
}
924
925
/**
926
 * Send file descriptor of the file which contains document to the portal to
927
 * finish the print operation.
928
 */
929
NS_IMETHODIMP
930
nsFlatpakPrintPortal::Observe(nsISupports *aObject, const char * aTopic,
931
                              const char16_t * aData)
932
0
{
933
0
  // Check that written file match to the stored filename in case multiple
934
0
  // print operations are in progress.
935
0
  nsAutoString filenameStr;
936
0
  mPrintAndPageSettings->GetToFileName(filenameStr);
937
0
  if (!nsDependentString(aData).Equals(filenameStr)) {
938
0
    // Different file is finished, not for this instance
939
0
    return NS_OK;
940
0
  }
941
0
  int fd, idx;
942
0
  fd = open(NS_ConvertUTF16toUTF8(filenameStr).get(), O_RDONLY|O_CLOEXEC);
943
0
  static auto s_g_unix_fd_list_new =
944
0
    reinterpret_cast<GUnixFDList* (*)(void)>
945
0
    (dlsym(RTLD_DEFAULT, "g_unix_fd_list_new"));
946
0
  NS_ASSERTION(s_g_unix_fd_list_new, "Cannot find g_unix_fd_list_new function.");
947
0
948
0
  GUnixFDList *fd_list = s_g_unix_fd_list_new();
949
0
  static auto s_g_unix_fd_list_append =
950
0
    reinterpret_cast<gint (*)(GUnixFDList*, gint, GError**)>
951
0
    (dlsym(RTLD_DEFAULT, "g_unix_fd_list_append"));
952
0
  idx = s_g_unix_fd_list_append(fd_list, fd, NULL);
953
0
  close(fd);
954
0
955
0
  GVariantBuilder opt_builder;
956
0
  g_variant_builder_init(&opt_builder, G_VARIANT_TYPE_VARDICT);
957
0
  g_variant_builder_add(&opt_builder, "{sv}",  "token",
958
0
      g_variant_new_uint32(mToken));
959
0
  g_dbus_proxy_call_with_unix_fd_list(
960
0
      mProxy,
961
0
      "Print",
962
0
      g_variant_new("(ssh@a{sv})",
963
0
                     "", /* window */
964
0
                     "Print", /* title */
965
0
                     idx,
966
0
                     g_variant_builder_end(&opt_builder)),
967
0
      G_DBUS_CALL_FLAGS_NONE,
968
0
      -1,
969
0
      fd_list,
970
0
      NULL,
971
0
      NULL, // TODO portal result cb function
972
0
      nullptr); // data
973
0
  g_object_unref(fd_list);
974
0
975
0
  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
976
0
  // Let the nsFlatpakPrintPortal instance die
977
0
  os->RemoveObserver(this, "print-to-file-finished");
978
0
  return NS_OK;
979
0
}
980
981
0
nsFlatpakPrintPortal::~nsFlatpakPrintPortal() {
982
0
  if (mProxy) {
983
0
    if (mResponseSignalId) {
984
0
      g_dbus_connection_signal_unsubscribe(
985
0
          g_dbus_proxy_get_connection(G_DBUS_PROXY(mProxy)), mResponseSignalId);
986
0
    }
987
0
    g_object_unref(mProxy);
988
0
  }
989
0
  if (mLoop)
990
0
    g_main_loop_quit(mLoop);
991
0
}
992
993
NS_IMETHODIMP
994
nsPrintDialogServiceGTK::Show(nsPIDOMWindowOuter *aParent,
995
                              nsIPrintSettings *aSettings,
996
                              nsIWebBrowserPrint *aWebBrowserPrint)
997
0
{
998
0
  MOZ_ASSERT(aParent, "aParent must not be null");
999
0
  MOZ_ASSERT(aSettings, "aSettings must not be null");
1000
0
1001
0
  // Check for the flatpak portal first
1002
0
  nsCOMPtr<nsIGIOService> giovfs =
1003
0
    do_GetService(NS_GIOSERVICE_CONTRACTID);
1004
0
  bool shouldUsePortal;
1005
0
  giovfs->ShouldUseFlatpakPortal(&shouldUsePortal);
1006
0
  if (shouldUsePortal && gtk_check_version(3, 22, 0) == nullptr) {
1007
0
    nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aParent);
1008
0
    NS_ASSERTION(widget, "Need a widget for dialog to be modal.");
1009
0
    GtkWindow* gtkParent = get_gtk_window_for_nsiwidget(widget);
1010
0
    NS_ASSERTION(gtkParent, "Need a GTK window for dialog to be modal.");
1011
0
1012
0
1013
0
    nsCOMPtr<nsPrintSettingsGTK> printSettingsGTK(do_QueryInterface(aSettings));
1014
0
    RefPtr<nsFlatpakPrintPortal> fpPrintPortal =
1015
0
      new nsFlatpakPrintPortal(printSettingsGTK);
1016
0
1017
0
    nsresult rv = fpPrintPortal->PreparePrintRequest(gtkParent);
1018
0
    NS_ENSURE_SUCCESS(rv, rv);
1019
0
1020
0
    // This blocks until nsFlatpakPrintPortal::FinishPrintDialog is called
1021
0
    GtkPrintOperationResult printDialogResult = fpPrintPortal->GetResult();
1022
0
1023
0
    rv = NS_OK;
1024
0
    switch (printDialogResult) {
1025
0
      case GTK_PRINT_OPERATION_RESULT_APPLY:
1026
0
        {
1027
0
          nsCOMPtr<nsIObserver> observer = do_QueryInterface(fpPrintPortal);
1028
0
          nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1029
0
          NS_ENSURE_STATE(os);
1030
0
          // Observer waits until notified that the file with the content
1031
0
          // to print has been written.
1032
0
          rv = os->AddObserver(observer, "print-to-file-finished", false);
1033
0
          NS_ENSURE_SUCCESS(rv, rv);
1034
0
          break;
1035
0
        }
1036
0
      case GTK_PRINT_OPERATION_RESULT_CANCEL:
1037
0
        rv = NS_ERROR_ABORT;
1038
0
        break;
1039
0
      default:
1040
0
        NS_WARNING("Unexpected response");
1041
0
        rv = NS_ERROR_ABORT;
1042
0
    }
1043
0
    return rv;
1044
0
  }
1045
0
1046
0
  nsPrintDialogWidgetGTK printDialog(aParent, aSettings);
1047
0
  nsresult rv = printDialog.ImportSettings(aSettings);
1048
0
1049
0
  NS_ENSURE_SUCCESS(rv, rv);
1050
0
1051
0
  const gint response = printDialog.Run();
1052
0
1053
0
  // Handle the result
1054
0
  switch (response) {
1055
0
    case GTK_RESPONSE_OK:                // Proceed
1056
0
      rv = printDialog.ExportSettings(aSettings);
1057
0
      break;
1058
0
1059
0
    case GTK_RESPONSE_CANCEL:
1060
0
    case GTK_RESPONSE_CLOSE:
1061
0
    case GTK_RESPONSE_DELETE_EVENT:
1062
0
    case GTK_RESPONSE_NONE:
1063
0
      rv = NS_ERROR_ABORT;
1064
0
      break;
1065
0
1066
0
    case GTK_RESPONSE_APPLY:                // Print preview
1067
0
    default:
1068
0
      NS_WARNING("Unexpected response");
1069
0
      rv = NS_ERROR_ABORT;
1070
0
  }
1071
0
  return rv;
1072
0
}
1073
1074
NS_IMETHODIMP
1075
nsPrintDialogServiceGTK::ShowPageSetup(nsPIDOMWindowOuter *aParent,
1076
                                       nsIPrintSettings *aNSSettings)
1077
0
{
1078
0
  MOZ_ASSERT(aParent, "aParent must not be null");
1079
0
  MOZ_ASSERT(aNSSettings, "aSettings must not be null");
1080
0
  NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);
1081
0
1082
0
  nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aParent);
1083
0
  NS_ASSERTION(widget, "Need a widget for dialog to be modal.");
1084
0
  GtkWindow* gtkParent = get_gtk_window_for_nsiwidget(widget);
1085
0
  NS_ASSERTION(gtkParent, "Need a GTK window for dialog to be modal.");
1086
0
1087
0
  nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings));
1088
0
  if (!aNSSettingsGTK)
1089
0
    return NS_ERROR_FAILURE;
1090
0
1091
0
  // We need to init the prefs here because aNSSettings in its current form is a dummy in both uses of the word
1092
0
  nsCOMPtr<nsIPrintSettingsService> psService = do_GetService("@mozilla.org/gfx/printsettings-service;1");
1093
0
  if (psService) {
1094
0
    nsString printName;
1095
0
    aNSSettings->GetPrinterName(printName);
1096
0
    if (printName.IsVoid()) {
1097
0
      psService->GetDefaultPrinterName(printName);
1098
0
      aNSSettings->SetPrinterName(printName);
1099
0
    }
1100
0
    psService->InitPrintSettingsFromPrefs(aNSSettings, true, nsIPrintSettings::kInitSaveAll);
1101
0
  }
1102
0
1103
0
  GtkPrintSettings* gtkSettings = aNSSettingsGTK->GetGtkPrintSettings();
1104
0
  GtkPageSetup* oldPageSetup = aNSSettingsGTK->GetGtkPageSetup();
1105
0
1106
0
  GtkPageSetup* newPageSetup = gtk_print_run_page_setup_dialog(gtkParent, oldPageSetup, gtkSettings);
1107
0
1108
0
  aNSSettingsGTK->SetGtkPageSetup(newPageSetup);
1109
0
1110
0
  // Now newPageSetup has a refcount of 2 (SetGtkPageSetup will addref), put it to 1 so if
1111
0
  // this gets replaced we don't leak.
1112
0
  g_object_unref(newPageSetup);
1113
0
1114
0
  if (psService)
1115
0
    psService->SavePrintSettingsToPrefs(aNSSettings, true, nsIPrintSettings::kInitSaveAll);
1116
0
1117
0
  return NS_OK;
1118
0
}