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