| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "printing/printing_context_system_dialog_win.h" |
| |
| #include <utility> |
| |
| #include "base/auto_reset.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/current_thread.h" |
| #include "printing/backend/win_helper.h" |
| #include "printing/buildflags/buildflags.h" |
| #include "printing/mojom/print.mojom.h" |
| #include "printing/print_settings_initializer_win.h" |
| #include "skia/ext/skia_utils_win.h" |
| |
| #if BUILDFLAG(ENABLE_OOP_PRINTING) |
| #include "printing/printing_features.h" |
| #endif |
| |
| namespace printing { |
| |
| HWND PrintingContextSystemDialogWin::GetWindow() { |
| #if BUILDFLAG(ENABLE_OOP_PRINTING) |
| if (features::kEnableOopPrintDriversJobPrint.Get()) { |
| // Delving through the view tree to get to root window happens separately |
| // in the browser process (i.e., not in `PrintingContextSystemDialogWin`) |
| // before sending the identified window owner to the Print Backend service. |
| // This means that this call is happening in the service, and thus should |
| // just use the parent view as-is instead of looking for the root window. |
| // TODO(crbug.com/809738) Pursue having a service-level instantiation of |
| // `PrintingContextSystemDialogWin` for this behavior. That would ensure |
| // this logic would be compile-time driven and only invoked by the service. |
| return reinterpret_cast<HWND>(delegate_->GetParentView()); |
| } |
| #endif |
| return GetRootWindow(delegate_->GetParentView()); |
| } |
| |
| PrintingContextSystemDialogWin::PrintingContextSystemDialogWin( |
| Delegate* delegate) |
| : PrintingContextWin(delegate) {} |
| |
| PrintingContextSystemDialogWin::~PrintingContextSystemDialogWin() {} |
| |
| void PrintingContextSystemDialogWin::AskUserForSettings( |
| int max_pages, |
| bool has_selection, |
| bool is_scripted, |
| PrintSettingsCallback callback) { |
| DCHECK(!in_print_job_); |
| |
| HWND window = GetWindow(); |
| DCHECK(window); |
| |
| // Show the OS-dependent dialog box. |
| // If the user press |
| // - OK, the settings are reset and reinitialized with the new settings. OK is |
| // returned. |
| // - Apply then Cancel, the settings are reset and reinitialized with the new |
| // settings. CANCEL is returned. |
| // - Cancel, the settings are not changed, the previous setting, if it was |
| // initialized before, are kept. CANCEL is returned. |
| // On failure, the settings are reset and FAILED is returned. |
| PRINTDLGEX dialog_options = {sizeof(PRINTDLGEX)}; |
| dialog_options.hwndOwner = window; |
| // Disable options we don't support currently. |
| // TODO(maruel): Reuse the previously loaded settings! |
| dialog_options.Flags = PD_RETURNDC | PD_USEDEVMODECOPIESANDCOLLATE | |
| PD_NOCURRENTPAGE | PD_HIDEPRINTTOFILE; |
| if (!has_selection) |
| dialog_options.Flags |= PD_NOSELECTION; |
| |
| PRINTPAGERANGE ranges[32]; |
| dialog_options.nStartPage = START_PAGE_GENERAL; |
| if (max_pages) { |
| // Default initialize to print all the pages. |
| memset(ranges, 0, sizeof(ranges)); |
| ranges[0].nFromPage = 1; |
| ranges[0].nToPage = max_pages; |
| dialog_options.nPageRanges = 1; |
| dialog_options.nMaxPageRanges = std::size(ranges); |
| dialog_options.nMinPage = 1; |
| dialog_options.nMaxPage = max_pages; |
| dialog_options.lpPageRanges = ranges; |
| } else { |
| // No need to bother, we don't know how many pages are available. |
| dialog_options.Flags |= PD_NOPAGENUMS; |
| } |
| |
| if (ShowPrintDialog(&dialog_options) != S_OK) { |
| ResetSettings(); |
| std::move(callback).Run(mojom::ResultCode::kFailed); |
| return; |
| } |
| |
| // TODO(maruel): Support PD_PRINTTOFILE. |
| std::move(callback).Run(ParseDialogResultEx(dialog_options)); |
| } |
| |
| HRESULT PrintingContextSystemDialogWin::ShowPrintDialog(PRINTDLGEX* options) { |
| // Runs always on the UI thread. |
| static bool is_dialog_shown = false; |
| if (is_dialog_shown) |
| return E_FAIL; |
| // Block opening dialog from nested task. It crashes PrintDlgEx. |
| base::AutoReset<bool> auto_reset(&is_dialog_shown, true); |
| |
| // Note that this cannot use ui::BaseShellDialog as the print dialog is |
| // system modal: opening it from a background thread can cause Windows to |
| // get the wrong Z-order which will make the print dialog appear behind the |
| // browser frame (but still being modal) so neither the browser frame nor |
| // the print dialog will get any input. See http://crbug.com/342697 |
| // http://crbug.com/180997 for details. |
| base::CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop allow; |
| |
| return PrintDlgEx(options); |
| } |
| |
| bool PrintingContextSystemDialogWin::InitializeSettingsWithRanges( |
| const DEVMODE& dev_mode, |
| const std::wstring& new_device_name, |
| const PRINTPAGERANGE* ranges, |
| int number_ranges, |
| bool selection_only) { |
| DCHECK(GetDeviceCaps(context(), CLIPCAPS)); |
| DCHECK(GetDeviceCaps(context(), RASTERCAPS) & RC_STRETCHDIB); |
| DCHECK(GetDeviceCaps(context(), RASTERCAPS) & RC_BITMAP64); |
| // Some printers don't advertise these. |
| // DCHECK(GetDeviceCaps(context(), RASTERCAPS) & RC_SCALING); |
| // DCHECK(GetDeviceCaps(context(), SHADEBLENDCAPS) & SB_CONST_ALPHA); |
| // DCHECK(GetDeviceCaps(context(), SHADEBLENDCAPS) & SB_PIXEL_ALPHA); |
| |
| // StretchDIBits() support is needed for printing. |
| if (!(GetDeviceCaps(context(), RASTERCAPS) & RC_STRETCHDIB) || |
| !(GetDeviceCaps(context(), RASTERCAPS) & RC_BITMAP64)) { |
| NOTREACHED(); |
| ResetSettings(); |
| return false; |
| } |
| |
| DCHECK(!in_print_job_); |
| DCHECK(context()); |
| PageRanges ranges_vector; |
| if (!selection_only) { |
| // Convert the PRINTPAGERANGE array to a PrintSettings::PageRanges vector. |
| ranges_vector.reserve(number_ranges); |
| for (int i = 0; i < number_ranges; ++i) { |
| PageRange range; |
| // Transfer from 1-based to 0-based. |
| range.from = ranges[i].nFromPage - 1; |
| range.to = ranges[i].nToPage - 1; |
| ranges_vector.push_back(range); |
| } |
| } |
| |
| settings_->set_ranges(ranges_vector); |
| settings_->set_device_name(base::WideToUTF16(new_device_name)); |
| settings_->set_selection_only(selection_only); |
| PrintSettingsInitializerWin::InitPrintSettings(context(), dev_mode, |
| settings_.get()); |
| |
| return true; |
| } |
| |
| mojom::ResultCode PrintingContextSystemDialogWin::ParseDialogResultEx( |
| const PRINTDLGEX& dialog_options) { |
| // If the user clicked OK or Apply then Cancel, but not only Cancel. |
| if (dialog_options.dwResultAction != PD_RESULT_CANCEL) { |
| // Start fresh, but preserve is_modifiable print setting. |
| bool is_modifiable = settings_->is_modifiable(); |
| ResetSettings(); |
| settings_->set_is_modifiable(is_modifiable); |
| |
| DEVMODE* dev_mode = NULL; |
| if (dialog_options.hDevMode) { |
| dev_mode = |
| reinterpret_cast<DEVMODE*>(GlobalLock(dialog_options.hDevMode)); |
| DCHECK(dev_mode); |
| } |
| |
| std::wstring device_name; |
| if (dialog_options.hDevNames) { |
| DEVNAMES* dev_names = |
| reinterpret_cast<DEVNAMES*>(GlobalLock(dialog_options.hDevNames)); |
| DCHECK(dev_names); |
| if (dev_names) { |
| device_name = reinterpret_cast<const wchar_t*>(dev_names) + |
| dev_names->wDeviceOffset; |
| GlobalUnlock(dialog_options.hDevNames); |
| } |
| } |
| |
| bool success = false; |
| if (dev_mode && !device_name.empty()) { |
| set_context(dialog_options.hDC); |
| PRINTPAGERANGE* page_ranges = NULL; |
| DWORD num_page_ranges = 0; |
| bool print_selection_only = false; |
| if (dialog_options.Flags & PD_PAGENUMS) { |
| page_ranges = dialog_options.lpPageRanges; |
| num_page_ranges = dialog_options.nPageRanges; |
| } |
| if (dialog_options.Flags & PD_SELECTION) { |
| print_selection_only = true; |
| } |
| success = |
| InitializeSettingsWithRanges(*dev_mode, device_name, page_ranges, |
| num_page_ranges, print_selection_only); |
| } |
| |
| if (!success && dialog_options.hDC) { |
| DeleteDC(dialog_options.hDC); |
| set_context(NULL); |
| } |
| |
| if (dev_mode) { |
| GlobalUnlock(dialog_options.hDevMode); |
| } |
| } else { |
| if (dialog_options.hDC) { |
| DeleteDC(dialog_options.hDC); |
| } |
| } |
| |
| if (dialog_options.hDevMode != NULL) |
| GlobalFree(dialog_options.hDevMode); |
| if (dialog_options.hDevNames != NULL) |
| GlobalFree(dialog_options.hDevNames); |
| |
| switch (dialog_options.dwResultAction) { |
| case PD_RESULT_PRINT: |
| return context() ? mojom::ResultCode::kSuccess |
| : mojom::ResultCode::kFailed; |
| case PD_RESULT_APPLY: |
| return context() ? mojom::ResultCode::kCanceled |
| : mojom::ResultCode::kFailed; |
| case PD_RESULT_CANCEL: |
| return mojom::ResultCode::kCanceled; |
| default: |
| return mojom::ResultCode::kFailed; |
| } |
| } |
| |
| } // namespace printing |