1 // Copyright (c) 2012 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/printing/print_dialog_gtk.h" 6 7 #include <gtk/gtkunixprint.h> 8 9 #include <string> 10 #include <vector> 11 12 #include "base/bind.h" 13 #include "base/file_util.h" 14 #include "base/files/file_util_proxy.h" 15 #include "base/lazy_instance.h" 16 #include "base/logging.h" 17 #include "base/message_loop/message_loop_proxy.h" 18 #include "base/strings/utf_string_conversions.h" 19 #include "base/values.h" 20 #include "printing/metafile.h" 21 #include "printing/print_job_constants.h" 22 #include "printing/print_settings.h" 23 #include "printing/print_settings_initializer_gtk.h" 24 25 using content::BrowserThread; 26 using printing::PageRanges; 27 using printing::PrintSettings; 28 29 namespace { 30 31 // CUPS Duplex attribute and values. 32 const char kCUPSDuplex[] = "cups-Duplex"; 33 const char kDuplexNone[] = "None"; 34 const char kDuplexTumble[] = "DuplexTumble"; 35 const char kDuplexNoTumble[] = "DuplexNoTumble"; 36 37 class StickyPrintSettingGtk { 38 public: 39 StickyPrintSettingGtk() : last_used_settings_(gtk_print_settings_new()) { 40 } 41 ~StickyPrintSettingGtk() { 42 NOTREACHED(); // Intended to be used with a Leaky LazyInstance. 43 } 44 45 GtkPrintSettings* settings() { 46 return last_used_settings_; 47 } 48 49 void SetLastUsedSettings(GtkPrintSettings* settings) { 50 DCHECK(last_used_settings_); 51 g_object_unref(last_used_settings_); 52 last_used_settings_ = gtk_print_settings_copy(settings); 53 } 54 55 private: 56 GtkPrintSettings* last_used_settings_; 57 58 DISALLOW_COPY_AND_ASSIGN(StickyPrintSettingGtk); 59 }; 60 61 base::LazyInstance<StickyPrintSettingGtk>::Leaky g_last_used_settings = 62 LAZY_INSTANCE_INITIALIZER; 63 64 // Helper class to track GTK printers. 65 class GtkPrinterList { 66 public: 67 GtkPrinterList() : default_printer_(NULL) { 68 gtk_enumerate_printers(SetPrinter, this, NULL, TRUE); 69 } 70 71 ~GtkPrinterList() { 72 for (std::vector<GtkPrinter*>::iterator it = printers_.begin(); 73 it < printers_.end(); ++it) { 74 g_object_unref(*it); 75 } 76 } 77 78 // Can return NULL if there's no default printer. E.g. Printer on a laptop 79 // is "home_printer", but the laptop is at work. 80 GtkPrinter* default_printer() { 81 return default_printer_; 82 } 83 84 // Can return NULL if the printer cannot be found due to: 85 // - Printer list out of sync with printer dialog UI. 86 // - Querying for non-existant printers like 'Print to PDF'. 87 GtkPrinter* GetPrinterWithName(const std::string& name) { 88 if (name.empty()) 89 return NULL; 90 91 for (std::vector<GtkPrinter*>::iterator it = printers_.begin(); 92 it < printers_.end(); ++it) { 93 if (gtk_printer_get_name(*it) == name) { 94 return *it; 95 } 96 } 97 98 return NULL; 99 } 100 101 private: 102 // Callback function used by gtk_enumerate_printers() to get all printer. 103 static gboolean SetPrinter(GtkPrinter* printer, gpointer data) { 104 GtkPrinterList* printer_list = reinterpret_cast<GtkPrinterList*>(data); 105 if (gtk_printer_is_default(printer)) 106 printer_list->default_printer_ = printer; 107 108 g_object_ref(printer); 109 printer_list->printers_.push_back(printer); 110 111 return FALSE; 112 } 113 114 std::vector<GtkPrinter*> printers_; 115 GtkPrinter* default_printer_; 116 }; 117 118 } // namespace 119 120 // static 121 printing::PrintDialogGtkInterface* PrintDialogGtk::CreatePrintDialog( 122 PrintingContextGtk* context) { 123 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 124 return new PrintDialogGtk(context); 125 } 126 127 PrintDialogGtk::PrintDialogGtk(PrintingContextGtk* context) 128 : context_(context), 129 dialog_(NULL), 130 gtk_settings_(NULL), 131 page_setup_(NULL), 132 printer_(NULL) { 133 } 134 135 PrintDialogGtk::~PrintDialogGtk() { 136 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 137 138 if (dialog_) { 139 gtk_widget_destroy(dialog_); 140 dialog_ = NULL; 141 } 142 if (gtk_settings_) { 143 g_object_unref(gtk_settings_); 144 gtk_settings_ = NULL; 145 } 146 if (page_setup_) { 147 g_object_unref(page_setup_); 148 page_setup_ = NULL; 149 } 150 if (printer_) { 151 g_object_unref(printer_); 152 printer_ = NULL; 153 } 154 } 155 156 void PrintDialogGtk::UseDefaultSettings() { 157 DCHECK(!page_setup_); 158 DCHECK(!printer_); 159 160 // |gtk_settings_| is a new copy. 161 gtk_settings_ = 162 gtk_print_settings_copy(g_last_used_settings.Get().settings()); 163 page_setup_ = gtk_page_setup_new(); 164 165 PrintSettings settings; 166 InitPrintSettings(&settings); 167 } 168 169 bool PrintDialogGtk::UpdateSettings(printing::PrintSettings* settings) { 170 if (!gtk_settings_) { 171 gtk_settings_ = 172 gtk_print_settings_copy(g_last_used_settings.Get().settings()); 173 } 174 175 scoped_ptr<GtkPrinterList> printer_list(new GtkPrinterList); 176 printer_ = printer_list->GetPrinterWithName( 177 UTF16ToUTF8(settings->device_name())); 178 if (printer_) { 179 g_object_ref(printer_); 180 gtk_print_settings_set_printer(gtk_settings_, 181 gtk_printer_get_name(printer_)); 182 if (!page_setup_) { 183 page_setup_ = gtk_printer_get_default_page_size(printer_); 184 } 185 } 186 187 gtk_print_settings_set_n_copies(gtk_settings_, settings->copies()); 188 gtk_print_settings_set_collate(gtk_settings_, settings->collate()); 189 190 #if defined(USE_CUPS) 191 std::string color_value; 192 std::string color_setting_name; 193 printing::GetColorModelForMode(settings->color(), &color_setting_name, 194 &color_value); 195 gtk_print_settings_set(gtk_settings_, color_setting_name.c_str(), 196 color_value.c_str()); 197 198 if (settings->duplex_mode() != printing::UNKNOWN_DUPLEX_MODE) { 199 const char* cups_duplex_mode = NULL; 200 switch (settings->duplex_mode()) { 201 case printing::LONG_EDGE: 202 cups_duplex_mode = kDuplexNoTumble; 203 break; 204 case printing::SHORT_EDGE: 205 cups_duplex_mode = kDuplexTumble; 206 break; 207 case printing::SIMPLEX: 208 cups_duplex_mode = kDuplexNone; 209 break; 210 default: // UNKNOWN_DUPLEX_MODE 211 NOTREACHED(); 212 break; 213 } 214 gtk_print_settings_set(gtk_settings_, kCUPSDuplex, cups_duplex_mode); 215 } 216 #endif 217 if (!page_setup_) 218 page_setup_ = gtk_page_setup_new(); 219 220 gtk_print_settings_set_orientation( 221 gtk_settings_, 222 settings->landscape() ? GTK_PAGE_ORIENTATION_LANDSCAPE : 223 GTK_PAGE_ORIENTATION_PORTRAIT); 224 225 InitPrintSettings(settings); 226 return true; 227 } 228 229 void PrintDialogGtk::ShowDialog( 230 gfx::NativeView parent_view, 231 bool has_selection, 232 const PrintingContextGtk::PrintSettingsCallback& callback) { 233 callback_ = callback; 234 235 GtkWindow* parent = GTK_WINDOW(gtk_widget_get_toplevel(parent_view)); 236 // TODO(estade): We need a window title here. 237 dialog_ = gtk_print_unix_dialog_new(NULL, parent); 238 g_signal_connect(dialog_, "delete-event", 239 G_CALLBACK(gtk_widget_hide_on_delete), NULL); 240 241 242 // Set modal so user cannot focus the same tab and press print again. 243 gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE); 244 245 // Since we only generate PDF, only show printers that support PDF. 246 // TODO(thestig) Add more capabilities to support? 247 GtkPrintCapabilities cap = static_cast<GtkPrintCapabilities>( 248 GTK_PRINT_CAPABILITY_GENERATE_PDF | 249 GTK_PRINT_CAPABILITY_PAGE_SET | 250 GTK_PRINT_CAPABILITY_COPIES | 251 GTK_PRINT_CAPABILITY_COLLATE | 252 GTK_PRINT_CAPABILITY_REVERSE); 253 gtk_print_unix_dialog_set_manual_capabilities(GTK_PRINT_UNIX_DIALOG(dialog_), 254 cap); 255 gtk_print_unix_dialog_set_embed_page_setup(GTK_PRINT_UNIX_DIALOG(dialog_), 256 TRUE); 257 gtk_print_unix_dialog_set_support_selection(GTK_PRINT_UNIX_DIALOG(dialog_), 258 TRUE); 259 gtk_print_unix_dialog_set_has_selection(GTK_PRINT_UNIX_DIALOG(dialog_), 260 has_selection); 261 gtk_print_unix_dialog_set_settings(GTK_PRINT_UNIX_DIALOG(dialog_), 262 gtk_settings_); 263 g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseThunk), this); 264 gtk_widget_show(dialog_); 265 } 266 267 void PrintDialogGtk::PrintDocument(const printing::Metafile* metafile, 268 const base::string16& document_name) { 269 // This runs on the print worker thread, does not block the UI thread. 270 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); 271 272 // The document printing tasks can outlive the PrintingContext that created 273 // this dialog. 274 AddRef(); 275 276 bool error = false; 277 if (!base::CreateTemporaryFile(&path_to_pdf_)) { 278 LOG(ERROR) << "Creating temporary file failed"; 279 error = true; 280 } 281 282 if (!error && !metafile->SaveTo(path_to_pdf_)) { 283 LOG(ERROR) << "Saving metafile failed"; 284 base::DeleteFile(path_to_pdf_, false); 285 error = true; 286 } 287 288 if (error) { 289 // Matches AddRef() above. 290 Release(); 291 } else { 292 // No errors, continue printing. 293 BrowserThread::PostTask( 294 BrowserThread::UI, FROM_HERE, 295 base::Bind(&PrintDialogGtk::SendDocumentToPrinter, this, 296 document_name)); 297 } 298 } 299 300 void PrintDialogGtk::AddRefToDialog() { 301 AddRef(); 302 } 303 304 void PrintDialogGtk::ReleaseDialog() { 305 Release(); 306 } 307 308 void PrintDialogGtk::OnResponse(GtkWidget* dialog, int response_id) { 309 int num_matched_handlers = g_signal_handlers_disconnect_by_func( 310 dialog_, reinterpret_cast<gpointer>(&OnResponseThunk), this); 311 CHECK_EQ(1, num_matched_handlers); 312 313 gtk_widget_hide(dialog_); 314 315 switch (response_id) { 316 case GTK_RESPONSE_OK: { 317 if (gtk_settings_) 318 g_object_unref(gtk_settings_); 319 gtk_settings_ = gtk_print_unix_dialog_get_settings( 320 GTK_PRINT_UNIX_DIALOG(dialog_)); 321 322 if (printer_) 323 g_object_unref(printer_); 324 printer_ = gtk_print_unix_dialog_get_selected_printer( 325 GTK_PRINT_UNIX_DIALOG(dialog_)); 326 g_object_ref(printer_); 327 328 if (page_setup_) 329 g_object_unref(page_setup_); 330 page_setup_ = gtk_print_unix_dialog_get_page_setup( 331 GTK_PRINT_UNIX_DIALOG(dialog_)); 332 g_object_ref(page_setup_); 333 334 // Handle page ranges. 335 PageRanges ranges_vector; 336 gint num_ranges; 337 bool print_selection_only = false; 338 switch (gtk_print_settings_get_print_pages(gtk_settings_)) { 339 case GTK_PRINT_PAGES_RANGES: { 340 GtkPageRange* gtk_range = 341 gtk_print_settings_get_page_ranges(gtk_settings_, &num_ranges); 342 if (gtk_range) { 343 for (int i = 0; i < num_ranges; ++i) { 344 printing::PageRange range; 345 range.from = gtk_range[i].start; 346 range.to = gtk_range[i].end; 347 ranges_vector.push_back(range); 348 } 349 g_free(gtk_range); 350 } 351 break; 352 } 353 case GTK_PRINT_PAGES_SELECTION: 354 print_selection_only = true; 355 break; 356 case GTK_PRINT_PAGES_ALL: 357 // Leave |ranges_vector| empty to indicate print all pages. 358 break; 359 case GTK_PRINT_PAGES_CURRENT: 360 default: 361 NOTREACHED(); 362 break; 363 } 364 365 PrintSettings settings; 366 settings.set_ranges(ranges_vector); 367 settings.set_selection_only(print_selection_only); 368 printing::PrintSettingsInitializerGtk::InitPrintSettings( 369 gtk_settings_, page_setup_, &settings); 370 context_->InitWithSettings(settings); 371 callback_.Run(PrintingContextGtk::OK); 372 callback_.Reset(); 373 return; 374 } 375 case GTK_RESPONSE_DELETE_EVENT: // Fall through. 376 case GTK_RESPONSE_CANCEL: { 377 callback_.Run(PrintingContextGtk::CANCEL); 378 callback_.Reset(); 379 return; 380 } 381 case GTK_RESPONSE_APPLY: 382 default: { 383 NOTREACHED(); 384 } 385 } 386 } 387 388 void PrintDialogGtk::SendDocumentToPrinter( 389 const base::string16& document_name) { 390 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 391 392 // If |printer_| is NULL then somehow the GTK printer list changed out under 393 // us. In which case, just bail out. 394 if (!printer_) { 395 // Matches AddRef() in PrintDocument(); 396 Release(); 397 return; 398 } 399 400 // Save the settings for next time. 401 g_last_used_settings.Get().SetLastUsedSettings(gtk_settings_); 402 403 GtkPrintJob* print_job = gtk_print_job_new( 404 UTF16ToUTF8(document_name).c_str(), 405 printer_, 406 gtk_settings_, 407 page_setup_); 408 gtk_print_job_set_source_file(print_job, path_to_pdf_.value().c_str(), NULL); 409 gtk_print_job_send(print_job, OnJobCompletedThunk, this, NULL); 410 } 411 412 // static 413 void PrintDialogGtk::OnJobCompletedThunk(GtkPrintJob* print_job, 414 gpointer user_data, 415 GError* error) { 416 static_cast<PrintDialogGtk*>(user_data)->OnJobCompleted(print_job, error); 417 } 418 419 void PrintDialogGtk::OnJobCompleted(GtkPrintJob* print_job, GError* error) { 420 if (error) 421 LOG(ERROR) << "Printing failed: " << error->message; 422 if (print_job) 423 g_object_unref(print_job); 424 base::FileUtilProxy::DeleteFile( 425 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE).get(), 426 path_to_pdf_, 427 false, 428 base::FileUtilProxy::StatusCallback()); 429 // Printing finished. Matches AddRef() in PrintDocument(); 430 Release(); 431 } 432 433 void PrintDialogGtk::InitPrintSettings(PrintSettings* settings) { 434 printing::PrintSettingsInitializerGtk::InitPrintSettings( 435 gtk_settings_, page_setup_, settings); 436 context_->InitWithSettings(*settings); 437 } 438