• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "chrome/browser/ui/libgtk2ui/print_dialog_gtk2.h"
6 
7 #include <gtk/gtkunixprint.h>
8 
9 #include <algorithm>
10 #include <cmath>
11 #include <string>
12 #include <vector>
13 
14 #include "base/bind.h"
15 #include "base/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/message_loop/message_loop_proxy.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/values.h"
22 #include "chrome/browser/ui/libgtk2ui/gtk2_util.h"
23 #include "chrome/browser/ui/libgtk2ui/printing_gtk2_util.h"
24 #include "printing/metafile.h"
25 #include "printing/print_job_constants.h"
26 #include "printing/print_settings.h"
27 #include "ui/aura/window.h"
28 
29 using content::BrowserThread;
30 using printing::PageRanges;
31 using printing::PrintSettings;
32 
33 namespace {
34 
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";
40 
41 int kPaperSizeTresholdMicrons = 100;
42 int kMicronsInMm = 1000;
43 
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.
PaperSizeMatch(GtkPaperSize * gtk_paper_size,const PrintSettings::RequestedMedia & media,bool fuzzy_match)48 bool PaperSizeMatch(GtkPaperSize* gtk_paper_size,
49                     const PrintSettings::RequestedMedia& media,
50                     bool fuzzy_match) {
51   if (!gtk_paper_size) {
52     return false;
53   }
54   gfx::Size paper_size_microns(
55       static_cast<int>(gtk_paper_size_get_width(gtk_paper_size, GTK_UNIT_MM) *
56                        kMicronsInMm + 0.5),
57       static_cast<int>(gtk_paper_size_get_height(gtk_paper_size, GTK_UNIT_MM) *
58                        kMicronsInMm + 0.5));
59   int diff = std::max(
60       std::abs(paper_size_microns.width() - media.size_microns.width()),
61       std::abs(paper_size_microns.height() - media.size_microns.height()));
62   if (fuzzy_match) {
63     return diff <= kPaperSizeTresholdMicrons;
64   }
65   return !media.vendor_id.empty() &&
66          media.vendor_id == gtk_paper_size_get_ppd_name(gtk_paper_size) &&
67          diff <= kPaperSizeTresholdMicrons;
68 }
69 
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
72 // match found.
FindPaperSizeMatch(GList * gtk_paper_sizes,const PrintSettings::RequestedMedia & media)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;
80     }
81     if (!first_fuzzy_match && PaperSizeMatch(gtk_paper_size, media, true)) {
82       first_fuzzy_match = gtk_paper_size;
83     }
84   }
85   return first_fuzzy_match;
86 }
87 
88 class StickyPrintSettingGtk {
89  public:
StickyPrintSettingGtk()90   StickyPrintSettingGtk() : last_used_settings_(gtk_print_settings_new()) {
91   }
~StickyPrintSettingGtk()92   ~StickyPrintSettingGtk() {
93     NOTREACHED();  // Intended to be used with a Leaky LazyInstance.
94   }
95 
settings()96   GtkPrintSettings* settings() {
97     return last_used_settings_;
98   }
99 
SetLastUsedSettings(GtkPrintSettings * 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);
104   }
105 
106  private:
107   GtkPrintSettings* last_used_settings_;
108 
109   DISALLOW_COPY_AND_ASSIGN(StickyPrintSettingGtk);
110 };
111 
112 base::LazyInstance<StickyPrintSettingGtk>::Leaky g_last_used_settings =
113     LAZY_INSTANCE_INITIALIZER;
114 
115 // Helper class to track GTK printers.
116 class GtkPrinterList {
117  public:
GtkPrinterList()118   GtkPrinterList() : default_printer_(NULL) {
119     gtk_enumerate_printers(SetPrinter, this, NULL, TRUE);
120   }
121 
~GtkPrinterList()122   ~GtkPrinterList() {
123     for (std::vector<GtkPrinter*>::iterator it = printers_.begin();
124          it < printers_.end(); ++it) {
125       g_object_unref(*it);
126     }
127   }
128 
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.
default_printer()131   GtkPrinter* default_printer() {
132     return default_printer_;
133   }
134 
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'.
GetPrinterWithName(const std::string & name)138   GtkPrinter* GetPrinterWithName(const std::string& name) {
139     if (name.empty())
140       return NULL;
141 
142     for (std::vector<GtkPrinter*>::iterator it = printers_.begin();
143          it < printers_.end(); ++it) {
144       if (gtk_printer_get_name(*it) == name) {
145         return *it;
146       }
147     }
148 
149     return NULL;
150   }
151 
152  private:
153   // Callback function used by gtk_enumerate_printers() to get all printer.
SetPrinter(GtkPrinter * printer,gpointer data)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;
158 
159     g_object_ref(printer);
160     printer_list->printers_.push_back(printer);
161 
162     return FALSE;
163   }
164 
165   std::vector<GtkPrinter*> printers_;
166   GtkPrinter* default_printer_;
167 };
168 
169 }  // namespace
170 
171 // static
CreatePrintDialog(PrintingContextLinux * context)172 printing::PrintDialogGtkInterface* PrintDialogGtk2::CreatePrintDialog(
173     PrintingContextLinux* context) {
174   DCHECK_CURRENTLY_ON(BrowserThread::UI);
175   return new PrintDialogGtk2(context);
176 }
177 
PrintDialogGtk2(PrintingContextLinux * context)178 PrintDialogGtk2::PrintDialogGtk2(PrintingContextLinux* context)
179     : context_(context),
180       dialog_(NULL),
181       gtk_settings_(NULL),
182       page_setup_(NULL),
183       printer_(NULL) {
184 }
185 
~PrintDialogGtk2()186 PrintDialogGtk2::~PrintDialogGtk2() {
187   DCHECK_CURRENTLY_ON(BrowserThread::UI);
188 
189   if (dialog_) {
190     aura::Window* parent = libgtk2ui::GetAuraTransientParent(dialog_);
191     if (parent) {
192       parent->RemoveObserver(this);
193       libgtk2ui::ClearAuraTransientParent(dialog_);
194     }
195     gtk_widget_destroy(dialog_);
196     dialog_ = NULL;
197   }
198   if (gtk_settings_) {
199     g_object_unref(gtk_settings_);
200     gtk_settings_ = NULL;
201   }
202   if (page_setup_) {
203     g_object_unref(page_setup_);
204     page_setup_ = NULL;
205   }
206   if (printer_) {
207     g_object_unref(printer_);
208     printer_ = NULL;
209   }
210 }
211 
UseDefaultSettings()212 void PrintDialogGtk2::UseDefaultSettings() {
213   DCHECK(!page_setup_);
214   DCHECK(!printer_);
215 
216   // |gtk_settings_| is a new copy.
217   gtk_settings_ =
218       gtk_print_settings_copy(g_last_used_settings.Get().settings());
219   page_setup_ = gtk_page_setup_new();
220 
221   PrintSettings settings;
222   InitPrintSettings(&settings);
223 }
224 
UpdateSettings(printing::PrintSettings * settings)225 bool PrintDialogGtk2::UpdateSettings(printing::PrintSettings* settings) {
226   if (!gtk_settings_) {
227     gtk_settings_ =
228         gtk_print_settings_copy(g_last_used_settings.Get().settings());
229   }
230 
231   scoped_ptr<GtkPrinterList> printer_list(new GtkPrinterList);
232   printer_ = printer_list->GetPrinterWithName(
233       base::UTF16ToUTF8(settings->device_name()));
234   if (printer_) {
235     g_object_ref(printer_);
236     gtk_print_settings_set_printer(gtk_settings_,
237                                    gtk_printer_get_name(printer_));
238     if (!page_setup_) {
239       page_setup_ = gtk_printer_get_default_page_size(printer_);
240     }
241   }
242 
243   gtk_print_settings_set_n_copies(gtk_settings_, settings->copies());
244   gtk_print_settings_set_collate(gtk_settings_, settings->collate());
245 
246 #if defined(USE_CUPS)
247   std::string color_value;
248   std::string color_setting_name;
249   printing::GetColorModelForMode(settings->color(), &color_setting_name,
250                                  &color_value);
251   gtk_print_settings_set(gtk_settings_, color_setting_name.c_str(),
252                          color_value.c_str());
253 
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;
259         break;
260       case printing::SHORT_EDGE:
261         cups_duplex_mode = kDuplexTumble;
262         break;
263       case printing::SIMPLEX:
264         cups_duplex_mode = kDuplexNone;
265         break;
266       default:  // UNKNOWN_DUPLEX_MODE
267         NOTREACHED();
268         break;
269     }
270     gtk_print_settings_set(gtk_settings_, kCUPSDuplex, cups_duplex_mode);
271   }
272 #endif
273   if (!page_setup_)
274     page_setup_ = gtk_page_setup_new();
275 
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);
291         } else {
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,
298               GTK_UNIT_MM);
299           gtk_page_setup_set_paper_size(page_setup_, custom_size);
300           gtk_paper_size_free(custom_size);
301         }
302         g_list_free_full(gtk_paper_sizes,
303                          reinterpret_cast<GDestroyNotify>(gtk_paper_size_free));
304       }
305     } else {
306       VLOG(1) << "Using default paper size";
307     }
308   }
309 
310   gtk_print_settings_set_orientation(
311       gtk_settings_,
312       settings->landscape() ? GTK_PAGE_ORIENTATION_LANDSCAPE :
313                               GTK_PAGE_ORIENTATION_PORTRAIT);
314 
315   InitPrintSettings(settings);
316   return true;
317 }
318 
ShowDialog(gfx::NativeView parent_view,bool has_selection,const PrintingContextLinux::PrintSettingsCallback & callback)319 void PrintDialogGtk2::ShowDialog(
320     gfx::NativeView parent_view,
321     bool has_selection,
322     const PrintingContextLinux::PrintSettingsCallback& callback) {
323   callback_ = callback;
324 
325   dialog_ = gtk_print_unix_dialog_new(NULL, NULL);
326   libgtk2ui::SetGtkTransientForAura(dialog_, parent_view);
327   if (parent_view)
328     parent_view->AddObserver(this);
329   g_signal_connect(dialog_, "delete-event",
330                    G_CALLBACK(gtk_widget_hide_on_delete), NULL);
331 
332 
333   // Set modal so user cannot focus the same tab and press print again.
334   gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE);
335 
336   // Since we only generate PDF, only show printers that support PDF.
337   // TODO(thestig) Add more capabilities to support?
338   GtkPrintCapabilities cap = static_cast<GtkPrintCapabilities>(
339       GTK_PRINT_CAPABILITY_GENERATE_PDF |
340       GTK_PRINT_CAPABILITY_PAGE_SET |
341       GTK_PRINT_CAPABILITY_COPIES |
342       GTK_PRINT_CAPABILITY_COLLATE |
343       GTK_PRINT_CAPABILITY_REVERSE);
344   gtk_print_unix_dialog_set_manual_capabilities(GTK_PRINT_UNIX_DIALOG(dialog_),
345                                                 cap);
346   gtk_print_unix_dialog_set_embed_page_setup(GTK_PRINT_UNIX_DIALOG(dialog_),
347                                              TRUE);
348   gtk_print_unix_dialog_set_support_selection(GTK_PRINT_UNIX_DIALOG(dialog_),
349                                               TRUE);
350   gtk_print_unix_dialog_set_has_selection(GTK_PRINT_UNIX_DIALOG(dialog_),
351                                           has_selection);
352   gtk_print_unix_dialog_set_settings(GTK_PRINT_UNIX_DIALOG(dialog_),
353                                      gtk_settings_);
354   g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseThunk), this);
355   gtk_widget_show(dialog_);
356 }
357 
PrintDocument(const printing::Metafile * metafile,const base::string16 & document_name)358 void PrintDialogGtk2::PrintDocument(const printing::Metafile* metafile,
359                                    const base::string16& document_name) {
360   // This runs on the print worker thread, does not block the UI thread.
361   DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
362 
363   // The document printing tasks can outlive the PrintingContext that created
364   // this dialog.
365   AddRef();
366 
367   bool error = false;
368   if (!base::CreateTemporaryFile(&path_to_pdf_)) {
369     LOG(ERROR) << "Creating temporary file failed";
370     error = true;
371   }
372 
373   if (!error && !metafile->SaveTo(path_to_pdf_)) {
374     LOG(ERROR) << "Saving metafile failed";
375     base::DeleteFile(path_to_pdf_, false);
376     error = true;
377   }
378 
379   if (error) {
380     // Matches AddRef() above.
381     Release();
382   } else {
383     // No errors, continue printing.
384     BrowserThread::PostTask(
385         BrowserThread::UI, FROM_HERE,
386         base::Bind(&PrintDialogGtk2::SendDocumentToPrinter, this,
387                    document_name));
388   }
389 }
390 
AddRefToDialog()391 void PrintDialogGtk2::AddRefToDialog() {
392   AddRef();
393 }
394 
ReleaseDialog()395 void PrintDialogGtk2::ReleaseDialog() {
396   Release();
397 }
398 
OnResponse(GtkWidget * dialog,int response_id)399 void PrintDialogGtk2::OnResponse(GtkWidget* dialog, int response_id) {
400   int num_matched_handlers = g_signal_handlers_disconnect_by_func(
401       dialog_, reinterpret_cast<gpointer>(&OnResponseThunk), this);
402   CHECK_EQ(1, num_matched_handlers);
403 
404   gtk_widget_hide(dialog_);
405 
406   switch (response_id) {
407     case GTK_RESPONSE_OK: {
408       if (gtk_settings_)
409         g_object_unref(gtk_settings_);
410       gtk_settings_ = gtk_print_unix_dialog_get_settings(
411           GTK_PRINT_UNIX_DIALOG(dialog_));
412 
413       if (printer_)
414         g_object_unref(printer_);
415       printer_ = gtk_print_unix_dialog_get_selected_printer(
416           GTK_PRINT_UNIX_DIALOG(dialog_));
417       g_object_ref(printer_);
418 
419       if (page_setup_)
420         g_object_unref(page_setup_);
421       page_setup_ = gtk_print_unix_dialog_get_page_setup(
422           GTK_PRINT_UNIX_DIALOG(dialog_));
423       g_object_ref(page_setup_);
424 
425       // Handle page ranges.
426       PageRanges ranges_vector;
427       gint num_ranges;
428       bool print_selection_only = false;
429       switch (gtk_print_settings_get_print_pages(gtk_settings_)) {
430         case GTK_PRINT_PAGES_RANGES: {
431           GtkPageRange* gtk_range =
432               gtk_print_settings_get_page_ranges(gtk_settings_, &num_ranges);
433           if (gtk_range) {
434             for (int i = 0; i < num_ranges; ++i) {
435               printing::PageRange range;
436               range.from = gtk_range[i].start;
437               range.to = gtk_range[i].end;
438               ranges_vector.push_back(range);
439             }
440             g_free(gtk_range);
441           }
442           break;
443         }
444         case GTK_PRINT_PAGES_SELECTION:
445           print_selection_only = true;
446           break;
447         case GTK_PRINT_PAGES_ALL:
448           // Leave |ranges_vector| empty to indicate print all pages.
449           break;
450         case GTK_PRINT_PAGES_CURRENT:
451         default:
452           NOTREACHED();
453           break;
454       }
455 
456       PrintSettings settings;
457       settings.set_ranges(ranges_vector);
458       settings.set_selection_only(print_selection_only);
459       InitPrintSettingsGtk(gtk_settings_, page_setup_, &settings);
460       context_->InitWithSettings(settings);
461       callback_.Run(PrintingContextLinux::OK);
462       callback_.Reset();
463       return;
464     }
465     case GTK_RESPONSE_DELETE_EVENT:  // Fall through.
466     case GTK_RESPONSE_CANCEL: {
467       callback_.Run(PrintingContextLinux::CANCEL);
468       callback_.Reset();
469       return;
470     }
471     case GTK_RESPONSE_APPLY:
472     default: {
473       NOTREACHED();
474     }
475   }
476 }
477 
SendDocumentToPrinter(const base::string16 & document_name)478 void PrintDialogGtk2::SendDocumentToPrinter(
479     const base::string16& document_name) {
480   DCHECK_CURRENTLY_ON(BrowserThread::UI);
481 
482   // If |printer_| is NULL then somehow the GTK printer list changed out under
483   // us. In which case, just bail out.
484   if (!printer_) {
485     // Matches AddRef() in PrintDocument();
486     Release();
487     return;
488   }
489 
490   // Save the settings for next time.
491   g_last_used_settings.Get().SetLastUsedSettings(gtk_settings_);
492 
493   GtkPrintJob* print_job = gtk_print_job_new(
494       base::UTF16ToUTF8(document_name).c_str(),
495       printer_,
496       gtk_settings_,
497       page_setup_);
498   gtk_print_job_set_source_file(print_job, path_to_pdf_.value().c_str(), NULL);
499   gtk_print_job_send(print_job, OnJobCompletedThunk, this, NULL);
500 }
501 
502 // static
OnJobCompletedThunk(GtkPrintJob * print_job,gpointer user_data,GError * error)503 void PrintDialogGtk2::OnJobCompletedThunk(GtkPrintJob* print_job,
504                                          gpointer user_data,
505                                          GError* error) {
506   static_cast<PrintDialogGtk2*>(user_data)->OnJobCompleted(print_job, error);
507 }
508 
OnJobCompleted(GtkPrintJob * print_job,GError * error)509 void PrintDialogGtk2::OnJobCompleted(GtkPrintJob* print_job, GError* error) {
510   if (error)
511     LOG(ERROR) << "Printing failed: " << error->message;
512   if (print_job)
513     g_object_unref(print_job);
514   base::FileUtilProxy::DeleteFile(
515       BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE).get(),
516       path_to_pdf_,
517       false,
518       base::FileUtilProxy::StatusCallback());
519   // Printing finished. Matches AddRef() in PrintDocument();
520   Release();
521 }
522 
InitPrintSettings(PrintSettings * settings)523 void PrintDialogGtk2::InitPrintSettings(PrintSettings* settings) {
524   InitPrintSettingsGtk(gtk_settings_, page_setup_, settings);
525   context_->InitWithSettings(*settings);
526 }
527 
OnWindowDestroying(aura::Window * window)528 void PrintDialogGtk2::OnWindowDestroying(aura::Window* window) {
529   DCHECK_EQ(libgtk2ui::GetAuraTransientParent(dialog_), window);
530 
531   libgtk2ui::ClearAuraTransientParent(dialog_);
532   window->RemoveObserver(this);
533   Release();
534 }
535