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 ---------------------------------------------------------------------- */
30 #include "mozilla/BackgroundHangMonitor.h"
31 #include "mozilla/ScopeExit.h"
32 #include "mozilla/Span.h"
34 #include "nsReadableUtils.h"
35 #include "nsIPrintSettings.h"
36 #include "nsIPrintSettingsWin.h"
37 #include "nsIPrinterList.h"
38 #include "nsServiceManagerUtils.h"
43 #include "prenv.h" /* for PR_GetEnv */
50 // For NS_CopyUnicodeToNative
51 #include "nsNativeCharsetUtils.h"
53 // This is for extending the dialog
56 #include "nsWindowsHelpers.h"
59 //-----------------------------------------------
61 //-----------------------------------------------
63 static HWND gParentWnd
= nullptr;
65 //----------------------------------------------------------------------------------
66 // Returns a Global Moveable Memory Handle to a DevMode
67 // from the Printer by the name of aPrintName
70 // This function assumes that aPrintName has already been converted from
73 static nsReturnRef
<nsHGLOBAL
> CreateGlobalDevModeAndInit(
74 const nsString
& aPrintName
, nsIPrintSettings
* aPS
) {
75 nsHPRINTER hPrinter
= nullptr;
76 // const cast kludge for silly Win32 api's
78 const_cast<wchar_t*>(static_cast<const wchar_t*>(aPrintName
.get()));
79 BOOL status
= ::OpenPrinterW(printName
, &hPrinter
, nullptr);
81 return nsReturnRef
<nsHGLOBAL
>();
84 // Make sure hPrinter is closed on all paths
85 nsAutoPrinter
autoPrinter(hPrinter
);
87 // Get the buffer size
88 LONG needed
= ::DocumentPropertiesW(gParentWnd
, hPrinter
, printName
, nullptr,
91 return nsReturnRef
<nsHGLOBAL
>();
94 // Some drivers do not return the correct size for their DEVMODE, so we
95 // over-allocate to try and compensate.
96 // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1664530#c5)
98 nsAutoDevMode
newDevMode(
99 (LPDEVMODEW
)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY
, needed
));
101 return nsReturnRef
<nsHGLOBAL
>();
104 nsHGLOBAL hDevMode
= ::GlobalAlloc(GHND
, needed
);
105 nsAutoGlobalMem
globalDevMode(hDevMode
);
107 return nsReturnRef
<nsHGLOBAL
>();
110 LONG ret
= ::DocumentPropertiesW(gParentWnd
, hPrinter
, printName
, newDevMode
,
111 nullptr, DM_OUT_BUFFER
);
113 return nsReturnRef
<nsHGLOBAL
>();
116 // Lock memory and copy contents from DEVMODE (current printer)
117 // to Global Memory DEVMODE
118 LPDEVMODEW devMode
= (DEVMODEW
*)::GlobalLock(hDevMode
);
120 return nsReturnRef
<nsHGLOBAL
>();
123 memcpy(devMode
, newDevMode
.get(), needed
);
124 // Initialize values from the PrintSettings
125 nsCOMPtr
<nsIPrintSettingsWin
> psWin
= do_QueryInterface(aPS
);
127 psWin
->CopyToNative(devMode
);
129 // Sets back the changes we made to the DevMode into the Printer Driver
130 ret
= ::DocumentPropertiesW(gParentWnd
, hPrinter
, printName
, devMode
, devMode
,
131 DM_IN_BUFFER
| DM_OUT_BUFFER
);
133 ::GlobalUnlock(hDevMode
);
134 return nsReturnRef
<nsHGLOBAL
>();
137 ::GlobalUnlock(hDevMode
);
139 return globalDevMode
.out();
142 //------------------------------------------------------------------
144 static void GetDefaultPrinterNameFromGlobalPrinters(nsAString
& aPrinterName
) {
145 aPrinterName
.Truncate();
146 nsCOMPtr
<nsIPrinterList
> printerList
=
147 do_GetService("@mozilla.org/gfx/printerlist;1");
149 printerList
->GetSystemDefaultPrinterName(aPrinterName
);
153 //------------------------------------------------------------------
154 // Displays the native Print Dialog
155 static nsresult
ShowNativePrintDialog(HWND aHWnd
,
156 nsIPrintSettings
* aPrintSettings
) {
157 // NS_ENSURE_ARG_POINTER(aHWnd);
158 NS_ENSURE_ARG_POINTER(aPrintSettings
);
160 // Get the Print Name to be used
161 nsString printerName
;
162 aPrintSettings
->GetPrinterName(printerName
);
164 // If there is no name then use the default printer
165 if (printerName
.IsEmpty()) {
166 GetDefaultPrinterNameFromGlobalPrinters(printerName
);
168 HANDLE hPrinter
= nullptr;
169 if (!::OpenPrinterW(const_cast<wchar_t*>(
170 static_cast<const wchar_t*>(printerName
.get())),
171 &hPrinter
, nullptr)) {
172 // If the last used printer is not found, we should use default printer.
173 GetDefaultPrinterNameFromGlobalPrinters(printerName
);
175 ::ClosePrinter(hPrinter
);
179 // Now create a DEVNAMES struct so the the dialog is initialized correctly.
181 uint32_t len
= printerName
.Length();
182 nsHGLOBAL hDevNames
=
183 ::GlobalAlloc(GHND
, sizeof(wchar_t) * (len
+ 1) + sizeof(DEVNAMES
));
184 nsAutoGlobalMem
autoDevNames(hDevNames
);
186 return NS_ERROR_OUT_OF_MEMORY
;
189 DEVNAMES
* pDevNames
= (DEVNAMES
*)::GlobalLock(hDevNames
);
191 return NS_ERROR_FAILURE
;
193 pDevNames
->wDriverOffset
= sizeof(DEVNAMES
) / sizeof(wchar_t);
194 pDevNames
->wDeviceOffset
= sizeof(DEVNAMES
) / sizeof(wchar_t);
195 pDevNames
->wOutputOffset
= sizeof(DEVNAMES
) / sizeof(wchar_t) + len
;
196 pDevNames
->wDefault
= 0;
198 memcpy(pDevNames
+ 1, printerName
.get(), (len
+ 1) * sizeof(wchar_t));
199 ::GlobalUnlock(hDevNames
);
201 // Create a Moveable Memory Object that holds a new DevMode
202 // from the Printer Name
203 // The PRINTDLG.hDevMode requires that it be a moveable memory object
204 // NOTE: autoDevMode is automatically freed when any error occurred
205 nsAutoGlobalMem
autoDevMode(
206 CreateGlobalDevModeAndInit(printerName
, aPrintSettings
));
208 // Prepare to Display the Print Dialog
209 // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms646942(v=vs.85)
210 // https://docs.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-printdlgexw
212 memset(&prntdlg
, 0, sizeof(prntdlg
));
214 prntdlg
.lStructSize
= sizeof(prntdlg
);
215 prntdlg
.hwndOwner
= aHWnd
;
216 prntdlg
.hDevMode
= autoDevMode
.get();
217 prntdlg
.hDevNames
= hDevNames
;
218 prntdlg
.hDC
= nullptr;
219 prntdlg
.Flags
= PD_ALLPAGES
| PD_RETURNIC
| PD_USEDEVMODECOPIESANDCOLLATE
|
220 PD_COLLATE
| PD_NOCURRENTPAGE
;
222 // If there is a current selection then enable the "Selection" radio button
223 if (!aPrintSettings
->GetIsPrintSelectionRBEnabled()) {
224 prntdlg
.Flags
|= PD_NOSELECTION
;
227 // 10 seems like a reasonable max number of ranges to support by default if
228 // the user doesn't choose a greater thing in the UI.
229 constexpr size_t kMinSupportedRanges
= 10;
231 AutoTArray
<PRINTPAGERANGE
, kMinSupportedRanges
> winPageRanges
;
232 // Set up the page ranges.
234 AutoTArray
<int32_t, kMinSupportedRanges
* 2> pageRanges
;
235 aPrintSettings
->GetPageRanges(pageRanges
);
236 // If there is a specified page range then enable the "Custom" radio button
237 if (!pageRanges
.IsEmpty()) {
238 prntdlg
.Flags
|= PD_PAGENUMS
;
241 const size_t specifiedRanges
= pageRanges
.Length() / 2;
242 const size_t maxRanges
= std::max(kMinSupportedRanges
, specifiedRanges
);
244 prntdlg
.nMaxPageRanges
= maxRanges
;
245 prntdlg
.nPageRanges
= specifiedRanges
;
247 winPageRanges
.SetCapacity(maxRanges
);
248 for (size_t i
= 0; i
< pageRanges
.Length(); i
+= 2) {
249 PRINTPAGERANGE
* range
= winPageRanges
.AppendElement();
250 range
->nFromPage
= pageRanges
[i
];
251 range
->nToPage
= pageRanges
[i
+ 1];
253 prntdlg
.lpPageRanges
= winPageRanges
.Elements();
255 prntdlg
.nMinPage
= 1;
256 // TODO(emilio): Could probably get the right page number here from the
258 prntdlg
.nMaxPage
= 0xFFFF;
261 // NOTE(emilio): This can always be 1 because we use the DEVMODE copies
262 // feature (see PD_USEDEVMODECOPIESANDCOLLATE).
265 prntdlg
.hInstance
= nullptr;
266 prntdlg
.lpPrintTemplateName
= nullptr;
268 prntdlg
.lpCallback
= nullptr;
269 prntdlg
.nPropertyPages
= 0;
270 prntdlg
.lphPropertyPages
= nullptr;
272 prntdlg
.nStartPage
= START_PAGE_GENERAL
;
273 prntdlg
.dwResultAction
= 0;
277 mozilla::widget::WinUtils::AutoSystemDpiAware dpiAwareness
;
278 mozilla::BackgroundHangMonitor().NotifyWait();
279 result
= ::PrintDlgExW(&prntdlg
);
282 auto cancelOnExit
= mozilla::MakeScopeExit([&] {
284 aPrintSettings
->SetIsCancelled(true);
287 if (NS_WARN_IF(!SUCCEEDED(result
))) {
289 printf_stderr("PrintDlgExW failed with %x\n", result
);
291 return NS_ERROR_FAILURE
;
293 if (NS_WARN_IF(prntdlg
.dwResultAction
!= PD_RESULT_PRINT
)) {
294 return NS_ERROR_ABORT
;
296 // check to make sure we don't have any nullptr pointers
297 NS_ENSURE_TRUE(prntdlg
.hDevMode
, NS_ERROR_ABORT
);
298 NS_ENSURE_TRUE(prntdlg
.hDevNames
, NS_ERROR_ABORT
);
299 // Lock the deviceNames and check for nullptr
300 DEVNAMES
* devnames
= (DEVNAMES
*)::GlobalLock(prntdlg
.hDevNames
);
301 NS_ENSURE_TRUE(devnames
, NS_ERROR_ABORT
);
303 char16_t
* device
= &(((char16_t
*)devnames
)[devnames
->wDeviceOffset
]);
304 char16_t
* driver
= &(((char16_t
*)devnames
)[devnames
->wDriverOffset
]);
306 // Check to see if the "Print To File" control is checked
307 // then take the name from devNames and set it in the PrintSettings
310 // As per Microsoft SDK documentation the returned value offset from
311 // devnames->wOutputOffset is either "FILE:" or nullptr
312 // if the "Print To File" checkbox is checked it MUST be "FILE:"
313 // We assert as an extra safety check.
314 if (prntdlg
.Flags
& PD_PRINTTOFILE
) {
315 char16ptr_t fileName
= &(((wchar_t*)devnames
)[devnames
->wOutputOffset
]);
316 NS_ASSERTION(wcscmp(fileName
, L
"FILE:") == 0, "FileName must be `FILE:`");
317 aPrintSettings
->SetToFileName(nsDependentString(fileName
));
318 aPrintSettings
->SetPrintToFile(true);
320 // clear "print to file" info
321 aPrintSettings
->SetPrintToFile(false);
322 aPrintSettings
->SetToFileName(u
""_ns
);
325 nsCOMPtr
<nsIPrintSettingsWin
> psWin(do_QueryInterface(aPrintSettings
));
326 MOZ_RELEASE_ASSERT(psWin
);
328 // Setup local Data members
329 psWin
->SetDeviceName(nsDependentString(device
));
330 psWin
->SetDriverName(nsDependentString(driver
));
332 // Fill the print options with the info from the dialog
333 aPrintSettings
->SetPrinterName(nsDependentString(device
));
334 aPrintSettings
->SetPrintSelectionOnly(prntdlg
.Flags
& PD_SELECTION
);
336 AutoTArray
<int32_t, kMinSupportedRanges
* 2> pageRanges
;
337 if (prntdlg
.Flags
& PD_PAGENUMS
) {
338 pageRanges
.SetCapacity(prntdlg
.nPageRanges
* 2);
339 for (const auto& range
:
340 mozilla::Span(prntdlg
.lpPageRanges
, prntdlg
.nPageRanges
)) {
341 pageRanges
.AppendElement(range
.nFromPage
);
342 pageRanges
.AppendElement(range
.nToPage
);
345 aPrintSettings
->SetPageRanges(pageRanges
);
347 // Unlock DeviceNames
348 ::GlobalUnlock(prntdlg
.hDevNames
);
350 // Transfer the settings from the native data to the PrintSettings
351 LPDEVMODEW devMode
= (LPDEVMODEW
)::GlobalLock(prntdlg
.hDevMode
);
352 if (!devMode
|| !prntdlg
.hDC
) {
353 return NS_ERROR_FAILURE
;
355 psWin
->SetDevMode(devMode
); // copies DevMode
356 psWin
->CopyFromNative(prntdlg
.hDC
, devMode
);
357 ::GlobalUnlock(prntdlg
.hDevMode
);
358 ::DeleteDC(prntdlg
.hDC
);
360 cancelOnExit
.release();
364 //----------------------------------------------------------------------------------
365 //-- Show Print Dialog
366 //----------------------------------------------------------------------------------
367 nsresult
NativeShowPrintDialog(HWND aHWnd
, nsIPrintSettings
* aPrintSettings
) {
368 nsresult rv
= ShowNativePrintDialog(aHWnd
, aPrintSettings
);
370 ::DestroyWindow(aHWnd
);