1 /* -*- Mode: C++; tab-width: 2; 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 /* -------------------------------------------------------------------
9 You need to add this to the the makefile.win in mozilla/dom/base:
11 .\$(OBJDIR)\nsFlyOwnPrintDialog.obj \
14 And this to the makefile.win in mozilla/content/build:
21 ---------------------------------------------------------------------- */
29 #include "mozilla/BackgroundHangMonitor.h"
30 #include "mozilla/ScopeExit.h"
31 #include "mozilla/Span.h"
33 #include "nsReadableUtils.h"
34 #include "nsIPrintSettings.h"
35 #include "nsIPrintSettingsWin.h"
36 #include "nsIPrinterList.h"
37 #include "nsServiceManagerUtils.h"
42 #include "prenv.h" /* for PR_GetEnv */
49 // For NS_CopyUnicodeToNative
50 #include "nsNativeCharsetUtils.h"
52 // This is for extending the dialog
55 #include "nsWindowsHelpers.h"
58 //-----------------------------------------------
60 //-----------------------------------------------
62 static HWND gParentWnd
= nullptr;
64 //----------------------------------------------------------------------------------
65 // Returns a Global Moveable Memory Handle to a DevMode
66 // from the Printer by the name of aPrintName
69 // This function assumes that aPrintName has already been converted from
72 static nsReturnRef
<nsHGLOBAL
> CreateGlobalDevModeAndInit(
73 const nsString
& aPrintName
, nsIPrintSettings
* aPS
) {
74 nsHPRINTER hPrinter
= nullptr;
75 // const cast kludge for silly Win32 api's
77 const_cast<wchar_t*>(static_cast<const wchar_t*>(aPrintName
.get()));
78 BOOL status
= ::OpenPrinterW(printName
, &hPrinter
, nullptr);
80 return nsReturnRef
<nsHGLOBAL
>();
83 // Make sure hPrinter is closed on all paths
84 nsAutoPrinter
autoPrinter(hPrinter
);
86 // Get the buffer size
87 LONG needed
= ::DocumentPropertiesW(gParentWnd
, hPrinter
, printName
, nullptr,
90 return nsReturnRef
<nsHGLOBAL
>();
93 // Some drivers do not return the correct size for their DEVMODE, so we
94 // over-allocate to try and compensate.
95 // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1664530#c5)
97 nsAutoDevMode
newDevMode(
98 (LPDEVMODEW
)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY
, needed
));
100 return nsReturnRef
<nsHGLOBAL
>();
103 nsHGLOBAL hDevMode
= ::GlobalAlloc(GHND
, needed
);
104 nsAutoGlobalMem
globalDevMode(hDevMode
);
106 return nsReturnRef
<nsHGLOBAL
>();
109 LONG ret
= ::DocumentPropertiesW(gParentWnd
, hPrinter
, printName
, newDevMode
,
110 nullptr, DM_OUT_BUFFER
);
112 return nsReturnRef
<nsHGLOBAL
>();
115 // Lock memory and copy contents from DEVMODE (current printer)
116 // to Global Memory DEVMODE
117 LPDEVMODEW devMode
= (DEVMODEW
*)::GlobalLock(hDevMode
);
119 return nsReturnRef
<nsHGLOBAL
>();
122 memcpy(devMode
, newDevMode
.get(), needed
);
123 // Initialize values from the PrintSettings
124 nsCOMPtr
<nsIPrintSettingsWin
> psWin
= do_QueryInterface(aPS
);
126 psWin
->CopyToNative(devMode
);
128 // Sets back the changes we made to the DevMode into the Printer Driver
129 ret
= ::DocumentPropertiesW(gParentWnd
, hPrinter
, printName
, devMode
, devMode
,
130 DM_IN_BUFFER
| DM_OUT_BUFFER
);
132 ::GlobalUnlock(hDevMode
);
133 return nsReturnRef
<nsHGLOBAL
>();
136 ::GlobalUnlock(hDevMode
);
138 return globalDevMode
.out();
141 //------------------------------------------------------------------
143 static void GetDefaultPrinterNameFromGlobalPrinters(nsAString
& aPrinterName
) {
144 aPrinterName
.Truncate();
145 nsCOMPtr
<nsIPrinterList
> printerList
=
146 do_GetService("@mozilla.org/gfx/printerlist;1");
148 printerList
->GetSystemDefaultPrinterName(aPrinterName
);
152 //------------------------------------------------------------------
153 // Displays the native Print Dialog
154 nsresult
NativeShowPrintDialog(HWND aHWnd
, bool aHaveSelection
,
155 nsIPrintSettings
* aPrintSettings
) {
156 // NS_ENSURE_ARG_POINTER(aHWnd);
157 NS_ENSURE_ARG_POINTER(aPrintSettings
);
159 // Get the Print Name to be used
160 nsString printerName
;
161 aPrintSettings
->GetPrinterName(printerName
);
163 // If there is no name then use the default printer
164 if (printerName
.IsEmpty()) {
165 GetDefaultPrinterNameFromGlobalPrinters(printerName
);
167 HANDLE hPrinter
= nullptr;
168 if (!::OpenPrinterW(const_cast<wchar_t*>(
169 static_cast<const wchar_t*>(printerName
.get())),
170 &hPrinter
, nullptr)) {
171 // If the last used printer is not found, we should use default printer.
172 GetDefaultPrinterNameFromGlobalPrinters(printerName
);
174 ::ClosePrinter(hPrinter
);
178 // Now create a DEVNAMES struct so the the dialog is initialized correctly.
180 uint32_t len
= printerName
.Length();
181 nsHGLOBAL hDevNames
=
182 ::GlobalAlloc(GHND
, sizeof(wchar_t) * (len
+ 1) + sizeof(DEVNAMES
));
183 nsAutoGlobalMem
autoDevNames(hDevNames
);
185 return NS_ERROR_OUT_OF_MEMORY
;
188 DEVNAMES
* pDevNames
= (DEVNAMES
*)::GlobalLock(hDevNames
);
190 return NS_ERROR_FAILURE
;
192 pDevNames
->wDriverOffset
= sizeof(DEVNAMES
) / sizeof(wchar_t);
193 pDevNames
->wDeviceOffset
= sizeof(DEVNAMES
) / sizeof(wchar_t);
194 pDevNames
->wOutputOffset
= sizeof(DEVNAMES
) / sizeof(wchar_t) + len
;
195 pDevNames
->wDefault
= 0;
197 memcpy(pDevNames
+ 1, printerName
.get(), (len
+ 1) * sizeof(wchar_t));
198 ::GlobalUnlock(hDevNames
);
200 // Create a Moveable Memory Object that holds a new DevMode
201 // from the Printer Name
202 // The PRINTDLG.hDevMode requires that it be a moveable memory object
203 // NOTE: autoDevMode is automatically freed when any error occurred
204 nsAutoGlobalMem
autoDevMode(
205 CreateGlobalDevModeAndInit(printerName
, aPrintSettings
));
207 // Prepare to Display the Print Dialog
208 // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms646942(v=vs.85)
209 // https://docs.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-printdlgexw
211 memset(&prntdlg
, 0, sizeof(prntdlg
));
213 prntdlg
.lStructSize
= sizeof(prntdlg
);
214 prntdlg
.hwndOwner
= aHWnd
;
215 prntdlg
.hDevMode
= autoDevMode
.get();
216 prntdlg
.hDevNames
= hDevNames
;
217 prntdlg
.hDC
= nullptr;
218 prntdlg
.Flags
= PD_ALLPAGES
| PD_RETURNIC
| PD_USEDEVMODECOPIESANDCOLLATE
|
219 PD_COLLATE
| PD_NOCURRENTPAGE
;
221 // If there is a current selection then enable the "Selection" radio button
222 if (!aHaveSelection
) {
223 prntdlg
.Flags
|= PD_NOSELECTION
;
226 // 10 seems like a reasonable max number of ranges to support by default if
227 // the user doesn't choose a greater thing in the UI.
228 constexpr size_t kMinSupportedRanges
= 10;
230 AutoTArray
<PRINTPAGERANGE
, kMinSupportedRanges
> winPageRanges
;
231 // Set up the page ranges.
233 AutoTArray
<int32_t, kMinSupportedRanges
* 2> pageRanges
;
234 aPrintSettings
->GetPageRanges(pageRanges
);
235 // If there is a specified page range then enable the "Custom" radio button
236 if (!pageRanges
.IsEmpty()) {
237 prntdlg
.Flags
|= PD_PAGENUMS
;
240 const size_t specifiedRanges
= pageRanges
.Length() / 2;
241 const size_t maxRanges
= std::max(kMinSupportedRanges
, specifiedRanges
);
243 prntdlg
.nMaxPageRanges
= maxRanges
;
244 prntdlg
.nPageRanges
= specifiedRanges
;
246 winPageRanges
.SetCapacity(maxRanges
);
247 for (size_t i
= 0; i
< pageRanges
.Length(); i
+= 2) {
248 PRINTPAGERANGE
* range
= winPageRanges
.AppendElement();
249 range
->nFromPage
= pageRanges
[i
];
250 range
->nToPage
= pageRanges
[i
+ 1];
252 prntdlg
.lpPageRanges
= winPageRanges
.Elements();
254 prntdlg
.nMinPage
= 1;
255 // TODO(emilio): Could probably get the right page number here from the
257 prntdlg
.nMaxPage
= 0xFFFF;
260 // NOTE(emilio): This can always be 1 because we use the DEVMODE copies
261 // feature (see PD_USEDEVMODECOPIESANDCOLLATE).
264 prntdlg
.hInstance
= nullptr;
265 prntdlg
.lpPrintTemplateName
= nullptr;
267 prntdlg
.lpCallback
= nullptr;
268 prntdlg
.nPropertyPages
= 0;
269 prntdlg
.lphPropertyPages
= nullptr;
271 prntdlg
.nStartPage
= START_PAGE_GENERAL
;
272 prntdlg
.dwResultAction
= 0;
276 mozilla::widget::WinUtils::AutoSystemDpiAware dpiAwareness
;
277 mozilla::BackgroundHangMonitor().NotifyWait();
278 result
= ::PrintDlgExW(&prntdlg
);
281 auto cancelOnExit
= mozilla::MakeScopeExit([&] { ::SetFocus(aHWnd
); });
283 if (NS_WARN_IF(!SUCCEEDED(result
))) {
285 printf_stderr("PrintDlgExW failed with %lx\n", result
);
287 return NS_ERROR_FAILURE
;
289 if (NS_WARN_IF(prntdlg
.dwResultAction
!= PD_RESULT_PRINT
)) {
290 return NS_ERROR_ABORT
;
292 // check to make sure we don't have any nullptr pointers
293 NS_ENSURE_TRUE(prntdlg
.hDevMode
, NS_ERROR_ABORT
);
294 NS_ENSURE_TRUE(prntdlg
.hDevNames
, NS_ERROR_ABORT
);
295 // Lock the deviceNames and check for nullptr
296 DEVNAMES
* devnames
= (DEVNAMES
*)::GlobalLock(prntdlg
.hDevNames
);
297 NS_ENSURE_TRUE(devnames
, NS_ERROR_ABORT
);
299 char16_t
* device
= &(((char16_t
*)devnames
)[devnames
->wDeviceOffset
]);
300 char16_t
* driver
= &(((char16_t
*)devnames
)[devnames
->wDriverOffset
]);
302 // Check to see if the "Print To File" control is checked
303 // then take the name from devNames and set it in the PrintSettings
306 // As per Microsoft SDK documentation the returned value offset from
307 // devnames->wOutputOffset is either "FILE:" or nullptr
308 // if the "Print To File" checkbox is checked it MUST be "FILE:"
309 // We assert as an extra safety check.
310 if (prntdlg
.Flags
& PD_PRINTTOFILE
) {
311 char16ptr_t fileName
= &(((wchar_t*)devnames
)[devnames
->wOutputOffset
]);
312 NS_ASSERTION(wcscmp(fileName
, L
"FILE:") == 0, "FileName must be `FILE:`");
313 aPrintSettings
->SetOutputDestination(
314 nsIPrintSettings::kOutputDestinationFile
);
315 aPrintSettings
->SetToFileName(nsDependentString(fileName
));
317 // clear "print to file" info
318 aPrintSettings
->SetOutputDestination(
319 nsIPrintSettings::kOutputDestinationPrinter
);
320 aPrintSettings
->SetToFileName(u
""_ns
);
323 nsCOMPtr
<nsIPrintSettingsWin
> psWin(do_QueryInterface(aPrintSettings
));
324 MOZ_RELEASE_ASSERT(psWin
);
326 // Setup local Data members
327 psWin
->SetDeviceName(nsDependentString(device
));
328 psWin
->SetDriverName(nsDependentString(driver
));
330 // Fill the print options with the info from the dialog
331 aPrintSettings
->SetPrinterName(nsDependentString(device
));
332 aPrintSettings
->SetPrintSelectionOnly(prntdlg
.Flags
& PD_SELECTION
);
334 AutoTArray
<int32_t, kMinSupportedRanges
* 2> pageRanges
;
335 if (prntdlg
.Flags
& PD_PAGENUMS
) {
336 pageRanges
.SetCapacity(prntdlg
.nPageRanges
* 2);
337 for (const auto& range
:
338 mozilla::Span(prntdlg
.lpPageRanges
, prntdlg
.nPageRanges
)) {
339 pageRanges
.AppendElement(range
.nFromPage
);
340 pageRanges
.AppendElement(range
.nToPage
);
343 aPrintSettings
->SetPageRanges(pageRanges
);
345 // Unlock DeviceNames
346 ::GlobalUnlock(prntdlg
.hDevNames
);
348 // Transfer the settings from the native data to the PrintSettings
349 LPDEVMODEW devMode
= (LPDEVMODEW
)::GlobalLock(prntdlg
.hDevMode
);
350 if (!devMode
|| !prntdlg
.hDC
) {
351 return NS_ERROR_FAILURE
;
353 psWin
->SetDevMode(devMode
); // copies DevMode
354 psWin
->CopyFromNative(prntdlg
.hDC
, devMode
);
355 ::GlobalUnlock(prntdlg
.hDevMode
);
356 ::DeleteDC(prntdlg
.hDC
);
358 cancelOnExit
.release();