1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/libgtk2ui/print_dialog_gtk2.h"
7 #include <gtk/gtkunixprint.h>
14 #include "base/bind.h"
15 #include "base/files/file_util.h"
16 #include "base/files/file_util_proxy.h"
17 #include "base/lazy_instance.h"
18 #include "base/logging.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/values.h"
21 #include "chrome/browser/ui/libgtk2ui/gtk2_util.h"
22 #include "chrome/browser/ui/libgtk2ui/printing_gtk2_util.h"
23 #include "printing/metafile.h"
24 #include "printing/print_job_constants.h"
25 #include "printing/print_settings.h"
26 #include "ui/aura/window.h"
27 #include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
29 using content::BrowserThread
;
30 using printing::PageRanges
;
31 using printing::PrintSettings
;
35 // CUPS Duplex attribute and values.
36 const char kCUPSDuplex
[] = "cups-Duplex";
37 const char kDuplexNone
[] = "None";
38 const char kDuplexTumble
[] = "DuplexTumble";
39 const char kDuplexNoTumble
[] = "DuplexNoTumble";
41 int kPaperSizeTresholdMicrons
= 100;
42 int kMicronsInMm
= 1000;
44 // Checks whether gtk_paper_size can be used to represent user selected media.
45 // In fuzzy match mode checks that paper sizes are "close enough" (less than
46 // 1mm difference). In the exact mode, looks for the paper with the same PPD
47 // name and "close enough" size.
48 bool PaperSizeMatch(GtkPaperSize
* gtk_paper_size
,
49 const PrintSettings::RequestedMedia
& media
,
51 if (!gtk_paper_size
) {
54 gfx::Size
paper_size_microns(
55 static_cast<int>(gtk_paper_size_get_width(gtk_paper_size
, GTK_UNIT_MM
) *
57 static_cast<int>(gtk_paper_size_get_height(gtk_paper_size
, GTK_UNIT_MM
) *
60 std::abs(paper_size_microns
.width() - media
.size_microns
.width()),
61 std::abs(paper_size_microns
.height() - media
.size_microns
.height()));
63 return diff
<= kPaperSizeTresholdMicrons
;
65 return !media
.vendor_id
.empty() &&
66 media
.vendor_id
== gtk_paper_size_get_ppd_name(gtk_paper_size
) &&
67 diff
<= kPaperSizeTresholdMicrons
;
70 // Looks up a paper size matching (in terms of PaperSizeMatch) the user selected
71 // media in the paper size list reported by GTK. Returns NULL if there's no
73 GtkPaperSize
* FindPaperSizeMatch(GList
* gtk_paper_sizes
,
74 const PrintSettings::RequestedMedia
& media
) {
75 GtkPaperSize
* first_fuzzy_match
= NULL
;
76 for (GList
* p
= gtk_paper_sizes
; p
&& p
->data
; p
= g_list_next(p
)) {
77 GtkPaperSize
* gtk_paper_size
= static_cast<GtkPaperSize
*>(p
->data
);
78 if (PaperSizeMatch(gtk_paper_size
, media
, false)) {
79 return gtk_paper_size
;
81 if (!first_fuzzy_match
&& PaperSizeMatch(gtk_paper_size
, media
, true)) {
82 first_fuzzy_match
= gtk_paper_size
;
85 return first_fuzzy_match
;
88 class StickyPrintSettingGtk
{
90 StickyPrintSettingGtk() : last_used_settings_(gtk_print_settings_new()) {
92 ~StickyPrintSettingGtk() {
93 NOTREACHED(); // Intended to be used with a Leaky LazyInstance.
96 GtkPrintSettings
* settings() {
97 return last_used_settings_
;
100 void SetLastUsedSettings(GtkPrintSettings
* settings
) {
101 DCHECK(last_used_settings_
);
102 g_object_unref(last_used_settings_
);
103 last_used_settings_
= gtk_print_settings_copy(settings
);
107 GtkPrintSettings
* last_used_settings_
;
109 DISALLOW_COPY_AND_ASSIGN(StickyPrintSettingGtk
);
112 base::LazyInstance
<StickyPrintSettingGtk
>::Leaky g_last_used_settings
=
113 LAZY_INSTANCE_INITIALIZER
;
115 // Helper class to track GTK printers.
116 class GtkPrinterList
{
118 GtkPrinterList() : default_printer_(NULL
) {
119 gtk_enumerate_printers(SetPrinter
, this, NULL
, TRUE
);
123 for (std::vector
<GtkPrinter
*>::iterator it
= printers_
.begin();
124 it
< printers_
.end(); ++it
) {
129 // Can return NULL if there's no default printer. E.g. Printer on a laptop
130 // is "home_printer", but the laptop is at work.
131 GtkPrinter
* default_printer() {
132 return default_printer_
;
135 // Can return NULL if the printer cannot be found due to:
136 // - Printer list out of sync with printer dialog UI.
137 // - Querying for non-existant printers like 'Print to PDF'.
138 GtkPrinter
* GetPrinterWithName(const std::string
& name
) {
142 for (std::vector
<GtkPrinter
*>::iterator it
= printers_
.begin();
143 it
< printers_
.end(); ++it
) {
144 if (gtk_printer_get_name(*it
) == name
) {
153 // Callback function used by gtk_enumerate_printers() to get all printer.
154 static gboolean
SetPrinter(GtkPrinter
* printer
, gpointer data
) {
155 GtkPrinterList
* printer_list
= reinterpret_cast<GtkPrinterList
*>(data
);
156 if (gtk_printer_is_default(printer
))
157 printer_list
->default_printer_
= printer
;
159 g_object_ref(printer
);
160 printer_list
->printers_
.push_back(printer
);
165 std::vector
<GtkPrinter
*> printers_
;
166 GtkPrinter
* default_printer_
;
172 printing::PrintDialogGtkInterface
* PrintDialogGtk2::CreatePrintDialog(
173 PrintingContextLinux
* context
) {
174 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
175 return new PrintDialogGtk2(context
);
178 PrintDialogGtk2::PrintDialogGtk2(PrintingContextLinux
* context
)
186 PrintDialogGtk2::~PrintDialogGtk2() {
187 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
190 aura::Window
* parent
= libgtk2ui::GetAuraTransientParent(dialog_
);
192 parent
->RemoveObserver(this);
193 libgtk2ui::ClearAuraTransientParent(dialog_
);
195 gtk_widget_destroy(dialog_
);
199 g_object_unref(gtk_settings_
);
200 gtk_settings_
= NULL
;
203 g_object_unref(page_setup_
);
207 g_object_unref(printer_
);
212 void PrintDialogGtk2::UseDefaultSettings() {
213 DCHECK(!page_setup_
);
216 // |gtk_settings_| is a new copy.
218 gtk_print_settings_copy(g_last_used_settings
.Get().settings());
219 page_setup_
= gtk_page_setup_new();
221 PrintSettings settings
;
222 InitPrintSettings(&settings
);
225 bool PrintDialogGtk2::UpdateSettings(printing::PrintSettings
* settings
) {
226 if (!gtk_settings_
) {
228 gtk_print_settings_copy(g_last_used_settings
.Get().settings());
231 scoped_ptr
<GtkPrinterList
> printer_list(new GtkPrinterList
);
232 printer_
= printer_list
->GetPrinterWithName(
233 base::UTF16ToUTF8(settings
->device_name()));
235 g_object_ref(printer_
);
236 gtk_print_settings_set_printer(gtk_settings_
,
237 gtk_printer_get_name(printer_
));
239 page_setup_
= gtk_printer_get_default_page_size(printer_
);
243 gtk_print_settings_set_n_copies(gtk_settings_
, settings
->copies());
244 gtk_print_settings_set_collate(gtk_settings_
, settings
->collate());
246 #if defined(USE_CUPS)
247 std::string color_value
;
248 std::string color_setting_name
;
249 printing::GetColorModelForMode(settings
->color(), &color_setting_name
,
251 gtk_print_settings_set(gtk_settings_
, color_setting_name
.c_str(),
252 color_value
.c_str());
254 if (settings
->duplex_mode() != printing::UNKNOWN_DUPLEX_MODE
) {
255 const char* cups_duplex_mode
= NULL
;
256 switch (settings
->duplex_mode()) {
257 case printing::LONG_EDGE
:
258 cups_duplex_mode
= kDuplexNoTumble
;
260 case printing::SHORT_EDGE
:
261 cups_duplex_mode
= kDuplexTumble
;
263 case printing::SIMPLEX
:
264 cups_duplex_mode
= kDuplexNone
;
266 default: // UNKNOWN_DUPLEX_MODE
270 gtk_print_settings_set(gtk_settings_
, kCUPSDuplex
, cups_duplex_mode
);
274 page_setup_
= gtk_page_setup_new();
276 if (page_setup_
&& !settings
->requested_media().IsDefault()) {
277 const PrintSettings::RequestedMedia
& requested_media
=
278 settings
->requested_media();
279 GtkPaperSize
* gtk_current_paper_size
=
280 gtk_page_setup_get_paper_size(page_setup_
);
281 if (!PaperSizeMatch(gtk_current_paper_size
, requested_media
,
282 true /*fuzzy_match*/)) {
283 GList
* gtk_paper_sizes
=
284 gtk_paper_size_get_paper_sizes(false /*include_custom*/);
285 if (gtk_paper_sizes
) {
286 GtkPaperSize
* matching_gtk_paper_size
=
287 FindPaperSizeMatch(gtk_paper_sizes
, requested_media
);
288 if (matching_gtk_paper_size
) {
289 VLOG(1) << "Using listed paper size";
290 gtk_page_setup_set_paper_size(page_setup_
, matching_gtk_paper_size
);
292 VLOG(1) << "Using custom paper size";
293 GtkPaperSize
* custom_size
= gtk_paper_size_new_custom(
294 requested_media
.vendor_id
.c_str(),
295 requested_media
.vendor_id
.c_str(),
296 requested_media
.size_microns
.width() / kMicronsInMm
,
297 requested_media
.size_microns
.height() / kMicronsInMm
,
299 gtk_page_setup_set_paper_size(page_setup_
, custom_size
);
300 gtk_paper_size_free(custom_size
);
302 #if GTK_CHECK_VERSION(2,28,0)
303 g_list_free_full(gtk_paper_sizes
,
304 reinterpret_cast<GDestroyNotify
>(gtk_paper_size_free
));
306 g_list_foreach(gtk_paper_sizes
,
307 reinterpret_cast<GFunc
>(gtk_paper_size_free
), NULL
);
308 g_list_free(gtk_paper_sizes
);
312 VLOG(1) << "Using default paper size";
316 gtk_print_settings_set_orientation(
318 settings
->landscape() ? GTK_PAGE_ORIENTATION_LANDSCAPE
:
319 GTK_PAGE_ORIENTATION_PORTRAIT
);
321 InitPrintSettings(settings
);
325 void PrintDialogGtk2::ShowDialog(
326 gfx::NativeView parent_view
,
328 const PrintingContextLinux::PrintSettingsCallback
& callback
) {
329 callback_
= callback
;
331 dialog_
= gtk_print_unix_dialog_new(NULL
, NULL
);
332 libgtk2ui::SetGtkTransientForAura(dialog_
, parent_view
);
334 parent_view
->AddObserver(this);
335 g_signal_connect(dialog_
, "delete-event",
336 G_CALLBACK(gtk_widget_hide_on_delete
), NULL
);
339 // Set modal so user cannot focus the same tab and press print again.
340 gtk_window_set_modal(GTK_WINDOW(dialog_
), TRUE
);
342 // Since we only generate PDF, only show printers that support PDF.
343 // TODO(thestig) Add more capabilities to support?
344 GtkPrintCapabilities cap
= static_cast<GtkPrintCapabilities
>(
345 GTK_PRINT_CAPABILITY_GENERATE_PDF
|
346 GTK_PRINT_CAPABILITY_PAGE_SET
|
347 GTK_PRINT_CAPABILITY_COPIES
|
348 GTK_PRINT_CAPABILITY_COLLATE
|
349 GTK_PRINT_CAPABILITY_REVERSE
);
350 gtk_print_unix_dialog_set_manual_capabilities(GTK_PRINT_UNIX_DIALOG(dialog_
),
352 gtk_print_unix_dialog_set_embed_page_setup(GTK_PRINT_UNIX_DIALOG(dialog_
),
354 gtk_print_unix_dialog_set_support_selection(GTK_PRINT_UNIX_DIALOG(dialog_
),
356 gtk_print_unix_dialog_set_has_selection(GTK_PRINT_UNIX_DIALOG(dialog_
),
358 gtk_print_unix_dialog_set_settings(GTK_PRINT_UNIX_DIALOG(dialog_
),
360 g_signal_connect(dialog_
, "response", G_CALLBACK(OnResponseThunk
), this);
361 gtk_widget_show(dialog_
);
363 // We need to call gtk_window_present after making the widgets visible to make
364 // sure window gets correctly raised and gets focus.
365 int time
= views::X11DesktopHandler::get()->wm_user_time_ms();
366 gtk_window_present_with_time(GTK_WINDOW(dialog_
), time
);
369 void PrintDialogGtk2::PrintDocument(const printing::MetafilePlayer
& metafile
,
370 const base::string16
& document_name
) {
371 // This runs on the print worker thread, does not block the UI thread.
372 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI
));
374 // The document printing tasks can outlive the PrintingContext that created
378 bool success
= base::CreateTemporaryFile(&path_to_pdf_
);
382 file
.Initialize(path_to_pdf_
,
383 base::File::FLAG_CREATE_ALWAYS
| base::File::FLAG_WRITE
);
384 success
= metafile
.SaveTo(&file
);
387 base::DeleteFile(path_to_pdf_
, false);
391 LOG(ERROR
) << "Saving metafile failed";
392 // Matches AddRef() above.
397 // No errors, continue printing.
398 BrowserThread::PostTask(
401 base::Bind(&PrintDialogGtk2::SendDocumentToPrinter
, this, document_name
));
404 void PrintDialogGtk2::AddRefToDialog() {
408 void PrintDialogGtk2::ReleaseDialog() {
412 void PrintDialogGtk2::OnResponse(GtkWidget
* dialog
, int response_id
) {
413 int num_matched_handlers
= g_signal_handlers_disconnect_by_func(
414 dialog_
, reinterpret_cast<gpointer
>(&OnResponseThunk
), this);
415 CHECK_EQ(1, num_matched_handlers
);
417 gtk_widget_hide(dialog_
);
419 switch (response_id
) {
420 case GTK_RESPONSE_OK
: {
422 g_object_unref(gtk_settings_
);
423 gtk_settings_
= gtk_print_unix_dialog_get_settings(
424 GTK_PRINT_UNIX_DIALOG(dialog_
));
427 g_object_unref(printer_
);
428 printer_
= gtk_print_unix_dialog_get_selected_printer(
429 GTK_PRINT_UNIX_DIALOG(dialog_
));
430 g_object_ref(printer_
);
433 g_object_unref(page_setup_
);
434 page_setup_
= gtk_print_unix_dialog_get_page_setup(
435 GTK_PRINT_UNIX_DIALOG(dialog_
));
436 g_object_ref(page_setup_
);
438 // Handle page ranges.
439 PageRanges ranges_vector
;
441 bool print_selection_only
= false;
442 switch (gtk_print_settings_get_print_pages(gtk_settings_
)) {
443 case GTK_PRINT_PAGES_RANGES
: {
444 GtkPageRange
* gtk_range
=
445 gtk_print_settings_get_page_ranges(gtk_settings_
, &num_ranges
);
447 for (int i
= 0; i
< num_ranges
; ++i
) {
448 printing::PageRange range
;
449 range
.from
= gtk_range
[i
].start
;
450 range
.to
= gtk_range
[i
].end
;
451 ranges_vector
.push_back(range
);
457 case GTK_PRINT_PAGES_SELECTION
:
458 print_selection_only
= true;
460 case GTK_PRINT_PAGES_ALL
:
461 // Leave |ranges_vector| empty to indicate print all pages.
463 case GTK_PRINT_PAGES_CURRENT
:
469 PrintSettings settings
;
470 settings
.set_ranges(ranges_vector
);
471 settings
.set_selection_only(print_selection_only
);
472 InitPrintSettingsGtk(gtk_settings_
, page_setup_
, &settings
);
473 context_
->InitWithSettings(settings
);
474 callback_
.Run(PrintingContextLinux::OK
);
478 case GTK_RESPONSE_DELETE_EVENT
: // Fall through.
479 case GTK_RESPONSE_CANCEL
: {
480 callback_
.Run(PrintingContextLinux::CANCEL
);
484 case GTK_RESPONSE_APPLY
:
491 void PrintDialogGtk2::SendDocumentToPrinter(
492 const base::string16
& document_name
) {
493 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
495 // If |printer_| is NULL then somehow the GTK printer list changed out under
496 // us. In which case, just bail out.
498 // Matches AddRef() in PrintDocument();
503 // Save the settings for next time.
504 g_last_used_settings
.Get().SetLastUsedSettings(gtk_settings_
);
506 GtkPrintJob
* print_job
= gtk_print_job_new(
507 base::UTF16ToUTF8(document_name
).c_str(),
511 gtk_print_job_set_source_file(print_job
, path_to_pdf_
.value().c_str(), NULL
);
512 gtk_print_job_send(print_job
, OnJobCompletedThunk
, this, NULL
);
516 void PrintDialogGtk2::OnJobCompletedThunk(GtkPrintJob
* print_job
,
519 static_cast<PrintDialogGtk2
*>(user_data
)->OnJobCompleted(print_job
, error
);
522 void PrintDialogGtk2::OnJobCompleted(GtkPrintJob
* print_job
, GError
* error
) {
524 LOG(ERROR
) << "Printing failed: " << error
->message
;
526 g_object_unref(print_job
);
527 base::FileUtilProxy::DeleteFile(
528 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE).get(),
531 base::FileUtilProxy::StatusCallback());
532 // Printing finished. Matches AddRef() in PrintDocument();
536 void PrintDialogGtk2::InitPrintSettings(PrintSettings
* settings
) {
537 InitPrintSettingsGtk(gtk_settings_
, page_setup_
, settings
);
538 context_
->InitWithSettings(*settings
);
541 void PrintDialogGtk2::OnWindowDestroying(aura::Window
* window
) {
542 DCHECK_EQ(libgtk2ui::GetAuraTransientParent(dialog_
), window
);
544 libgtk2ui::ClearAuraTransientParent(dialog_
);
545 window
->RemoveObserver(this);