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