/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 | } |