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
76 LPWSTR printName
= const_cast<wchar_t*>(aPrintName
.getW());
77 BOOL status
= ::OpenPrinterW(printName
, &hPrinter
, nullptr);
79 return nsReturnRef
<nsHGLOBAL
>();
82 // Make sure hPrinter is closed on all paths
83 nsAutoPrinter
autoPrinter(hPrinter
);
85 // Get the buffer size
86 LONG needed
= ::DocumentPropertiesW(gParentWnd
, hPrinter
, printName
, nullptr,
89 return nsReturnRef
<nsHGLOBAL
>();
92 // Some drivers do not return the correct size for their DEVMODE, so we
93 // over-allocate to try and compensate.
94 // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1664530#c5)
96 nsAutoDevMode
newDevMode(
97 (LPDEVMODEW
)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY
, needed
));
99 return nsReturnRef
<nsHGLOBAL
>();
102 nsHGLOBAL hDevMode
= ::GlobalAlloc(GHND
, needed
);
103 nsAutoGlobalMem
globalDevMode(hDevMode
);
105 return nsReturnRef
<nsHGLOBAL
>();
108 LONG ret
= ::DocumentPropertiesW(gParentWnd
, hPrinter
, printName
, newDevMode
,
109 nullptr, DM_OUT_BUFFER
);
111 return nsReturnRef
<nsHGLOBAL
>();
114 // Lock memory and copy contents from DEVMODE (current printer)
115 // to Global Memory DEVMODE
116 LPDEVMODEW devMode
= (DEVMODEW
*)::GlobalLock(hDevMode
);
118 return nsReturnRef
<nsHGLOBAL
>();
121 memcpy(devMode
, newDevMode
.get(), needed
);
122 // Initialize values from the PrintSettings
123 nsCOMPtr
<nsIPrintSettingsWin
> psWin
= do_QueryInterface(aPS
);
125 psWin
->CopyToNative(devMode
);
127 // Sets back the changes we made to the DevMode into the Printer Driver
128 ret
= ::DocumentPropertiesW(gParentWnd
, hPrinter
, printName
, devMode
, devMode
,
129 DM_IN_BUFFER
| DM_OUT_BUFFER
);
131 ::GlobalUnlock(hDevMode
);
132 return nsReturnRef
<nsHGLOBAL
>();
135 ::GlobalUnlock(hDevMode
);
137 return globalDevMode
.out();
140 //------------------------------------------------------------------
142 static void GetDefaultPrinterNameFromGlobalPrinters(nsAString
& aPrinterName
) {
143 aPrinterName
.Truncate();
144 nsCOMPtr
<nsIPrinterList
> printerList
=
145 do_GetService("@mozilla.org/gfx/printerlist;1");
147 printerList
->GetSystemDefaultPrinterName(aPrinterName
);
151 //------------------------------------------------------------------
152 // Displays the native Print Dialog
153 nsresult
NativeShowPrintDialog(HWND aHWnd
, bool aHaveSelection
,
154 nsIPrintSettings
* aPrintSettings
) {
155 // NS_ENSURE_ARG_POINTER(aHWnd);
156 NS_ENSURE_ARG_POINTER(aPrintSettings
);
158 // Get the Print Name to be used
159 nsString printerName
;
160 aPrintSettings
->GetPrinterName(printerName
);
162 // If there is no name then use the default printer
163 if (printerName
.IsEmpty()) {
164 GetDefaultPrinterNameFromGlobalPrinters(printerName
);
166 HANDLE hPrinter
= nullptr;
167 if (!::OpenPrinterW(const_cast<wchar_t*>(printerName
.getW()), &hPrinter
,
169 // If the last used printer is not found, we should use default printer.
170 GetDefaultPrinterNameFromGlobalPrinters(printerName
);
172 ::ClosePrinter(hPrinter
);
176 // Now create a DEVNAMES struct so the the dialog is initialized correctly.
178 uint32_t len
= printerName
.Length();
179 nsHGLOBAL hDevNames
=
180 ::GlobalAlloc(GHND
, sizeof(wchar_t) * (len
+ 1) + sizeof(DEVNAMES
));
181 nsAutoGlobalMem
autoDevNames(hDevNames
);
183 return NS_ERROR_OUT_OF_MEMORY
;
186 DEVNAMES
* pDevNames
= (DEVNAMES
*)::GlobalLock(hDevNames
);
188 return NS_ERROR_FAILURE
;
190 pDevNames
->wDriverOffset
= sizeof(DEVNAMES
) / sizeof(wchar_t);
191 pDevNames
->wDeviceOffset
= sizeof(DEVNAMES
) / sizeof(wchar_t);
192 pDevNames
->wOutputOffset
= sizeof(DEVNAMES
) / sizeof(wchar_t) + len
;
193 pDevNames
->wDefault
= 0;
195 memcpy(pDevNames
+ 1, printerName
.get(), (len
+ 1) * sizeof(wchar_t));
196 ::GlobalUnlock(hDevNames
);
198 // Create a Moveable Memory Object that holds a new DevMode
199 // from the Printer Name
200 // The PRINTDLG.hDevMode requires that it be a moveable memory object
201 // NOTE: autoDevMode is automatically freed when any error occurred
202 nsAutoGlobalMem
autoDevMode(
203 CreateGlobalDevModeAndInit(printerName
, aPrintSettings
));
205 // Prepare to Display the Print Dialog
206 // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms646942(v=vs.85)
207 // https://docs.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-printdlgexw
209 memset(&prntdlg
, 0, sizeof(prntdlg
));
211 prntdlg
.lStructSize
= sizeof(prntdlg
);
212 prntdlg
.hwndOwner
= aHWnd
;
213 prntdlg
.hDevMode
= autoDevMode
.get();
214 prntdlg
.hDevNames
= hDevNames
;
215 prntdlg
.hDC
= nullptr;
216 prntdlg
.Flags
= PD_ALLPAGES
| PD_RETURNIC
| PD_USEDEVMODECOPIESANDCOLLATE
|
217 PD_COLLATE
| PD_NOCURRENTPAGE
;
219 // If there is a current selection then enable the "Selection" radio button
220 if (!aHaveSelection
) {
221 prntdlg
.Flags
|= PD_NOSELECTION
;
224 // 10 seems like a reasonable max number of ranges to support by default if
225 // the user doesn't choose a greater thing in the UI.
226 constexpr size_t kMinSupportedRanges
= 10;
228 AutoTArray
<PRINTPAGERANGE
, kMinSupportedRanges
> winPageRanges
;
229 // Set up the page ranges.
231 AutoTArray
<int32_t, kMinSupportedRanges
* 2> pageRanges
;
232 aPrintSettings
->GetPageRanges(pageRanges
);
233 // If there is a specified page range then enable the "Custom" radio button
234 if (!pageRanges
.IsEmpty()) {
235 prntdlg
.Flags
|= PD_PAGENUMS
;
238 const size_t specifiedRanges
= pageRanges
.Length() / 2;
239 const size_t maxRanges
= std::max(kMinSupportedRanges
, specifiedRanges
);
241 prntdlg
.nMaxPageRanges
= maxRanges
;
242 prntdlg
.nPageRanges
= specifiedRanges
;
244 winPageRanges
.SetCapacity(maxRanges
);
245 for (size_t i
= 0; i
< pageRanges
.Length(); i
+= 2) {
246 PRINTPAGERANGE
* range
= winPageRanges
.AppendElement();
247 range
->nFromPage
= pageRanges
[i
];
248 range
->nToPage
= pageRanges
[i
+ 1];
250 prntdlg
.lpPageRanges
= winPageRanges
.Elements();
252 prntdlg
.nMinPage
= 1;
253 // TODO(emilio): Could probably get the right page number here from the
255 prntdlg
.nMaxPage
= 0xFFFF;
258 // NOTE(emilio): This can always be 1 because we use the DEVMODE copies
259 // feature (see PD_USEDEVMODECOPIESANDCOLLATE).
262 prntdlg
.hInstance
= nullptr;
263 prntdlg
.lpPrintTemplateName
= nullptr;
265 prntdlg
.lpCallback
= nullptr;
266 prntdlg
.nPropertyPages
= 0;
267 prntdlg
.lphPropertyPages
= nullptr;
269 prntdlg
.nStartPage
= START_PAGE_GENERAL
;
270 prntdlg
.dwResultAction
= 0;
274 mozilla::widget::WinUtils::AutoSystemDpiAware dpiAwareness
;
275 mozilla::BackgroundHangMonitor().NotifyWait();
276 result
= ::PrintDlgExW(&prntdlg
);
279 auto cancelOnExit
= mozilla::MakeScopeExit([&] { ::SetFocus(aHWnd
); });
281 if (NS_WARN_IF(!SUCCEEDED(result
))) {
283 printf_stderr("PrintDlgExW failed with %lx\n", result
);
285 return NS_ERROR_FAILURE
;
287 if (NS_WARN_IF(prntdlg
.dwResultAction
!= PD_RESULT_PRINT
)) {
288 return NS_ERROR_ABORT
;
290 // check to make sure we don't have any nullptr pointers
291 NS_ENSURE_TRUE(prntdlg
.hDevMode
, NS_ERROR_ABORT
);
292 NS_ENSURE_TRUE(prntdlg
.hDevNames
, NS_ERROR_ABORT
);
293 // Lock the deviceNames and check for nullptr
294 DEVNAMES
* devnames
= (DEVNAMES
*)::GlobalLock(prntdlg
.hDevNames
);
295 NS_ENSURE_TRUE(devnames
, NS_ERROR_ABORT
);
297 char16_t
* device
= &(((char16_t
*)devnames
)[devnames
->wDeviceOffset
]);
298 char16_t
* driver
= &(((char16_t
*)devnames
)[devnames
->wDriverOffset
]);
300 // Check to see if the "Print To File" control is checked
301 // then take the name from devNames and set it in the PrintSettings
304 // As per Microsoft SDK documentation the returned value offset from
305 // devnames->wOutputOffset is either "FILE:" or nullptr
306 // if the "Print To File" checkbox is checked it MUST be "FILE:"
307 // We assert as an extra safety check.
308 if (prntdlg
.Flags
& PD_PRINTTOFILE
) {
309 char16ptr_t fileName
= &(((wchar_t*)devnames
)[devnames
->wOutputOffset
]);
310 NS_ASSERTION(wcscmp(fileName
, L
"FILE:") == 0, "FileName must be `FILE:`");
311 aPrintSettings
->SetOutputDestination(
312 nsIPrintSettings::kOutputDestinationFile
);
313 aPrintSettings
->SetToFileName(nsDependentString(fileName
));
315 // clear "print to file" info
316 aPrintSettings
->SetOutputDestination(
317 nsIPrintSettings::kOutputDestinationPrinter
);
318 aPrintSettings
->SetToFileName(u
""_ns
);
321 nsCOMPtr
<nsIPrintSettingsWin
> psWin(do_QueryInterface(aPrintSettings
));
322 MOZ_RELEASE_ASSERT(psWin
);
324 // Setup local Data members
325 psWin
->SetDeviceName(nsDependentString(device
));
326 psWin
->SetDriverName(nsDependentString(driver
));
328 // Fill the print options with the info from the dialog
329 aPrintSettings
->SetPrinterName(nsDependentString(device
));
330 aPrintSettings
->SetPrintSelectionOnly(prntdlg
.Flags
& PD_SELECTION
);
332 AutoTArray
<int32_t, kMinSupportedRanges
* 2> pageRanges
;
333 if (prntdlg
.Flags
& PD_PAGENUMS
) {
334 pageRanges
.SetCapacity(prntdlg
.nPageRanges
* 2);
335 for (const auto& range
:
336 mozilla::Span(prntdlg
.lpPageRanges
, prntdlg
.nPageRanges
)) {
337 pageRanges
.AppendElement(range
.nFromPage
);
338 pageRanges
.AppendElement(range
.nToPage
);
341 aPrintSettings
->SetPageRanges(pageRanges
);
343 // Unlock DeviceNames
344 ::GlobalUnlock(prntdlg
.hDevNames
);
346 // Transfer the settings from the native data to the PrintSettings
347 LPDEVMODEW devMode
= (LPDEVMODEW
)::GlobalLock(prntdlg
.hDevMode
);
348 if (!devMode
|| !prntdlg
.hDC
) {
349 return NS_ERROR_FAILURE
;
351 psWin
->SetDevMode(devMode
); // copies DevMode
352 psWin
->CopyFromNative(prntdlg
.hDC
, devMode
);
353 ::GlobalUnlock(prntdlg
.hDevMode
);
354 ::DeleteDC(prntdlg
.hDC
);
356 cancelOnExit
.release();