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