diff options
Diffstat (limited to '')
-rw-r--r-- | widget/windows/nsPrintDialogUtil.cpp | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/widget/windows/nsPrintDialogUtil.cpp b/widget/windows/nsPrintDialogUtil.cpp new file mode 100644 index 0000000000..43f56e9706 --- /dev/null +++ b/widget/windows/nsPrintDialogUtil.cpp @@ -0,0 +1,360 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* ------------------------------------------------------------------- +To Build This: + + You need to add this to the the makefile.win in mozilla/dom/base: + + .\$(OBJDIR)\nsFlyOwnPrintDialog.obj \ + + + And this to the makefile.win in mozilla/content/build: + +WIN_LIBS= \ + winspool.lib \ + comctl32.lib \ + comdlg32.lib + +---------------------------------------------------------------------- */ + +#include <windows.h> +#include <tchar.h> + +#include <unknwn.h> +#include <commdlg.h> + +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Span.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsIPrintSettings.h" +#include "nsIPrintSettingsWin.h" +#include "nsIPrinterList.h" +#include "nsServiceManagerUtils.h" + +#include "nsRect.h" + +#include "nsCRT.h" +#include "prenv.h" /* for PR_GetEnv */ + +#include <windows.h> +#include <winspool.h> + +// For Localization + +// For NS_CopyUnicodeToNative +#include "nsNativeCharsetUtils.h" + +// This is for extending the dialog +#include <dlgs.h> + +#include "nsWindowsHelpers.h" +#include "WinUtils.h" + +//----------------------------------------------- +// Global Data +//----------------------------------------------- + +static HWND gParentWnd = nullptr; + +//---------------------------------------------------------------------------------- +// Returns a Global Moveable Memory Handle to a DevMode +// from the Printer by the name of aPrintName +// +// NOTE: +// This function assumes that aPrintName has already been converted from +// unicode +// +static nsReturnRef<nsHGLOBAL> CreateGlobalDevModeAndInit( + const nsString& aPrintName, nsIPrintSettings* aPS) { + nsHPRINTER hPrinter = nullptr; + // const cast kludge for silly Win32 api's + LPWSTR printName = + const_cast<wchar_t*>(static_cast<const wchar_t*>(aPrintName.get())); + BOOL status = ::OpenPrinterW(printName, &hPrinter, nullptr); + if (!status) { + return nsReturnRef<nsHGLOBAL>(); + } + + // Make sure hPrinter is closed on all paths + nsAutoPrinter autoPrinter(hPrinter); + + // Get the buffer size + LONG needed = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, nullptr, + nullptr, 0); + if (needed < 0) { + return nsReturnRef<nsHGLOBAL>(); + } + + // Some drivers do not return the correct size for their DEVMODE, so we + // over-allocate to try and compensate. + // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1664530#c5) + needed *= 2; + nsAutoDevMode newDevMode( + (LPDEVMODEW)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, needed)); + if (!newDevMode) { + return nsReturnRef<nsHGLOBAL>(); + } + + nsHGLOBAL hDevMode = ::GlobalAlloc(GHND, needed); + nsAutoGlobalMem globalDevMode(hDevMode); + if (!hDevMode) { + return nsReturnRef<nsHGLOBAL>(); + } + + LONG ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, newDevMode, + nullptr, DM_OUT_BUFFER); + if (ret != IDOK) { + return nsReturnRef<nsHGLOBAL>(); + } + + // Lock memory and copy contents from DEVMODE (current printer) + // to Global Memory DEVMODE + LPDEVMODEW devMode = (DEVMODEW*)::GlobalLock(hDevMode); + if (!devMode) { + return nsReturnRef<nsHGLOBAL>(); + } + + memcpy(devMode, newDevMode.get(), needed); + // Initialize values from the PrintSettings + nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aPS); + MOZ_ASSERT(psWin); + psWin->CopyToNative(devMode); + + // Sets back the changes we made to the DevMode into the Printer Driver + ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, devMode, devMode, + DM_IN_BUFFER | DM_OUT_BUFFER); + if (ret != IDOK) { + ::GlobalUnlock(hDevMode); + return nsReturnRef<nsHGLOBAL>(); + } + + ::GlobalUnlock(hDevMode); + + return globalDevMode.out(); +} + +//------------------------------------------------------------------ +// helper +static void GetDefaultPrinterNameFromGlobalPrinters(nsAString& aPrinterName) { + aPrinterName.Truncate(); + nsCOMPtr<nsIPrinterList> printerList = + do_GetService("@mozilla.org/gfx/printerlist;1"); + if (printerList) { + printerList->GetSystemDefaultPrinterName(aPrinterName); + } +} + +//------------------------------------------------------------------ +// Displays the native Print Dialog +nsresult NativeShowPrintDialog(HWND aHWnd, bool aHaveSelection, + nsIPrintSettings* aPrintSettings) { + // NS_ENSURE_ARG_POINTER(aHWnd); + NS_ENSURE_ARG_POINTER(aPrintSettings); + + // Get the Print Name to be used + nsString printerName; + aPrintSettings->GetPrinterName(printerName); + + // If there is no name then use the default printer + if (printerName.IsEmpty()) { + GetDefaultPrinterNameFromGlobalPrinters(printerName); + } else { + HANDLE hPrinter = nullptr; + if (!::OpenPrinterW(const_cast<wchar_t*>( + static_cast<const wchar_t*>(printerName.get())), + &hPrinter, nullptr)) { + // If the last used printer is not found, we should use default printer. + GetDefaultPrinterNameFromGlobalPrinters(printerName); + } else { + ::ClosePrinter(hPrinter); + } + } + + // Now create a DEVNAMES struct so the the dialog is initialized correctly. + + uint32_t len = printerName.Length(); + nsHGLOBAL hDevNames = + ::GlobalAlloc(GHND, sizeof(wchar_t) * (len + 1) + sizeof(DEVNAMES)); + nsAutoGlobalMem autoDevNames(hDevNames); + if (!hDevNames) { + return NS_ERROR_OUT_OF_MEMORY; + } + + DEVNAMES* pDevNames = (DEVNAMES*)::GlobalLock(hDevNames); + if (!pDevNames) { + return NS_ERROR_FAILURE; + } + pDevNames->wDriverOffset = sizeof(DEVNAMES) / sizeof(wchar_t); + pDevNames->wDeviceOffset = sizeof(DEVNAMES) / sizeof(wchar_t); + pDevNames->wOutputOffset = sizeof(DEVNAMES) / sizeof(wchar_t) + len; + pDevNames->wDefault = 0; + + memcpy(pDevNames + 1, printerName.get(), (len + 1) * sizeof(wchar_t)); + ::GlobalUnlock(hDevNames); + + // Create a Moveable Memory Object that holds a new DevMode + // from the Printer Name + // The PRINTDLG.hDevMode requires that it be a moveable memory object + // NOTE: autoDevMode is automatically freed when any error occurred + nsAutoGlobalMem autoDevMode( + CreateGlobalDevModeAndInit(printerName, aPrintSettings)); + + // Prepare to Display the Print Dialog + // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms646942(v=vs.85) + // https://docs.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-printdlgexw + PRINTDLGEXW prntdlg; + memset(&prntdlg, 0, sizeof(prntdlg)); + + prntdlg.lStructSize = sizeof(prntdlg); + prntdlg.hwndOwner = aHWnd; + prntdlg.hDevMode = autoDevMode.get(); + prntdlg.hDevNames = hDevNames; + prntdlg.hDC = nullptr; + prntdlg.Flags = PD_ALLPAGES | PD_RETURNIC | PD_USEDEVMODECOPIESANDCOLLATE | + PD_COLLATE | PD_NOCURRENTPAGE; + + // If there is a current selection then enable the "Selection" radio button + if (!aHaveSelection) { + prntdlg.Flags |= PD_NOSELECTION; + } + + // 10 seems like a reasonable max number of ranges to support by default if + // the user doesn't choose a greater thing in the UI. + constexpr size_t kMinSupportedRanges = 10; + + AutoTArray<PRINTPAGERANGE, kMinSupportedRanges> winPageRanges; + // Set up the page ranges. + { + AutoTArray<int32_t, kMinSupportedRanges * 2> pageRanges; + aPrintSettings->GetPageRanges(pageRanges); + // If there is a specified page range then enable the "Custom" radio button + if (!pageRanges.IsEmpty()) { + prntdlg.Flags |= PD_PAGENUMS; + } + + const size_t specifiedRanges = pageRanges.Length() / 2; + const size_t maxRanges = std::max(kMinSupportedRanges, specifiedRanges); + + prntdlg.nMaxPageRanges = maxRanges; + prntdlg.nPageRanges = specifiedRanges; + + winPageRanges.SetCapacity(maxRanges); + for (size_t i = 0; i < pageRanges.Length(); i += 2) { + PRINTPAGERANGE* range = winPageRanges.AppendElement(); + range->nFromPage = pageRanges[i]; + range->nToPage = pageRanges[i + 1]; + } + prntdlg.lpPageRanges = winPageRanges.Elements(); + + prntdlg.nMinPage = 1; + // TODO(emilio): Could probably get the right page number here from the + // new print UI. + prntdlg.nMaxPage = 0xFFFF; + } + + // NOTE(emilio): This can always be 1 because we use the DEVMODE copies + // feature (see PD_USEDEVMODECOPIESANDCOLLATE). + prntdlg.nCopies = 1; + + prntdlg.hInstance = nullptr; + prntdlg.lpPrintTemplateName = nullptr; + + prntdlg.lpCallback = nullptr; + prntdlg.nPropertyPages = 0; + prntdlg.lphPropertyPages = nullptr; + + prntdlg.nStartPage = START_PAGE_GENERAL; + prntdlg.dwResultAction = 0; + + HRESULT result; + { + mozilla::widget::WinUtils::AutoSystemDpiAware dpiAwareness; + mozilla::BackgroundHangMonitor().NotifyWait(); + result = ::PrintDlgExW(&prntdlg); + } + + auto cancelOnExit = mozilla::MakeScopeExit([&] { ::SetFocus(aHWnd); }); + + if (NS_WARN_IF(!SUCCEEDED(result))) { +#ifdef DEBUG + printf_stderr("PrintDlgExW failed with %lx\n", result); +#endif + return NS_ERROR_FAILURE; + } + if (NS_WARN_IF(prntdlg.dwResultAction != PD_RESULT_PRINT)) { + return NS_ERROR_ABORT; + } + // check to make sure we don't have any nullptr pointers + NS_ENSURE_TRUE(prntdlg.hDevMode, NS_ERROR_ABORT); + NS_ENSURE_TRUE(prntdlg.hDevNames, NS_ERROR_ABORT); + // Lock the deviceNames and check for nullptr + DEVNAMES* devnames = (DEVNAMES*)::GlobalLock(prntdlg.hDevNames); + NS_ENSURE_TRUE(devnames, NS_ERROR_ABORT); + + char16_t* device = &(((char16_t*)devnames)[devnames->wDeviceOffset]); + char16_t* driver = &(((char16_t*)devnames)[devnames->wDriverOffset]); + + // Check to see if the "Print To File" control is checked + // then take the name from devNames and set it in the PrintSettings + // + // NOTE: + // As per Microsoft SDK documentation the returned value offset from + // devnames->wOutputOffset is either "FILE:" or nullptr + // if the "Print To File" checkbox is checked it MUST be "FILE:" + // We assert as an extra safety check. + if (prntdlg.Flags & PD_PRINTTOFILE) { + char16ptr_t fileName = &(((wchar_t*)devnames)[devnames->wOutputOffset]); + NS_ASSERTION(wcscmp(fileName, L"FILE:") == 0, "FileName must be `FILE:`"); + aPrintSettings->SetOutputDestination( + nsIPrintSettings::kOutputDestinationFile); + aPrintSettings->SetToFileName(nsDependentString(fileName)); + } else { + // clear "print to file" info + aPrintSettings->SetOutputDestination( + nsIPrintSettings::kOutputDestinationPrinter); + aPrintSettings->SetToFileName(u""_ns); + } + + nsCOMPtr<nsIPrintSettingsWin> psWin(do_QueryInterface(aPrintSettings)); + MOZ_RELEASE_ASSERT(psWin); + + // Setup local Data members + psWin->SetDeviceName(nsDependentString(device)); + psWin->SetDriverName(nsDependentString(driver)); + + // Fill the print options with the info from the dialog + aPrintSettings->SetPrinterName(nsDependentString(device)); + aPrintSettings->SetPrintSelectionOnly(prntdlg.Flags & PD_SELECTION); + + AutoTArray<int32_t, kMinSupportedRanges * 2> pageRanges; + if (prntdlg.Flags & PD_PAGENUMS) { + pageRanges.SetCapacity(prntdlg.nPageRanges * 2); + for (const auto& range : + mozilla::Span(prntdlg.lpPageRanges, prntdlg.nPageRanges)) { + pageRanges.AppendElement(range.nFromPage); + pageRanges.AppendElement(range.nToPage); + } + } + aPrintSettings->SetPageRanges(pageRanges); + + // Unlock DeviceNames + ::GlobalUnlock(prntdlg.hDevNames); + + // Transfer the settings from the native data to the PrintSettings + LPDEVMODEW devMode = (LPDEVMODEW)::GlobalLock(prntdlg.hDevMode); + if (!devMode || !prntdlg.hDC) { + return NS_ERROR_FAILURE; + } + psWin->SetDevMode(devMode); // copies DevMode + psWin->CopyFromNative(prntdlg.hDC, devMode); + ::GlobalUnlock(prntdlg.hDevMode); + ::DeleteDC(prntdlg.hDC); + + cancelOnExit.release(); + return NS_OK; +} |