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 "printing/backend/win_helper.h" 6 7 #include <algorithm> 8 9 #include "base/file_version_info.h" 10 #include "base/files/file_path.h" 11 #include "base/logging.h" 12 #include "base/memory/scoped_ptr.h" 13 #include "base/numerics/safe_conversions.h" 14 #include "base/strings/string_util.h" 15 #include "base/strings/stringprintf.h" 16 #include "base/strings/utf_string_conversions.h" 17 #include "base/win/scoped_comptr.h" 18 #include "printing/backend/print_backend.h" 19 #include "printing/backend/print_backend_consts.h" 20 #include "printing/backend/printing_info_win.h" 21 22 namespace { 23 24 typedef HRESULT (WINAPI* PTOpenProviderProc)(PCWSTR printer_name, 25 DWORD version, 26 HPTPROVIDER* provider); 27 28 typedef HRESULT (WINAPI* PTGetPrintCapabilitiesProc)(HPTPROVIDER provider, 29 IStream* print_ticket, 30 IStream* capabilities, 31 BSTR* error_message); 32 33 typedef HRESULT (WINAPI* PTConvertDevModeToPrintTicketProc)( 34 HPTPROVIDER provider, 35 ULONG devmode_size_in_bytes, 36 PDEVMODE devmode, 37 EPrintTicketScope scope, 38 IStream* print_ticket); 39 40 typedef HRESULT (WINAPI* PTConvertPrintTicketToDevModeProc)( 41 HPTPROVIDER provider, 42 IStream* print_ticket, 43 EDefaultDevmodeType base_devmode_type, 44 EPrintTicketScope scope, 45 ULONG* devmode_byte_count, 46 PDEVMODE* devmode, 47 BSTR* error_message); 48 49 typedef HRESULT (WINAPI* PTMergeAndValidatePrintTicketProc)( 50 HPTPROVIDER provider, 51 IStream* base_ticket, 52 IStream* delta_ticket, 53 EPrintTicketScope scope, 54 IStream* result_ticket, 55 BSTR* error_message); 56 57 typedef HRESULT (WINAPI* PTReleaseMemoryProc)(PVOID buffer); 58 59 typedef HRESULT (WINAPI* PTCloseProviderProc)(HPTPROVIDER provider); 60 61 typedef HRESULT (WINAPI* StartXpsPrintJobProc)( 62 const LPCWSTR printer_name, 63 const LPCWSTR job_name, 64 const LPCWSTR output_file_name, 65 HANDLE progress_event, 66 HANDLE completion_event, 67 UINT8* printable_pages_on, 68 UINT32 printable_pages_on_count, 69 IXpsPrintJob** xps_print_job, 70 IXpsPrintJobStream** document_stream, 71 IXpsPrintJobStream** print_ticket_stream); 72 73 PTOpenProviderProc g_open_provider_proc = NULL; 74 PTGetPrintCapabilitiesProc g_get_print_capabilities_proc = NULL; 75 PTConvertDevModeToPrintTicketProc g_convert_devmode_to_print_ticket_proc = NULL; 76 PTConvertPrintTicketToDevModeProc g_convert_print_ticket_to_devmode_proc = NULL; 77 PTMergeAndValidatePrintTicketProc g_merge_and_validate_print_ticket_proc = NULL; 78 PTReleaseMemoryProc g_release_memory_proc = NULL; 79 PTCloseProviderProc g_close_provider_proc = NULL; 80 StartXpsPrintJobProc g_start_xps_print_job_proc = NULL; 81 82 HRESULT StreamFromPrintTicket(const std::string& print_ticket, 83 IStream** stream) { 84 DCHECK(stream); 85 HRESULT hr = CreateStreamOnHGlobal(NULL, TRUE, stream); 86 if (FAILED(hr)) { 87 return hr; 88 } 89 ULONG bytes_written = 0; 90 (*stream)->Write(print_ticket.c_str(), 91 base::checked_cast<ULONG>(print_ticket.length()), 92 &bytes_written); 93 DCHECK(bytes_written == print_ticket.length()); 94 LARGE_INTEGER pos = {0}; 95 ULARGE_INTEGER new_pos = {0}; 96 (*stream)->Seek(pos, STREAM_SEEK_SET, &new_pos); 97 return S_OK; 98 } 99 100 const char kXpsTicketTemplate[] = 101 "<?xml version='1.0' encoding='UTF-8'?>" 102 "<psf:PrintTicket " 103 "xmlns:psf='" 104 "http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework' " 105 "xmlns:psk=" 106 "'http://schemas.microsoft.com/windows/2003/08/printing/printschemakeywords' " 107 "version='1'>" 108 "<psf:Feature name='psk:PageOutputColor'>" 109 "<psf:Option name='psk:%s'>" 110 "</psf:Option>" 111 "</psf:Feature>" 112 "</psf:PrintTicket>"; 113 114 const char kXpsTicketColor[] = "Color"; 115 const char kXpsTicketMonochrome[] = "Monochrome"; 116 117 118 } // namespace 119 120 121 namespace printing { 122 123 bool XPSModule::Init() { 124 static bool initialized = InitImpl(); 125 return initialized; 126 } 127 128 bool XPSModule::InitImpl() { 129 HMODULE prntvpt_module = LoadLibrary(L"prntvpt.dll"); 130 if (prntvpt_module == NULL) 131 return false; 132 g_open_provider_proc = reinterpret_cast<PTOpenProviderProc>( 133 GetProcAddress(prntvpt_module, "PTOpenProvider")); 134 if (!g_open_provider_proc) { 135 NOTREACHED(); 136 return false; 137 } 138 g_get_print_capabilities_proc = reinterpret_cast<PTGetPrintCapabilitiesProc>( 139 GetProcAddress(prntvpt_module, "PTGetPrintCapabilities")); 140 if (!g_get_print_capabilities_proc) { 141 NOTREACHED(); 142 return false; 143 } 144 g_convert_devmode_to_print_ticket_proc = 145 reinterpret_cast<PTConvertDevModeToPrintTicketProc>( 146 GetProcAddress(prntvpt_module, "PTConvertDevModeToPrintTicket")); 147 if (!g_convert_devmode_to_print_ticket_proc) { 148 NOTREACHED(); 149 return false; 150 } 151 g_convert_print_ticket_to_devmode_proc = 152 reinterpret_cast<PTConvertPrintTicketToDevModeProc>( 153 GetProcAddress(prntvpt_module, "PTConvertPrintTicketToDevMode")); 154 if (!g_convert_print_ticket_to_devmode_proc) { 155 NOTREACHED(); 156 return false; 157 } 158 g_merge_and_validate_print_ticket_proc = 159 reinterpret_cast<PTMergeAndValidatePrintTicketProc>( 160 GetProcAddress(prntvpt_module, "PTMergeAndValidatePrintTicket")); 161 if (!g_merge_and_validate_print_ticket_proc) { 162 NOTREACHED(); 163 return false; 164 } 165 g_release_memory_proc = 166 reinterpret_cast<PTReleaseMemoryProc>( 167 GetProcAddress(prntvpt_module, "PTReleaseMemory")); 168 if (!g_release_memory_proc) { 169 NOTREACHED(); 170 return false; 171 } 172 g_close_provider_proc = 173 reinterpret_cast<PTCloseProviderProc>( 174 GetProcAddress(prntvpt_module, "PTCloseProvider")); 175 if (!g_close_provider_proc) { 176 NOTREACHED(); 177 return false; 178 } 179 return true; 180 } 181 182 HRESULT XPSModule::OpenProvider(const base::string16& printer_name, 183 DWORD version, 184 HPTPROVIDER* provider) { 185 return g_open_provider_proc(printer_name.c_str(), version, provider); 186 } 187 188 HRESULT XPSModule::GetPrintCapabilities(HPTPROVIDER provider, 189 IStream* print_ticket, 190 IStream* capabilities, 191 BSTR* error_message) { 192 return g_get_print_capabilities_proc(provider, 193 print_ticket, 194 capabilities, 195 error_message); 196 } 197 198 HRESULT XPSModule::ConvertDevModeToPrintTicket(HPTPROVIDER provider, 199 ULONG devmode_size_in_bytes, 200 PDEVMODE devmode, 201 EPrintTicketScope scope, 202 IStream* print_ticket) { 203 return g_convert_devmode_to_print_ticket_proc(provider, 204 devmode_size_in_bytes, 205 devmode, 206 scope, 207 print_ticket); 208 } 209 210 HRESULT XPSModule::ConvertPrintTicketToDevMode( 211 HPTPROVIDER provider, 212 IStream* print_ticket, 213 EDefaultDevmodeType base_devmode_type, 214 EPrintTicketScope scope, 215 ULONG* devmode_byte_count, 216 PDEVMODE* devmode, 217 BSTR* error_message) { 218 return g_convert_print_ticket_to_devmode_proc(provider, 219 print_ticket, 220 base_devmode_type, 221 scope, 222 devmode_byte_count, 223 devmode, 224 error_message); 225 } 226 227 HRESULT XPSModule::MergeAndValidatePrintTicket(HPTPROVIDER provider, 228 IStream* base_ticket, 229 IStream* delta_ticket, 230 EPrintTicketScope scope, 231 IStream* result_ticket, 232 BSTR* error_message) { 233 return g_merge_and_validate_print_ticket_proc(provider, 234 base_ticket, 235 delta_ticket, 236 scope, 237 result_ticket, 238 error_message); 239 } 240 241 HRESULT XPSModule::ReleaseMemory(PVOID buffer) { 242 return g_release_memory_proc(buffer); 243 } 244 245 HRESULT XPSModule::CloseProvider(HPTPROVIDER provider) { 246 return g_close_provider_proc(provider); 247 } 248 249 ScopedXPSInitializer::ScopedXPSInitializer() : initialized_(false) { 250 if (!XPSModule::Init()) 251 return; 252 // Calls to XPS APIs typically require the XPS provider to be opened with 253 // PTOpenProvider. PTOpenProvider calls CoInitializeEx with 254 // COINIT_MULTITHREADED. We have seen certain buggy HP printer driver DLLs 255 // that call CoInitializeEx with COINIT_APARTMENTTHREADED in the context of 256 // PTGetPrintCapabilities. This call fails but the printer driver calls 257 // CoUninitialize anyway. This results in the apartment being torn down too 258 // early and the msxml DLL being unloaded which in turn causes code in 259 // unidrvui.dll to have a dangling pointer to an XML document which causes a 260 // crash. To protect ourselves from such drivers we make sure we always have 261 // an extra CoInitialize (calls to CoInitialize/CoUninitialize are 262 // refcounted). 263 HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); 264 // If this succeeded we are done because the PTOpenProvider call will provide 265 // the extra refcount on the apartment. If it failed because someone already 266 // called CoInitializeEx with COINIT_APARTMENTTHREADED, we try the other model 267 // to provide the additional refcount (since we don't know which model buggy 268 // printer drivers will use). 269 if (!SUCCEEDED(hr)) 270 hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); 271 DCHECK(SUCCEEDED(hr)); 272 initialized_ = true; 273 } 274 275 ScopedXPSInitializer::~ScopedXPSInitializer() { 276 if (initialized_) 277 CoUninitialize(); 278 initialized_ = false; 279 } 280 281 bool XPSPrintModule::Init() { 282 static bool initialized = InitImpl(); 283 return initialized; 284 } 285 286 bool XPSPrintModule::InitImpl() { 287 HMODULE xpsprint_module = LoadLibrary(L"xpsprint.dll"); 288 if (xpsprint_module == NULL) 289 return false; 290 g_start_xps_print_job_proc = reinterpret_cast<StartXpsPrintJobProc>( 291 GetProcAddress(xpsprint_module, "StartXpsPrintJob")); 292 if (!g_start_xps_print_job_proc) { 293 NOTREACHED(); 294 return false; 295 } 296 return true; 297 } 298 299 HRESULT XPSPrintModule::StartXpsPrintJob( 300 const LPCWSTR printer_name, 301 const LPCWSTR job_name, 302 const LPCWSTR output_file_name, 303 HANDLE progress_event, 304 HANDLE completion_event, 305 UINT8* printable_pages_on, 306 UINT32 printable_pages_on_count, 307 IXpsPrintJob** xps_print_job, 308 IXpsPrintJobStream** document_stream, 309 IXpsPrintJobStream** print_ticket_stream) { 310 return g_start_xps_print_job_proc(printer_name, 311 job_name, 312 output_file_name, 313 progress_event, 314 completion_event, 315 printable_pages_on, 316 printable_pages_on_count, 317 xps_print_job, 318 document_stream, 319 print_ticket_stream); 320 } 321 322 bool InitBasicPrinterInfo(HANDLE printer, PrinterBasicInfo* printer_info) { 323 DCHECK(printer); 324 DCHECK(printer_info); 325 if (!printer) 326 return false; 327 328 PrinterInfo2 info_2; 329 if (!info_2.Init(printer)) 330 return false; 331 332 printer_info->printer_name = base::WideToUTF8(info_2.get()->pPrinterName); 333 if (info_2.get()->pComment) { 334 printer_info->printer_description = 335 base::WideToUTF8(info_2.get()->pComment); 336 } 337 if (info_2.get()->pLocation) { 338 printer_info->options[kLocationTagName] = 339 base::WideToUTF8(info_2.get()->pLocation); 340 } 341 if (info_2.get()->pDriverName) { 342 printer_info->options[kDriverNameTagName] = 343 base::WideToUTF8(info_2.get()->pDriverName); 344 } 345 printer_info->printer_status = info_2.get()->Status; 346 347 std::string driver_info = GetDriverInfo(printer); 348 if (!driver_info.empty()) 349 printer_info->options[kDriverInfoTagName] = driver_info; 350 return true; 351 } 352 353 std::string GetDriverInfo(HANDLE printer) { 354 DCHECK(printer); 355 std::string driver_info; 356 357 if (!printer) 358 return driver_info; 359 360 DriverInfo6 info_6; 361 if (!info_6.Init(printer)) 362 return driver_info; 363 364 std::string info[4]; 365 if (info_6.get()->pName) 366 info[0] = base::WideToUTF8(info_6.get()->pName); 367 368 if (info_6.get()->pDriverPath) { 369 scoped_ptr<FileVersionInfo> version_info( 370 FileVersionInfo::CreateFileVersionInfo( 371 base::FilePath(info_6.get()->pDriverPath))); 372 if (version_info.get()) { 373 info[1] = base::WideToUTF8(version_info->file_version()); 374 info[2] = base::WideToUTF8(version_info->product_name()); 375 info[3] = base::WideToUTF8(version_info->product_version()); 376 } 377 } 378 379 for (size_t i = 0; i < arraysize(info); ++i) { 380 std::replace(info[i].begin(), info[i].end(), ';', ','); 381 driver_info.append(info[i]); 382 if (i < arraysize(info) - 1) 383 driver_info.append(";"); 384 } 385 return driver_info; 386 } 387 388 scoped_ptr<DEVMODE, base::FreeDeleter> XpsTicketToDevMode( 389 const base::string16& printer_name, 390 const std::string& print_ticket) { 391 scoped_ptr<DEVMODE, base::FreeDeleter> dev_mode; 392 printing::ScopedXPSInitializer xps_initializer; 393 if (!xps_initializer.initialized()) { 394 // TODO(sanjeevr): Handle legacy proxy case (with no prntvpt.dll) 395 return dev_mode.Pass(); 396 } 397 398 printing::ScopedPrinterHandle printer; 399 if (!printer.OpenPrinter(printer_name.c_str())) 400 return dev_mode.Pass(); 401 402 base::win::ScopedComPtr<IStream> pt_stream; 403 HRESULT hr = StreamFromPrintTicket(print_ticket, pt_stream.Receive()); 404 if (FAILED(hr)) 405 return dev_mode.Pass(); 406 407 HPTPROVIDER provider = NULL; 408 hr = printing::XPSModule::OpenProvider(printer_name, 1, &provider); 409 if (SUCCEEDED(hr)) { 410 ULONG size = 0; 411 DEVMODE* dm = NULL; 412 // Use kPTJobScope, because kPTDocumentScope breaks duplex. 413 hr = printing::XPSModule::ConvertPrintTicketToDevMode(provider, 414 pt_stream, 415 kUserDefaultDevmode, 416 kPTJobScope, 417 &size, 418 &dm, 419 NULL); 420 if (SUCCEEDED(hr)) { 421 // Correct DEVMODE using DocumentProperties. See documentation for 422 // PTConvertPrintTicketToDevMode. 423 dev_mode = CreateDevMode(printer.Get(), dm); 424 printing::XPSModule::ReleaseMemory(dm); 425 } 426 printing::XPSModule::CloseProvider(provider); 427 } 428 return dev_mode.Pass(); 429 } 430 431 scoped_ptr<DEVMODE, base::FreeDeleter> CreateDevModeWithColor( 432 HANDLE printer, 433 const base::string16& printer_name, 434 bool color) { 435 scoped_ptr<DEVMODE, base::FreeDeleter> default_ticket = 436 CreateDevMode(printer, NULL); 437 if (!default_ticket) 438 return default_ticket.Pass(); 439 440 if ((default_ticket->dmFields & DM_COLOR) && 441 ((default_ticket->dmColor == DMCOLOR_COLOR) == color)) { 442 return default_ticket.Pass(); 443 } 444 445 default_ticket->dmFields |= DM_COLOR; 446 default_ticket->dmColor = color ? DMCOLOR_COLOR : DMCOLOR_MONOCHROME; 447 448 DriverInfo6 info_6; 449 if (!info_6.Init(printer)) 450 return default_ticket.Pass(); 451 452 const DRIVER_INFO_6* p = info_6.get(); 453 454 // Only HP known to have issues. 455 if (!p->pszMfgName || wcscmp(p->pszMfgName, L"HP") != 0) 456 return default_ticket.Pass(); 457 458 // Need XPS for this workaround. 459 printing::ScopedXPSInitializer xps_initializer; 460 if (!xps_initializer.initialized()) 461 return default_ticket.Pass(); 462 463 const char* xps_color = color ? kXpsTicketColor : kXpsTicketMonochrome; 464 std::string xps_ticket = base::StringPrintf(kXpsTicketTemplate, xps_color); 465 scoped_ptr<DEVMODE, base::FreeDeleter> ticket = 466 printing::XpsTicketToDevMode(printer_name, xps_ticket); 467 if (!ticket) 468 return default_ticket.Pass(); 469 470 return ticket.Pass(); 471 } 472 473 scoped_ptr<DEVMODE, base::FreeDeleter> CreateDevMode(HANDLE printer, 474 DEVMODE* in) { 475 LONG buffer_size = DocumentProperties( 476 NULL, printer, const_cast<wchar_t*>(L""), NULL, NULL, 0); 477 if (buffer_size < static_cast<int>(sizeof(DEVMODE))) 478 return scoped_ptr<DEVMODE, base::FreeDeleter>(); 479 scoped_ptr<DEVMODE, base::FreeDeleter> out( 480 reinterpret_cast<DEVMODE*>(malloc(buffer_size))); 481 DWORD flags = (in ? (DM_IN_BUFFER) : 0) | DM_OUT_BUFFER; 482 if (DocumentProperties( 483 NULL, printer, const_cast<wchar_t*>(L""), out.get(), in, flags) != 484 IDOK) { 485 return scoped_ptr<DEVMODE, base::FreeDeleter>(); 486 } 487 CHECK_GE(buffer_size, out.get()->dmSize + out.get()->dmDriverExtra); 488 return out.Pass(); 489 } 490 491 scoped_ptr<DEVMODE, base::FreeDeleter> PromptDevMode( 492 HANDLE printer, 493 const base::string16& printer_name, 494 DEVMODE* in, 495 HWND window, 496 bool* canceled) { 497 LONG buffer_size = 498 DocumentProperties(window, 499 printer, 500 const_cast<wchar_t*>(printer_name.c_str()), 501 NULL, 502 NULL, 503 0); 504 if (buffer_size < static_cast<int>(sizeof(DEVMODE))) 505 return scoped_ptr<DEVMODE, base::FreeDeleter>(); 506 scoped_ptr<DEVMODE, base::FreeDeleter> out( 507 reinterpret_cast<DEVMODE*>(malloc(buffer_size))); 508 DWORD flags = (in ? (DM_IN_BUFFER) : 0) | DM_OUT_BUFFER | DM_IN_PROMPT; 509 LONG result = DocumentProperties(window, 510 printer, 511 const_cast<wchar_t*>(printer_name.c_str()), 512 out.get(), 513 in, 514 flags); 515 if (canceled) 516 *canceled = (result == IDCANCEL); 517 if (result != IDOK) 518 return scoped_ptr<DEVMODE, base::FreeDeleter>(); 519 CHECK_GE(buffer_size, out.get()->dmSize + out.get()->dmDriverExtra); 520 return out.Pass(); 521 } 522 523 } // namespace printing 524