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.
5 #include "printing/backend/win_helper.h"
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"
24 typedef HRESULT (WINAPI
* PTOpenProviderProc
)(PCWSTR printer_name
,
26 HPTPROVIDER
* provider
);
28 typedef HRESULT (WINAPI
* PTGetPrintCapabilitiesProc
)(HPTPROVIDER provider
,
29 IStream
* print_ticket
,
30 IStream
* capabilities
,
33 typedef HRESULT (WINAPI
* PTConvertDevModeToPrintTicketProc
)(
35 ULONG devmode_size_in_bytes
,
37 EPrintTicketScope scope
,
38 IStream
* print_ticket
);
40 typedef HRESULT (WINAPI
* PTConvertPrintTicketToDevModeProc
)(
42 IStream
* print_ticket
,
43 EDefaultDevmodeType base_devmode_type
,
44 EPrintTicketScope scope
,
45 ULONG
* devmode_byte_count
,
49 typedef HRESULT (WINAPI
* PTMergeAndValidatePrintTicketProc
)(
52 IStream
* delta_ticket
,
53 EPrintTicketScope scope
,
54 IStream
* result_ticket
,
57 typedef HRESULT (WINAPI
* PTReleaseMemoryProc
)(PVOID buffer
);
59 typedef HRESULT (WINAPI
* PTCloseProviderProc
)(HPTPROVIDER provider
);
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
);
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
;
82 HRESULT
StreamFromPrintTicket(const std::string
& print_ticket
,
85 HRESULT hr
= CreateStreamOnHGlobal(NULL
, TRUE
, stream
);
89 ULONG bytes_written
= 0;
90 (*stream
)->Write(print_ticket
.c_str(),
91 base::checked_cast
<ULONG
>(print_ticket
.length()),
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
);
100 const char kXpsTicketTemplate
[] =
101 "<?xml version='1.0' encoding='UTF-8'?>"
104 "http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework' "
106 "'http://schemas.microsoft.com/windows/2003/08/printing/printschemakeywords' "
108 "<psf:Feature name='psk:PageOutputColor'>"
109 "<psf:Option name='psk:%s'>"
112 "</psf:PrintTicket>";
114 const char kXpsTicketColor
[] = "Color";
115 const char kXpsTicketMonochrome
[] = "Monochrome";
123 bool XPSModule::Init() {
124 static bool initialized
= InitImpl();
128 bool XPSModule::InitImpl() {
129 HMODULE prntvpt_module
= LoadLibrary(L
"prntvpt.dll");
130 if (prntvpt_module
== NULL
)
132 g_open_provider_proc
= reinterpret_cast<PTOpenProviderProc
>(
133 GetProcAddress(prntvpt_module
, "PTOpenProvider"));
134 if (!g_open_provider_proc
) {
138 g_get_print_capabilities_proc
= reinterpret_cast<PTGetPrintCapabilitiesProc
>(
139 GetProcAddress(prntvpt_module
, "PTGetPrintCapabilities"));
140 if (!g_get_print_capabilities_proc
) {
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
) {
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
) {
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
) {
165 g_release_memory_proc
=
166 reinterpret_cast<PTReleaseMemoryProc
>(
167 GetProcAddress(prntvpt_module
, "PTReleaseMemory"));
168 if (!g_release_memory_proc
) {
172 g_close_provider_proc
=
173 reinterpret_cast<PTCloseProviderProc
>(
174 GetProcAddress(prntvpt_module
, "PTCloseProvider"));
175 if (!g_close_provider_proc
) {
182 HRESULT
XPSModule::OpenProvider(const base::string16
& printer_name
,
184 HPTPROVIDER
* provider
) {
185 return g_open_provider_proc(printer_name
.c_str(), version
, provider
);
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
,
198 HRESULT
XPSModule::ConvertDevModeToPrintTicket(HPTPROVIDER provider
,
199 ULONG devmode_size_in_bytes
,
201 EPrintTicketScope scope
,
202 IStream
* print_ticket
) {
203 return g_convert_devmode_to_print_ticket_proc(provider
,
204 devmode_size_in_bytes
,
210 HRESULT
XPSModule::ConvertPrintTicketToDevMode(
211 HPTPROVIDER provider
,
212 IStream
* print_ticket
,
213 EDefaultDevmodeType base_devmode_type
,
214 EPrintTicketScope scope
,
215 ULONG
* devmode_byte_count
,
217 BSTR
* error_message
) {
218 return g_convert_print_ticket_to_devmode_proc(provider
,
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
,
241 HRESULT
XPSModule::ReleaseMemory(PVOID buffer
) {
242 return g_release_memory_proc(buffer
);
245 HRESULT
XPSModule::CloseProvider(HPTPROVIDER provider
) {
246 return g_close_provider_proc(provider
);
249 ScopedXPSInitializer::ScopedXPSInitializer() : initialized_(false) {
250 if (!XPSModule::Init())
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
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).
270 hr
= CoInitializeEx(NULL
, COINIT_APARTMENTTHREADED
);
271 DCHECK(SUCCEEDED(hr
));
275 ScopedXPSInitializer::~ScopedXPSInitializer() {
278 initialized_
= false;
281 bool XPSPrintModule::Init() {
282 static bool initialized
= InitImpl();
286 bool XPSPrintModule::InitImpl() {
287 HMODULE xpsprint_module
= LoadLibrary(L
"xpsprint.dll");
288 if (xpsprint_module
== NULL
)
290 g_start_xps_print_job_proc
= reinterpret_cast<StartXpsPrintJobProc
>(
291 GetProcAddress(xpsprint_module
, "StartXpsPrintJob"));
292 if (!g_start_xps_print_job_proc
) {
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
,
316 printable_pages_on_count
,
319 print_ticket_stream
);
322 bool InitBasicPrinterInfo(HANDLE printer
, PrinterBasicInfo
* printer_info
) {
324 DCHECK(printer_info
);
329 if (!info_2
.Init(printer
))
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
);
337 if (info_2
.get()->pLocation
) {
338 printer_info
->options
[kLocationTagName
] =
339 base::WideToUTF8(info_2
.get()->pLocation
);
341 if (info_2
.get()->pDriverName
) {
342 printer_info
->options
[kDriverNameTagName
] =
343 base::WideToUTF8(info_2
.get()->pDriverName
);
345 printer_info
->printer_status
= info_2
.get()->Status
;
347 std::string driver_info
= GetDriverInfo(printer
);
348 if (!driver_info
.empty())
349 printer_info
->options
[kDriverInfoTagName
] = driver_info
;
353 std::string
GetDriverInfo(HANDLE printer
) {
355 std::string driver_info
;
361 if (!info_6
.Init(printer
))
365 if (info_6
.get()->pName
)
366 info
[0] = base::WideToUTF8(info_6
.get()->pName
);
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());
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(";");
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();
398 printing::ScopedPrinterHandle printer
;
399 if (!printer
.OpenPrinter(printer_name
.c_str()))
400 return dev_mode
.Pass();
402 base::win::ScopedComPtr
<IStream
> pt_stream
;
403 HRESULT hr
= StreamFromPrintTicket(print_ticket
, pt_stream
.Receive());
405 return dev_mode
.Pass();
407 HPTPROVIDER provider
= NULL
;
408 hr
= printing::XPSModule::OpenProvider(printer_name
, 1, &provider
);
412 // Use kPTJobScope, because kPTDocumentScope breaks duplex.
413 hr
= printing::XPSModule::ConvertPrintTicketToDevMode(provider
,
421 // Correct DEVMODE using DocumentProperties. See documentation for
422 // PTConvertPrintTicketToDevMode.
423 dev_mode
= CreateDevMode(printer
.Get(), dm
);
424 printing::XPSModule::ReleaseMemory(dm
);
426 printing::XPSModule::CloseProvider(provider
);
428 return dev_mode
.Pass();
431 scoped_ptr
<DEVMODE
, base::FreeDeleter
> CreateDevModeWithColor(
433 const base::string16
& printer_name
,
435 scoped_ptr
<DEVMODE
, base::FreeDeleter
> default_ticket
=
436 CreateDevMode(printer
, NULL
);
438 return default_ticket
.Pass();
440 if ((default_ticket
->dmFields
& DM_COLOR
) &&
441 ((default_ticket
->dmColor
== DMCOLOR_COLOR
) == color
)) {
442 return default_ticket
.Pass();
445 default_ticket
->dmFields
|= DM_COLOR
;
446 default_ticket
->dmColor
= color
? DMCOLOR_COLOR
: DMCOLOR_MONOCHROME
;
449 if (!info_6
.Init(printer
))
450 return default_ticket
.Pass();
452 const DRIVER_INFO_6
* p
= info_6
.get();
454 // Only HP known to have issues.
455 if (!p
->pszMfgName
|| wcscmp(p
->pszMfgName
, L
"HP") != 0)
456 return default_ticket
.Pass();
458 // Need XPS for this workaround.
459 printing::ScopedXPSInitializer xps_initializer
;
460 if (!xps_initializer
.initialized())
461 return default_ticket
.Pass();
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
);
468 return default_ticket
.Pass();
470 return ticket
.Pass();
473 scoped_ptr
<DEVMODE
, base::FreeDeleter
> CreateDevMode(HANDLE printer
,
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
*>(calloc(buffer_size
, 1)));
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
) !=
485 return scoped_ptr
<DEVMODE
, base::FreeDeleter
>();
487 int size
= out
->dmSize
;
488 int extra_size
= out
->dmDriverExtra
;
489 CHECK_GE(buffer_size
, size
+ extra_size
);
493 scoped_ptr
<DEVMODE
, base::FreeDeleter
> PromptDevMode(
495 const base::string16
& printer_name
,
500 DocumentProperties(window
,
502 const_cast<wchar_t*>(printer_name
.c_str()),
506 if (buffer_size
< static_cast<int>(sizeof(DEVMODE
)))
507 return scoped_ptr
<DEVMODE
, base::FreeDeleter
>();
508 scoped_ptr
<DEVMODE
, base::FreeDeleter
> out(
509 reinterpret_cast<DEVMODE
*>(calloc(buffer_size
, 1)));
510 DWORD flags
= (in
? (DM_IN_BUFFER
) : 0) | DM_OUT_BUFFER
| DM_IN_PROMPT
;
511 LONG result
= DocumentProperties(window
,
513 const_cast<wchar_t*>(printer_name
.c_str()),
518 *canceled
= (result
== IDCANCEL
);
520 return scoped_ptr
<DEVMODE
, base::FreeDeleter
>();
521 int size
= out
->dmSize
;
522 int extra_size
= out
->dmDriverExtra
;
523 CHECK_GE(buffer_size
, size
+ extra_size
);
527 } // namespace printing