// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "printing/backend/win_helper.h"
#include <stddef.h>
#include <wrl/client.h>
#include <algorithm>
#include <memory>
#include <string_view>
#include "base/check_op.h"
#include "base/containers/fixed_flat_set.h"
#include "base/debug/alias.h"
#include "base/file_version_info.h"
#include "base/files/file_path.h"
#include "base/memory/free_deleter.h"
#include "base/notreached.h"
#include "base/numerics/checked_math.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/win/registry.h"
#include "base/win/windows_types.h"
#include "base/win/windows_version.h"
#include "printing/backend/print_backend.h"
#include "printing/backend/print_backend_consts.h"
#include "printing/backend/printing_info_win.h"
namespace {
typedef HRESULT(WINAPI* PTOpenProviderProc)(PCWSTR printer_name,
DWORD version,
HPTPROVIDER* provider);
typedef HRESULT(WINAPI* PTGetPrintCapabilitiesProc)(HPTPROVIDER provider,
IStream* print_ticket,
IStream* capabilities,
BSTR* error_message);
typedef HRESULT(WINAPI* PTConvertDevModeToPrintTicketProc)(
HPTPROVIDER provider,
ULONG devmode_size_in_bytes,
PDEVMODE devmode,
EPrintTicketScope scope,
IStream* print_ticket);
typedef HRESULT(WINAPI* PTConvertPrintTicketToDevModeProc)(
HPTPROVIDER provider,
IStream* print_ticket,
EDefaultDevmodeType base_devmode_type,
EPrintTicketScope scope,
ULONG* devmode_byte_count,
PDEVMODE* devmode,
BSTR* error_message);
typedef HRESULT(WINAPI* PTMergeAndValidatePrintTicketProc)(
HPTPROVIDER provider,
IStream* base_ticket,
IStream* delta_ticket,
EPrintTicketScope scope,
IStream* result_ticket,
BSTR* error_message);
typedef HRESULT(WINAPI* PTReleaseMemoryProc)(PVOID buffer);
typedef HRESULT(WINAPI* PTCloseProviderProc)(HPTPROVIDER provider);
typedef HRESULT(WINAPI* StartXpsPrintJobProc)(
const LPCWSTR printer_name,
const LPCWSTR job_name,
const LPCWSTR output_file_name,
HANDLE progress_event,
HANDLE completion_event,
UINT8* printable_pages_on,
UINT32 printable_pages_on_count,
IXpsPrintJob** xps_print_job,
IXpsPrintJobStream** document_stream,
IXpsPrintJobStream** print_ticket_stream);
PTOpenProviderProc g_open_provider_proc = nullptr;
PTGetPrintCapabilitiesProc g_get_print_capabilities_proc = nullptr;
PTConvertDevModeToPrintTicketProc g_convert_devmode_to_print_ticket_proc =
nullptr;
PTConvertPrintTicketToDevModeProc g_convert_print_ticket_to_devmode_proc =
nullptr;
PTMergeAndValidatePrintTicketProc g_merge_and_validate_print_ticket_proc =
nullptr;
PTReleaseMemoryProc g_release_memory_proc = nullptr;
PTCloseProviderProc g_close_provider_proc = nullptr;
StartXpsPrintJobProc g_start_xps_print_job_proc = nullptr;
typedef std::string (*GetDisplayNameFunc)(const std::string& printer_name);
GetDisplayNameFunc g_get_display_name_func = nullptr;
HRESULT StreamFromPrintTicket(const std::string& print_ticket,
IStream** stream) {
DCHECK(stream);
HRESULT hr = CreateStreamOnHGlobal(nullptr, TRUE, stream);
if (FAILED(hr)) {
return hr;
}
ULONG bytes_written = 0;
(*stream)->Write(print_ticket.c_str(),
base::checked_cast<ULONG>(print_ticket.length()),
&bytes_written);
DCHECK(bytes_written == print_ticket.length());
LARGE_INTEGER pos = {};
ULARGE_INTEGER new_pos = {};
(*stream)->Seek(pos, STREAM_SEEK_SET, &new_pos);
return S_OK;
}
const char kXpsTicketTemplate[] =
"<?xml version='1.0' encoding='UTF-8'?>"
"<psf:PrintTicket "
"xmlns:psf='"
"http://schemas.microsoft.com/windows/2003/08/printing/"
"printschemaframework' "
"xmlns:psk="
"'http://schemas.microsoft.com/windows/2003/08/printing/"
"printschemakeywords' "
"version='1'>"
"<psf:Feature name='psk:PageOutputColor'>"
"<psf:Option name='psk:%s'>"
"</psf:Option>"
"</psf:Feature>"
"</psf:PrintTicket>";
const char kXpsTicketColor[] = "Color";
const char kXpsTicketMonochrome[] = "Monochrome";
// Registry path prefix for the printer drivers' keys.
constexpr wchar_t kDriversRegistryKeyPath[] =
L"SYSTEM\\CurrentControlSet\\Control\\Print\\Printers\\";
// Registry value name for a port.
constexpr wchar_t kPortRegistryValue[] = L"Port";
// List of printer ports which are known to cause a UI dialog to be displayed
// when printing.
constexpr wchar_t kPrinterDriverPortFile[] = L"FILE:";
constexpr wchar_t kPrinterDriverPortPrompt[] = L"PORTPROMPT:";
constexpr wchar_t kPrinterDriverPortFax[] = L"SHRFAX:";
// Gets the port used for a particular printer driver. This can be found in
// the Windows registry entry for the driver.
std::wstring GetPrinterDriverPort(const std::string& printer_name) {
base::win::RegKey reg_key;
std::wstring root_key(std::wstring(kDriversRegistryKeyPath) +
base::UTF8ToWide(printer_name));
LONG result =
reg_key.Open(HKEY_LOCAL_MACHINE, root_key.c_str(), KEY_QUERY_VALUE);
if (result != ERROR_SUCCESS)
return std::wstring();
std::wstring port_value;
result = reg_key.ReadValue(kPortRegistryValue, &port_value);
if (result != ERROR_SUCCESS)
return std::wstring();
return port_value;
}
std::string GetDriverVersionString(DWORDLONG version_number) {
// A Windows driver version number is four 16-bit unsigned integers
// concatenated together as w.x.y.z in a 64 bit unsigned int.
// https://learn.microsoft.com/en-us/windows-hardware/drivers/install/inf-driverver-directive
return base::StringPrintf(
"%u.%u.%u.%u", static_cast<uint16_t>((version_number >> 48) & 0xFFFF),
static_cast<uint16_t>((version_number >> 32) & 0xFFFF),
static_cast<uint16_t>((version_number >> 16) & 0xFFFF),
static_cast<uint16_t>(version_number & 0xFFFF));
}
} // namespace
namespace printing {
// static
bool PrinterHandleTraits::CloseHandle(HANDLE handle) {
return ::ClosePrinter(handle) != FALSE;
}
// static
bool PrinterChangeHandleTraits::CloseHandle(HANDLE handle) {
::FindClosePrinterChangeNotification(handle);
return true;
}
bool ScopedPrinterHandle::OpenPrinterWithName(const wchar_t* printer) {
HANDLE temp_handle;
// ::OpenPrinter may return error but assign some value into handle.
if (::OpenPrinter(const_cast<LPTSTR>(printer), &temp_handle, nullptr)) {
Set(temp_handle);
}
return IsValid();
}
bool XPSModule::Init() {
static bool initialized = InitImpl();
return initialized;
}
bool XPSModule::InitImpl() {
HMODULE prntvpt_module = LoadLibrary(L"prntvpt.dll");
if (!prntvpt_module)
return false;
g_open_provider_proc = reinterpret_cast<PTOpenProviderProc>(
GetProcAddress(prntvpt_module, "PTOpenProvider"));
if (!g_open_provider_proc) {
NOTREACHED_IN_MIGRATION();
return false;
}
g_get_print_capabilities_proc = reinterpret_cast<PTGetPrintCapabilitiesProc>(
GetProcAddress(prntvpt_module, "PTGetPrintCapabilities"));
if (!g_get_print_capabilities_proc) {
NOTREACHED_IN_MIGRATION();
return false;
}
g_convert_devmode_to_print_ticket_proc =
reinterpret_cast<PTConvertDevModeToPrintTicketProc>(
GetProcAddress(prntvpt_module, "PTConvertDevModeToPrintTicket"));
if (!g_convert_devmode_to_print_ticket_proc) {
NOTREACHED_IN_MIGRATION();
return false;
}
g_convert_print_ticket_to_devmode_proc =
reinterpret_cast<PTConvertPrintTicketToDevModeProc>(
GetProcAddress(prntvpt_module, "PTConvertPrintTicketToDevMode"));
if (!g_convert_print_ticket_to_devmode_proc) {
NOTREACHED_IN_MIGRATION();
return false;
}
g_merge_and_validate_print_ticket_proc =
reinterpret_cast<PTMergeAndValidatePrintTicketProc>(
GetProcAddress(prntvpt_module, "PTMergeAndValidatePrintTicket"));
if (!g_merge_and_validate_print_ticket_proc) {
NOTREACHED_IN_MIGRATION();
return false;
}
g_release_memory_proc = reinterpret_cast<PTReleaseMemoryProc>(
GetProcAddress(prntvpt_module, "PTReleaseMemory"));
if (!g_release_memory_proc) {
NOTREACHED_IN_MIGRATION();
return false;
}
g_close_provider_proc = reinterpret_cast<PTCloseProviderProc>(
GetProcAddress(prntvpt_module, "PTCloseProvider"));
if (!g_close_provider_proc) {
NOTREACHED_IN_MIGRATION();
return false;
}
return true;
}
HRESULT XPSModule::OpenProvider(const std::wstring& printer_name,
DWORD version,
HPTPROVIDER* provider) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
return g_open_provider_proc(printer_name.c_str(), version, provider);
}
HRESULT XPSModule::GetPrintCapabilities(HPTPROVIDER provider,
IStream* print_ticket,
IStream* capabilities,
BSTR* error_message) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
return g_get_print_capabilities_proc(provider, print_ticket, capabilities,
error_message);
}
HRESULT XPSModule::ConvertDevModeToPrintTicket(HPTPROVIDER provider,
ULONG devmode_size_in_bytes,
PDEVMODE devmode,
EPrintTicketScope scope,
IStream* print_ticket) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
return g_convert_devmode_to_print_ticket_proc(provider, devmode_size_in_bytes,
devmode, scope, print_ticket);
}
HRESULT XPSModule::ConvertPrintTicketToDevMode(
HPTPROVIDER provider,
IStream* print_ticket,
EDefaultDevmodeType base_devmode_type,
EPrintTicketScope scope,
ULONG* devmode_byte_count,
PDEVMODE* devmode,
BSTR* error_message) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
return g_convert_print_ticket_to_devmode_proc(
provider, print_ticket, base_devmode_type, scope, devmode_byte_count,
devmode, error_message);
}
HRESULT XPSModule::MergeAndValidatePrintTicket(HPTPROVIDER provider,
IStream* base_ticket,
IStream* delta_ticket,
EPrintTicketScope scope,
IStream* result_ticket,
BSTR* error_message) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
return g_merge_and_validate_print_ticket_proc(
provider, base_ticket, delta_ticket, scope, result_ticket, error_message);
}
HRESULT XPSModule::ReleaseMemory(PVOID buffer) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
return g_release_memory_proc(buffer);
}
HRESULT XPSModule::CloseProvider(HPTPROVIDER provider) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
return g_close_provider_proc(provider);
}
ScopedXPSInitializer::ScopedXPSInitializer() : initialized_(false) {
if (!XPSModule::Init())
return;
// Calls to XPS APIs typically require the XPS provider to be opened with
// PTOpenProvider. PTOpenProvider calls CoInitializeEx with
// COINIT_MULTITHREADED. We have seen certain buggy HP printer driver DLLs
// that call CoInitializeEx with COINIT_APARTMENTTHREADED in the context of
// PTGetPrintCapabilities. This call fails but the printer driver calls
// CoUninitialize anyway. This results in the apartment being torn down too
// early and the msxml DLL being unloaded which in turn causes code in
// unidrvui.dll to have a dangling pointer to an XML document which causes a
// crash. To protect ourselves from such drivers we make sure we always have
// an extra CoInitialize (calls to CoInitialize/CoUninitialize are
// refcounted).
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
// If this succeeded we are done because the PTOpenProvider call will provide
// the extra refcount on the apartment. If it failed because someone already
// called CoInitializeEx with COINIT_APARTMENTTHREADED, we try the other model
// to provide the additional refcount (since we don't know which model buggy
// printer drivers will use).
if (!SUCCEEDED(hr))
hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
DCHECK(SUCCEEDED(hr));
initialized_ = true;
}
ScopedXPSInitializer::~ScopedXPSInitializer() {
if (initialized_)
CoUninitialize();
initialized_ = false;
}
bool XPSPrintModule::Init() {
static bool initialized = InitImpl();
return initialized;
}
bool XPSPrintModule::InitImpl() {
HMODULE xpsprint_module = LoadLibrary(L"xpsprint.dll");
if (!xpsprint_module)
return false;
g_start_xps_print_job_proc = reinterpret_cast<StartXpsPrintJobProc>(
GetProcAddress(xpsprint_module, "StartXpsPrintJob"));
if (!g_start_xps_print_job_proc) {
NOTREACHED_IN_MIGRATION();
return false;
}
return true;
}
HRESULT XPSPrintModule::StartXpsPrintJob(
const LPCWSTR printer_name,
const LPCWSTR job_name,
const LPCWSTR output_file_name,
HANDLE progress_event,
HANDLE completion_event,
UINT8* printable_pages_on,
UINT32 printable_pages_on_count,
IXpsPrintJob** xps_print_job,
IXpsPrintJobStream** document_stream,
IXpsPrintJobStream** print_ticket_stream) {
return g_start_xps_print_job_proc(
printer_name, job_name, output_file_name, progress_event,
completion_event, printable_pages_on, printable_pages_on_count,
xps_print_job, document_stream, print_ticket_stream);
}
void SetGetDisplayNameFunction(GetDisplayNameFunc get_display_name_func) {
DCHECK(get_display_name_func);
DCHECK(!g_get_display_name_func);
g_get_display_name_func = get_display_name_func;
}
bool InitBasicPrinterInfo(HANDLE printer, PrinterBasicInfo* printer_info) {
DCHECK(printer);
DCHECK(printer_info);
if (!printer)
return false;
PrinterInfo2 info_2;
if (!info_2.Init(printer))
return false;
printer_info->printer_name = base::WideToUTF8(info_2.get()->pPrinterName);
if (g_get_display_name_func) {
printer_info->display_name =
g_get_display_name_func(printer_info->printer_name);
} else {
printer_info->display_name = printer_info->printer_name;
}
if (info_2.get()->pComment) {
printer_info->printer_description =
base::WideToUTF8(info_2.get()->pComment);
}
if (info_2.get()->pLocation) {
printer_info->options[kLocationTagName] =
base::WideToUTF8(info_2.get()->pLocation);
}
if (info_2.get()->pDriverName) {
printer_info->options[kDriverNameTagName] =
base::WideToUTF8(info_2.get()->pDriverName);
}
printer_info->printer_status = info_2.get()->Status;
std::vector<std::string> driver_info = GetDriverInfo(printer);
if (!driver_info.empty()) {
printer_info->options[kDriverInfoTagName] =
base::JoinString(driver_info, ";");
}
return true;
}
std::vector<std::string> GetDriverInfo(HANDLE printer) {
DCHECK(printer);
std::vector<std::string> driver_info;
if (!printer) {
return driver_info;
}
DriverInfo6 info_6;
if (!info_6.Init(printer)) {
return driver_info;
}
driver_info.emplace_back(info_6.get()->pName
? base::WideToUTF8(info_6.get()->pName)
: std::string());
driver_info.emplace_back(
info_6.get()->dwlDriverVersion
? GetDriverVersionString(info_6.get()->dwlDriverVersion)
: std::string());
if (info_6.get()->pDriverPath) {
std::unique_ptr<FileVersionInfo> version_info(
FileVersionInfo::CreateFileVersionInfo(
base::FilePath(info_6.get()->pDriverPath)));
if (version_info.get()) {
driver_info.emplace_back(base::UTF16ToUTF8(version_info->file_version()));
driver_info.emplace_back(base::UTF16ToUTF8(version_info->product_name()));
}
}
return driver_info;
}
bool DoesDriverDisplayFileDialogForPrinting(const std::string& printer_name) {
static constexpr auto kPortNames = base::MakeFixedFlatSet<std::wstring_view>(
{kPrinterDriverPortFile, kPrinterDriverPortPrompt,
kPrinterDriverPortFax});
return kPortNames.contains(GetPrinterDriverPort(printer_name));
}
std::unique_ptr<DEVMODE, base::FreeDeleter> XpsTicketToDevMode(
const std::wstring& printer_name,
const std::string& print_ticket) {
std::unique_ptr<DEVMODE, base::FreeDeleter> dev_mode;
ScopedXPSInitializer xps_initializer;
if (!xps_initializer.initialized()) {
// TODO(sanjeevr): Handle legacy proxy case (with no prntvpt.dll)
return dev_mode;
}
ScopedPrinterHandle printer;
if (!printer.OpenPrinterWithName(printer_name.c_str()))
return dev_mode;
Microsoft::WRL::ComPtr<IStream> pt_stream;
HRESULT hr = StreamFromPrintTicket(print_ticket, &pt_stream);
if (FAILED(hr))
return dev_mode;
HPTPROVIDER provider = nullptr;
hr = XPSModule::OpenProvider(printer_name, 1, &provider);
if (SUCCEEDED(hr)) {
ULONG size = 0;
DEVMODE* dm = nullptr;
// Use kPTJobScope, because kPTDocumentScope breaks duplex.
hr = XPSModule::ConvertPrintTicketToDevMode(
provider, pt_stream.Get(), kUserDefaultDevmode, kPTJobScope, &size, &dm,
nullptr);
if (SUCCEEDED(hr)) {
// Correct DEVMODE using DocumentProperties. See documentation for
// PTConvertPrintTicketToDevMode.
dev_mode = CreateDevMode(printer.Get(), dm);
XPSModule::ReleaseMemory(dm);
}
XPSModule::CloseProvider(provider);
}
return dev_mode;
}
bool IsDevModeWithColor(const DEVMODE* devmode) {
return (devmode->dmFields & DM_COLOR) && (devmode->dmColor == DMCOLOR_COLOR);
}
std::unique_ptr<DEVMODE, base::FreeDeleter> CreateDevModeWithColor(
HANDLE printer,
const std::wstring& printer_name,
bool color) {
std::unique_ptr<DEVMODE, base::FreeDeleter> default_ticket =
CreateDevMode(printer, nullptr);
if (!default_ticket || IsDevModeWithColor(default_ticket.get()) == color)
return default_ticket;
default_ticket->dmFields |= DM_COLOR;
default_ticket->dmColor = color ? DMCOLOR_COLOR : DMCOLOR_MONOCHROME;
DriverInfo6 info_6;
if (!info_6.Init(printer))
return default_ticket;
const DRIVER_INFO_6* p = info_6.get();
// Only HP known to have issues.
if (!p->pszMfgName || wcscmp(p->pszMfgName, L"HP") != 0)
return default_ticket;
// Need XPS for this workaround.
ScopedXPSInitializer xps_initializer;
if (!xps_initializer.initialized())
return default_ticket;
const char* xps_color = color ? kXpsTicketColor : kXpsTicketMonochrome;
std::string xps_ticket = base::StringPrintf(kXpsTicketTemplate, xps_color);
std::unique_ptr<DEVMODE, base::FreeDeleter> ticket =
XpsTicketToDevMode(printer_name, xps_ticket);
if (!ticket)
return default_ticket;
return ticket;
}
bool PrinterHasValidPaperSize(const wchar_t* name, const wchar_t* port) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
return DeviceCapabilities(name, port, DC_PAPERSIZE, nullptr, nullptr) > 0;
}
std::unique_ptr<DEVMODE, base::FreeDeleter> CreateDevMode(HANDLE printer,
DEVMODE* in) {
wchar_t* device_name_ptr = const_cast<wchar_t*>(L"");
LONG buffer_size;
{
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::MAY_BLOCK);
buffer_size = DocumentProperties(nullptr, printer, device_name_ptr, nullptr,
nullptr, 0);
}
// TODO(thestig): Consider limiting `buffer_size` to avoid buggy printer
// drivers that return excessively large values.
if (buffer_size < static_cast<LONG>(sizeof(DEVMODE)))
return nullptr;
// Some drivers request buffers with size smaller than dmSize + dmDriverExtra.
// Examples: crbug.com/421402, crbug.com/780016
// Pad the `out` buffer so there is plenty of space. Calculate the size using
// `base::CheckedNumeric` to avoid a potential integer overflow.
base::CheckedNumeric<LONG> safe_buffer_size = buffer_size;
safe_buffer_size *= 2;
safe_buffer_size += 8192;
buffer_size = safe_buffer_size.ValueOrDie();
std::unique_ptr<DEVMODE, base::FreeDeleter> out(
reinterpret_cast<DEVMODE*>(calloc(buffer_size, 1)));
DWORD flags = (in ? (DM_IN_BUFFER) : 0) | DM_OUT_BUFFER;
PrinterInfo5 info_5;
if (!info_5.Init(printer))
return nullptr;
// Check that valid paper sizes exist; some old drivers return no paper sizes
// and crash in DocumentProperties if used with Win10. See crbug.com/679160,
// crbug.com/724595
const wchar_t* name = info_5.get()->pPrinterName;
const wchar_t* port = info_5.get()->pPortName;
if (!PrinterHasValidPaperSize(name, port)) {
return nullptr;
}
{
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::MAY_BLOCK);
if (DocumentProperties(nullptr, printer, device_name_ptr, out.get(), in,
flags) != IDOK) {
return nullptr;
}
}
int size = out->dmSize;
int extra_size = out->dmDriverExtra;
// The CHECK_GE() below can fail. Alias the variable values so they are
// recorded in crash dumps.
// See https://crbug.com/780016 and https://crbug.com/806016 for example
// crashes.
// TODO(crbug.com/41352705): Remove this debug code if the CHECK_GE() below
// stops failing.
base::debug::Alias(&size);
base::debug::Alias(&extra_size);
base::debug::Alias(&buffer_size);
CHECK_GE(buffer_size, size + extra_size);
return out;
}
std::unique_ptr<DEVMODE, base::FreeDeleter> PromptDevMode(
HANDLE printer,
const std::wstring& printer_name,
DEVMODE* in,
HWND window,
bool* canceled) {
wchar_t* printer_name_ptr = const_cast<wchar_t*>(printer_name.c_str());
LONG buffer_size = DocumentProperties(window, printer, printer_name_ptr,
nullptr, nullptr, 0);
if (buffer_size < static_cast<int>(sizeof(DEVMODE)))
return std::unique_ptr<DEVMODE, base::FreeDeleter>();
// Some drivers request buffers with size smaller than dmSize + dmDriverExtra.
// crbug.com/421402
buffer_size *= 2;
std::unique_ptr<DEVMODE, base::FreeDeleter> out(
reinterpret_cast<DEVMODE*>(calloc(buffer_size, 1)));
DWORD flags = (in ? (DM_IN_BUFFER) : 0) | DM_OUT_BUFFER | DM_IN_PROMPT;
LONG result;
{
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::MAY_BLOCK);
result = DocumentProperties(window, printer, printer_name_ptr, out.get(),
in, flags);
}
if (canceled)
*canceled = (result == IDCANCEL);
if (result != IDOK)
return std::unique_ptr<DEVMODE, base::FreeDeleter>();
int size = out->dmSize;
int extra_size = out->dmDriverExtra;
CHECK_GE(buffer_size, size + extra_size);
return out;
}
std::string GetDriverVersionStringForTesting(DWORDLONG version_number) {
return GetDriverVersionString(version_number);
}
mojom::ResultCode GetResultCodeFromSystemErrorCode(
logging::SystemErrorCode system_code) {
if (system_code == ERROR_ACCESS_DENIED) {
return mojom::ResultCode::kAccessDenied;
}
return mojom::ResultCode::kFailed;
}
} // namespace printing