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. 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. 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: 90 StickyPrintSettingGtk() : last_used_settings_(gtk_print_settings_new()) { 91 } 92 ~StickyPrintSettingGtk() { 93 NOTREACHED(); // Intended to be used with a Leaky LazyInstance. 94 } 95 96 GtkPrintSettings* settings() { 97 return last_used_settings_; 98 } 99 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: 118 GtkPrinterList() : default_printer_(NULL) { 119 gtk_enumerate_printers(SetPrinter, this, NULL, TRUE); 120 } 121 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. 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'. 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. 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 172 printing::PrintDialogGtkInterface* PrintDialogGtk2::CreatePrintDialog( 173 PrintingContextLinux* context) { 174 DCHECK_CURRENTLY_ON(BrowserThread::UI); 175 return new PrintDialogGtk2(context); 176 } 177 178 PrintDialogGtk2::PrintDialogGtk2(PrintingContextLinux* context) 179 : context_(context), 180 dialog_(NULL), 181 gtk_settings_(NULL), 182 page_setup_(NULL), 183 printer_(NULL) { 184 } 185 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 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 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 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 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 391 void PrintDialogGtk2::AddRefToDialog() { 392 AddRef(); 393 } 394 395 void PrintDialogGtk2::ReleaseDialog() { 396 Release(); 397 } 398 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 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 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 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 523 void PrintDialogGtk2::InitPrintSettings(PrintSettings* settings) { 524 InitPrintSettingsGtk(gtk_settings_, page_setup_, settings); 525 context_->InitWithSettings(*settings); 526 } 527 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