1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsPrinterWin.h"
12 #include "mozilla/Array.h"
13 #include "mozilla/dom/Promise.h"
15 #include "nsPrintSettingsImpl.h"
16 #include "nsPrintSettingsWin.h"
17 #include "nsWindowsHelpers.h"
18 #include "PrintBackgroundTask.h"
21 using namespace mozilla
;
22 using namespace mozilla::gfx
;
23 using namespace mozilla::widget
;
24 using mozilla::PrintSettingsInitializer
;
25 using mozilla::dom::Promise
;
27 static const double kPointsPerTenthMM
= 72.0 / 254.0;
28 static const double kPointsPerInch
= 72.0;
30 nsPrinterWin::nsPrinterWin(const CommonPaperInfoArray
* aArray
,
31 const nsAString
& aName
)
32 : nsPrinterBase(aArray
),
34 mDefaultDevmodeWStorage("nsPrinterWin::mDefaultDevmodeWStorage") {}
37 already_AddRefed
<nsPrinterWin
> nsPrinterWin::Create(
38 const CommonPaperInfoArray
* aArray
, const nsAString
& aName
) {
39 return do_AddRef(new nsPrinterWin(aArray
, aName
));
43 static nsTArray
<T
> GetDeviceCapabilityArray(const LPWSTR aPrinterName
,
45 mozilla::Mutex
& aDriverMutex
,
47 MOZ_ASSERT(aCount
>= 0, "Possibly passed aCount from previous error case.");
51 // We only want to access printer drivers in the parent process.
52 if (!XRE_IsParentProcess()) {
56 // Both the call to get the size and the call to actually populate the array
57 // are relatively expensive, so as sometimes the lengths of the arrays that we
58 // retrieve depend on each other we allow a count to be passed in to save the
59 // first call. As we allocate double the count anyway this should allay any
62 // Passing nullptr as the port here seems to work. Given that we would have
63 // to OpenPrinter with just the name anyway to get the port that makes
64 // sense. Also, the printer set-up seems to stop you from having two
65 // printers with the same name. Note: this (and the call below) are blocking
66 // calls, which could be slow.
67 MutexAutoLock
autoLock(aDriverMutex
);
68 aCount
= ::DeviceCapabilitiesW(aPrinterName
, nullptr, aCapabilityID
,
75 // As DeviceCapabilitiesW doesn't take a size, there is a greater risk of the
76 // buffer being overflowed, so we over-allocate for safety.
77 caps
.SetLength(aCount
* 2);
78 MutexAutoLock
autoLock(aDriverMutex
);
80 ::DeviceCapabilitiesW(aPrinterName
, nullptr, aCapabilityID
,
81 reinterpret_cast<LPWSTR
>(caps
.Elements()), nullptr);
87 // We know from bug 1673708 that sometimes the final array returned is smaller
88 // than the required array count. Assert here to see if this is reproduced on
90 MOZ_ASSERT(count
== aCount
, "Different array count returned than expected.");
92 // Note that TruncateLength will crash if count > caps.Length().
93 caps
.TruncateLength(count
);
97 static void DevmodeToSettingsInitializer(
98 const nsString
& aPrinterName
, const DEVMODEW
* aDevmode
,
99 mozilla::Mutex
& aDriverMutex
,
100 PrintSettingsInitializer
& aSettingsInitializer
) {
101 aSettingsInitializer
.mPrinter
.Assign(aPrinterName
);
105 MutexAutoLock
autoLock(aDriverMutex
);
106 dc
= ::CreateICW(nullptr, aPrinterName
.get(), nullptr, aDevmode
);
108 nsAutoHDC
printerDc(dc
);
109 MOZ_ASSERT(printerDc
, "CreateICW failed");
114 if (aDevmode
->dmFields
& DM_PAPERSIZE
) {
115 aSettingsInitializer
.mPaperInfo
.mId
.Truncate();
116 aSettingsInitializer
.mPaperInfo
.mId
.AppendInt(aDevmode
->dmPaperSize
);
117 // If it is not a paper size we know about, the unit will remain unchanged.
118 nsPrintSettingsWin::PaperSizeUnitFromDmPaperSize(
119 aDevmode
->dmPaperSize
, aSettingsInitializer
.mPaperSizeUnit
);
122 int pixelsPerInchY
= ::GetDeviceCaps(printerDc
, LOGPIXELSY
);
123 int physicalHeight
= ::GetDeviceCaps(printerDc
, PHYSICALHEIGHT
);
124 double heightInInches
= double(physicalHeight
) / pixelsPerInchY
;
125 int pixelsPerInchX
= ::GetDeviceCaps(printerDc
, LOGPIXELSX
);
126 int physicalWidth
= ::GetDeviceCaps(printerDc
, PHYSICALWIDTH
);
127 double widthInches
= double(physicalWidth
) / pixelsPerInchX
;
128 if (aDevmode
->dmFields
& DM_ORIENTATION
&&
129 aDevmode
->dmOrientation
== DMORIENT_LANDSCAPE
) {
130 std::swap(widthInches
, heightInInches
);
132 aSettingsInitializer
.mPaperInfo
.mSize
.SizeTo(widthInches
* kPointsPerInch
,
133 heightInInches
* kPointsPerInch
);
135 gfx::MarginDouble margin
=
136 WinUtils::GetUnwriteableMarginsForDeviceInInches(printerDc
);
137 aSettingsInitializer
.mPaperInfo
.mUnwriteableMargin
= Some(MarginDouble
{
138 margin
.top
* kPointsPerInch
, margin
.right
* kPointsPerInch
,
139 margin
.bottom
* kPointsPerInch
, margin
.left
* kPointsPerInch
});
141 // Using Y to match existing code for print scaling calculations.
142 aSettingsInitializer
.mResolution
= pixelsPerInchY
;
144 if (aDevmode
->dmFields
& DM_COLOR
) {
145 // See comment for PrintSettingsInitializer.mPrintInColor
146 aSettingsInitializer
.mPrintInColor
=
147 aDevmode
->dmColor
!= DMCOLOR_MONOCHROME
;
150 if (aDevmode
->dmFields
& DM_ORIENTATION
) {
151 aSettingsInitializer
.mSheetOrientation
=
152 int32_t(aDevmode
->dmOrientation
== DMORIENT_PORTRAIT
153 ? nsPrintSettings::kPortraitOrientation
154 : nsPrintSettings::kLandscapeOrientation
);
157 if (aDevmode
->dmFields
& DM_COPIES
) {
158 aSettingsInitializer
.mNumCopies
= aDevmode
->dmCopies
;
161 if (aDevmode
->dmFields
& DM_DUPLEX
) {
162 switch (aDevmode
->dmDuplex
) {
164 MOZ_FALLTHROUGH_ASSERT("bad value for dmDuplex field");
166 aSettingsInitializer
.mDuplex
= nsPrintSettings::kDuplexNone
;
169 aSettingsInitializer
.mDuplex
= nsPrintSettings::kDuplexFlipOnLongEdge
;
171 case DMDUP_HORIZONTAL
:
172 aSettingsInitializer
.mDuplex
= nsPrintSettings::kDuplexFlipOnShortEdge
;
179 nsPrinterWin::GetName(nsAString
& aName
) {
185 nsPrinterWin::GetSystemName(nsAString
& aName
) {
192 void ResolveOrReject(Promise
& aPromise
, nsPrinterWin
& aPrinter
,
193 const PrintSettingsInitializer
& aResult
) {
194 aPromise
.MaybeResolve(
195 RefPtr
<nsIPrintSettings
>(CreatePlatformPrintSettings(aResult
)));
197 } // namespace mozilla
199 NS_IMETHODIMP
nsPrinterWin::CopyFromWithValidation(
200 nsIPrintSettings
* aSettingsToCopyFrom
, JSContext
* aCx
,
201 Promise
** aResultPromise
) {
202 MOZ_ASSERT(NS_IsMainThread());
203 MOZ_ASSERT(aResultPromise
);
205 PrintSettingsInitializer settingsInitializer
=
206 aSettingsToCopyFrom
->GetSettingsInitializer();
207 return PrintBackgroundTaskPromise(
208 *this, aCx
, aResultPromise
, "CopyFromWithValidation"_ns
,
209 &nsPrinterWin::GetValidatedSettings
, settingsInitializer
);
212 bool nsPrinterWin::SupportsDuplex() const {
213 MutexAutoLock
autoLock(mDriverMutex
);
214 return ::DeviceCapabilitiesW(mName
.get(), nullptr, DC_DUPLEX
, nullptr,
218 bool nsPrinterWin::SupportsColor() const {
219 MutexAutoLock
autoLock(mDriverMutex
);
220 return ::DeviceCapabilitiesW(mName
.get(), nullptr, DC_COLORDEVICE
, nullptr,
224 bool nsPrinterWin::SupportsMonochrome() const {
225 if (!SupportsColor()) {
229 nsHPRINTER hPrinter
= nullptr;
230 if (NS_WARN_IF(!::OpenPrinterW(mName
.get(), &hPrinter
, nullptr))) {
233 nsAutoPrinter
autoPrinter(hPrinter
);
235 nsTArray
<uint8_t> devmodeWStorage
= CopyDefaultDevmodeW();
236 if (devmodeWStorage
.IsEmpty()) {
240 auto* devmode
= reinterpret_cast<DEVMODEW
*>(devmodeWStorage
.Elements());
242 devmode
->dmFields
|= DM_COLOR
;
243 devmode
->dmColor
= DMCOLOR_MONOCHROME
;
244 // Try to modify the devmode settings and see if the setting sticks.
246 // This has been the only reliable way to detect it that we've found.
247 MutexAutoLock
autoLock(mDriverMutex
);
249 ::DocumentPropertiesW(nullptr, autoPrinter
.get(), mName
.get(), devmode
,
250 devmode
, DM_IN_BUFFER
| DM_OUT_BUFFER
);
254 return !(devmode
->dmFields
& DM_COLOR
) ||
255 devmode
->dmColor
== DMCOLOR_MONOCHROME
;
258 bool nsPrinterWin::SupportsCollation() const {
259 MutexAutoLock
autoLock(mDriverMutex
);
260 return ::DeviceCapabilitiesW(mName
.get(), nullptr, DC_COLLATE
, nullptr,
264 nsPrinterBase::PrinterInfo
nsPrinterWin::CreatePrinterInfo() const {
265 return PrinterInfo
{PaperList(), DefaultSettings()};
268 mozilla::gfx::MarginDouble
nsPrinterWin::GetMarginsForPaper(
269 nsString aPaperId
) const {
270 gfx::MarginDouble margin
;
272 nsTArray
<uint8_t> devmodeWStorage
= CopyDefaultDevmodeW();
273 if (devmodeWStorage
.IsEmpty()) {
277 auto* devmode
= reinterpret_cast<DEVMODEW
*>(devmodeWStorage
.Elements());
279 devmode
->dmFields
= DM_PAPERSIZE
;
280 devmode
->dmPaperSize
= _wtoi((const wchar_t*)aPaperId
.BeginReading());
283 MutexAutoLock
autoLock(mDriverMutex
);
284 dc
= ::CreateICW(nullptr, mName
.get(), nullptr, devmode
);
286 nsAutoHDC
printerDc(dc
);
287 MOZ_ASSERT(printerDc
, "CreateICW failed");
291 margin
= WinUtils::GetUnwriteableMarginsForDeviceInInches(printerDc
);
292 margin
.top
*= kPointsPerInch
;
293 margin
.right
*= kPointsPerInch
;
294 margin
.bottom
*= kPointsPerInch
;
295 margin
.left
*= kPointsPerInch
;
300 nsTArray
<uint8_t> nsPrinterWin::CopyDefaultDevmodeW() const {
301 nsTArray
<uint8_t> devmodeStorageW
;
303 auto devmodeStorageWLock
= mDefaultDevmodeWStorage
.Lock();
304 if (devmodeStorageWLock
->IsEmpty()) {
305 nsHPRINTER hPrinter
= nullptr;
306 // OpenPrinter could fail if, for example, the printer has been removed
307 // or otherwise become inaccessible since it was selected.
308 if (NS_WARN_IF(!::OpenPrinterW(mName
.get(), &hPrinter
, nullptr))) {
309 return devmodeStorageW
;
311 nsAutoPrinter
autoPrinter(hPrinter
);
312 // Allocate devmode storage of the correct size.
313 MutexAutoLock
autoLock(mDriverMutex
);
314 LONG bytesNeeded
= ::DocumentPropertiesW(nullptr, autoPrinter
.get(),
315 mName
.get(), nullptr, nullptr, 0);
316 // Note that we must cast the sizeof() to a signed type so that comparison
317 // with the signed, potentially-negative bytesNeeded will work!
318 MOZ_ASSERT(bytesNeeded
>= LONG(sizeof(DEVMODEW
)),
319 "DocumentPropertiesW failed to get valid size");
320 if (bytesNeeded
< LONG(sizeof(DEVMODEW
))) {
321 return devmodeStorageW
;
324 // Allocate extra space in case of bad drivers that return a too-small
325 // result from DocumentProperties.
326 // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1664530#c5)
327 if (!devmodeStorageWLock
->SetLength(bytesNeeded
* 2, fallible
)) {
328 return devmodeStorageW
;
331 memset(devmodeStorageWLock
->Elements(), 0, devmodeStorageWLock
->Length());
333 reinterpret_cast<DEVMODEW
*>(devmodeStorageWLock
->Elements());
334 LONG ret
= ::DocumentPropertiesW(nullptr, autoPrinter
.get(), mName
.get(),
335 devmode
, nullptr, DM_OUT_BUFFER
);
336 MOZ_ASSERT(ret
== IDOK
, "DocumentPropertiesW failed");
337 // Make sure that the lengths in the DEVMODEW make sense.
338 if (ret
!= IDOK
|| devmode
->dmSize
!= sizeof(DEVMODEW
) ||
339 devmode
->dmSize
+ devmode
->dmDriverExtra
>
340 devmodeStorageWLock
->Length()) {
341 // Clear mDefaultDevmodeWStorage to make sure we try again next time.
342 devmodeStorageWLock
->Clear();
343 return devmodeStorageW
;
347 devmodeStorageW
.Assign(devmodeStorageWLock
.ref());
348 return devmodeStorageW
;
351 nsTArray
<mozilla::PaperInfo
> nsPrinterWin::PaperList() const {
352 // Paper IDs are returned as WORDs.
353 int requiredArrayCount
= 0;
354 auto paperIds
= GetDeviceCapabilityArray
<WORD
>(
355 mName
.get(), DC_PAPERS
, mDriverMutex
, requiredArrayCount
);
356 if (!paperIds
.Length()) {
360 // Paper names are returned in 64 long character buffers.
361 auto paperNames
= GetDeviceCapabilityArray
<Array
<wchar_t, 64>>(
362 mName
.get(), DC_PAPERNAMES
, mDriverMutex
, requiredArrayCount
);
363 // Check that we have the same number of names as IDs.
364 if (paperNames
.Length() != paperIds
.Length()) {
368 // Paper sizes are returned as POINT structs with a tenth of a millimeter as
370 auto paperSizes
= GetDeviceCapabilityArray
<POINT
>(
371 mName
.get(), DC_PAPERSIZE
, mDriverMutex
, requiredArrayCount
);
372 // Check that we have the same number of sizes as IDs.
373 if (paperSizes
.Length() != paperIds
.Length()) {
377 nsTArray
<mozilla::PaperInfo
> paperList
;
378 paperList
.SetCapacity(paperNames
.Length());
379 for (size_t i
= 0; i
< paperNames
.Length(); ++i
) {
380 // Paper names are null terminated unless they are 64 characters long.
382 std::find(paperNames
[i
].cbegin(), paperNames
[i
].cend(), L
'\0');
383 auto nameLength
= firstNull
- paperNames
[i
].cbegin();
384 double width
= paperSizes
[i
].x
* kPointsPerTenthMM
;
385 double height
= paperSizes
[i
].y
* kPointsPerTenthMM
;
387 // Skip if no name or invalid size.
388 if (!nameLength
|| width
<= 0 || height
<= 0) {
392 // Windows paper IDs are 16-bit integers; we stringify them to store in the
393 // PaperInfo.mId field.
394 nsString paperIdString
;
395 paperIdString
.AppendInt(paperIds
[i
]);
397 // We don't resolve the margins eagerly because they're really expensive (on
398 // the order of seconds for some drivers).
399 nsDependentSubstring
name(paperNames
[i
].cbegin(), nameLength
);
400 paperList
.AppendElement(mozilla::PaperInfo(paperIdString
, nsString(name
),
401 {width
, height
}, Nothing()));
407 PrintSettingsInitializer
nsPrinterWin::DefaultSettings() const {
408 nsTArray
<uint8_t> devmodeWStorage
= CopyDefaultDevmodeW();
409 if (devmodeWStorage
.IsEmpty()) {
413 const auto* devmode
=
414 reinterpret_cast<const DEVMODEW
*>(devmodeWStorage
.Elements());
416 PrintSettingsInitializer settingsInitializer
;
417 DevmodeToSettingsInitializer(mName
, devmode
, mDriverMutex
,
418 settingsInitializer
);
419 settingsInitializer
.mDevmodeWStorage
= std::move(devmodeWStorage
);
420 return settingsInitializer
;
423 PrintSettingsInitializer
nsPrinterWin::GetValidatedSettings(
424 PrintSettingsInitializer aSettingsToValidate
) const {
425 // This function validates the settings by relying on the printer driver
426 // rejecting any invalid settings and resetting them to valid values.
428 // Create a copy of the default DEVMODE for this printer.
429 nsTArray
<uint8_t> devmodeWStorage
= CopyDefaultDevmodeW();
430 if (devmodeWStorage
.IsEmpty()) {
431 return aSettingsToValidate
;
434 nsHPRINTER hPrinter
= nullptr;
435 if (NS_WARN_IF(!::OpenPrinterW(mName
.get(), &hPrinter
, nullptr))) {
436 return aSettingsToValidate
;
438 nsAutoPrinter
autoPrinter(hPrinter
);
440 // Copy the settings from aSettingsToValidate into our DEVMODE.
441 DEVMODEW
* devmode
= reinterpret_cast<DEVMODEW
*>(devmodeWStorage
.Elements());
442 if (!aSettingsToValidate
.mPaperInfo
.mId
.IsEmpty()) {
443 devmode
->dmPaperSize
= _wtoi(
444 (const wchar_t*)aSettingsToValidate
.mPaperInfo
.mId
.BeginReading());
445 devmode
->dmFields
|= DM_PAPERSIZE
;
447 devmode
->dmPaperSize
= 0;
448 devmode
->dmFields
&= ~DM_PAPERSIZE
;
451 devmode
->dmFields
|= DM_COLOR
;
453 aSettingsToValidate
.mPrintInColor
? DMCOLOR_COLOR
: DMCOLOR_MONOCHROME
;
455 // Note: small page sizes can be required here for sticker, label and slide
456 // printers etc. see bug 1271900.
457 if (aSettingsToValidate
.mPaperInfo
.mSize
.height
> 0) {
458 devmode
->dmPaperLength
= std::round(
459 aSettingsToValidate
.mPaperInfo
.mSize
.height
/ kPointsPerTenthMM
);
460 devmode
->dmFields
|= DM_PAPERLENGTH
;
462 devmode
->dmPaperLength
= 0;
463 devmode
->dmFields
&= ~DM_PAPERLENGTH
;
466 if (aSettingsToValidate
.mPaperInfo
.mSize
.width
> 0) {
467 devmode
->dmPaperWidth
= std::round(
468 aSettingsToValidate
.mPaperInfo
.mSize
.width
/ kPointsPerTenthMM
);
469 devmode
->dmFields
|= DM_PAPERWIDTH
;
471 devmode
->dmPaperWidth
= 0;
472 devmode
->dmFields
&= ~DM_PAPERWIDTH
;
476 devmode
->dmOrientation
= aSettingsToValidate
.mSheetOrientation
==
477 nsPrintSettings::kPortraitOrientation
479 : DMORIENT_LANDSCAPE
;
480 devmode
->dmFields
|= DM_ORIENTATION
;
482 // Setup Number of Copies
483 devmode
->dmCopies
= aSettingsToValidate
.mNumCopies
;
484 devmode
->dmFields
|= DM_COPIES
;
486 // Setup Simplex/Duplex mode
487 devmode
->dmFields
|= DM_DUPLEX
;
488 switch (aSettingsToValidate
.mDuplex
) {
489 case nsPrintSettings::kDuplexNone
:
490 devmode
->dmDuplex
= DMDUP_SIMPLEX
;
492 case nsPrintSettings::kDuplexFlipOnLongEdge
:
493 devmode
->dmDuplex
= DMDUP_VERTICAL
;
495 case nsPrintSettings::kDuplexFlipOnShortEdge
:
496 devmode
->dmDuplex
= DMDUP_HORIZONTAL
;
499 MOZ_ASSERT_UNREACHABLE("bad value for duplex option");
503 // Apply the settings in the DEVMODE to the printer and retrieve the updated
504 // DEVMODE back into the same structure.
507 MutexAutoLock
autoLock(mDriverMutex
);
508 ret
= ::DocumentPropertiesW(nullptr, autoPrinter
.get(), mName
.get(),
509 devmode
, devmode
, DM_IN_BUFFER
| DM_OUT_BUFFER
);
512 return aSettingsToValidate
;
515 // Copy the settings back from the DEVMODE into aSettingsToValidate and
517 DevmodeToSettingsInitializer(mName
, devmode
, mDriverMutex
,
518 aSettingsToValidate
);
519 aSettingsToValidate
.mDevmodeWStorage
= std::move(devmodeWStorage
);
520 return aSettingsToValidate
;