/**************************************************************************** ** ** Copyright (C) 2014 John Layt ** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qwindowsprintdevice.h" #include #ifndef DC_COLLATE # define DC_COLLATE 22 #endif QT_BEGIN_NAMESPACE QT_WARNING_DISABLE_GCC("-Wsign-compare") typedef QVector WindowsPrinterLookup; Q_GLOBAL_STATIC(WindowsPrinterLookup, windowsDeviceLookup); extern qreal qt_pointMultiplier(QPageLayout::Unit unit); static inline uint qwcsnlen(const wchar_t *str, uint maxlen) { uint length = 0; if (str) { while (length < maxlen && *str++) length++; } return length; } static QPrint::InputSlot paperBinToInputSlot(int windowsId, const QString &name) { QPrint::InputSlot slot; slot.name = name; int i; for (i = 0; inputSlotMap[i].id != QPrint::CustomInputSlot; ++i) { if (inputSlotMap[i].windowsId == windowsId) { slot.key = inputSlotMap[i].key; slot.id = inputSlotMap[i].id; slot.windowsId = inputSlotMap[i].windowsId; return slot; } } slot.key = inputSlotMap[i].key; slot.id = inputSlotMap[i].id; slot.windowsId = windowsId; return slot; } static LPDEVMODE getDevmode(HANDLE hPrinter, const QString &printerId) { LPWSTR printerIdUtf16 = const_cast(reinterpret_cast(printerId.utf16())); // Allocate the required DEVMODE buffer LONG dmSize = DocumentProperties(NULL, hPrinter, printerIdUtf16, NULL, NULL, 0); if (dmSize <= 0) return nullptr; LPDEVMODE pDevMode = reinterpret_cast(malloc(dmSize)); // Get the default DevMode LONG result = DocumentProperties(NULL, hPrinter, printerIdUtf16, pDevMode, NULL, DM_OUT_BUFFER); if (result != IDOK) { free(pDevMode); pDevMode = nullptr; } return pDevMode; } QWindowsPrintDevice::QWindowsPrintDevice() : QPlatformPrintDevice(), m_hPrinter(0) { } QWindowsPrintDevice::QWindowsPrintDevice(const QString &id) : QPlatformPrintDevice(id), m_hPrinter(0) { // First do a fast lookup to see if printer exists, if it does then open it if (!id.isEmpty() && QWindowsPrintDevice::availablePrintDeviceIds().contains(id)) { if (OpenPrinter(const_cast(wcharId()), &m_hPrinter, nullptr)) { DWORD needed = 0; GetPrinter(m_hPrinter, 2, 0, 0, &needed); QScopedArrayPointer buffer(new BYTE[needed]); if (GetPrinter(m_hPrinter, 2, buffer.data(), needed, &needed)) { PPRINTER_INFO_2 info = reinterpret_cast(buffer.data()); m_name = QString::fromWCharArray(info->pPrinterName); m_location = QString::fromWCharArray(info->pLocation); m_makeAndModel = QString::fromWCharArray(info->pDriverName); // TODO Check is not available elsewhere m_isRemote = info->Attributes & PRINTER_ATTRIBUTE_NETWORK; } QWindowsPrinterInfo m_info; m_info.m_id = m_id; m_info.m_name = m_name; m_info.m_location = m_location; m_info.m_makeAndModel = m_makeAndModel; m_info.m_isRemote = m_isRemote; m_infoIndex = windowsDeviceLookup()->indexOf(m_info); if (m_infoIndex != -1) { m_info = windowsDeviceLookup()->at(m_infoIndex); m_havePageSizes = m_info.m_havePageSizes; m_pageSizes = m_info.m_pageSizes; m_haveResolutions = m_info.m_haveResolutions; m_resolutions = m_info.m_resolutions; m_haveCopies = m_info.m_haveCopies; m_supportsMultipleCopies = m_info.m_supportsMultipleCopies; m_supportsCollateCopies = m_info.m_supportsCollateCopies; m_haveMinMaxPageSizes = m_info.m_haveMinMaxPageSizes; m_minimumPhysicalPageSize = m_info.m_minimumPhysicalPageSize; m_maximumPhysicalPageSize = m_info.m_maximumPhysicalPageSize; m_supportsCustomPageSizes = m_info.m_supportsCustomPageSizes; m_haveInputSlots = m_info.m_haveInputSlots; m_inputSlots = m_info.m_inputSlots; m_haveOutputBins = m_info.m_haveOutputBins; m_outputBins = m_info.m_outputBins; m_haveDuplexModes = m_info.m_haveDuplexModes; m_duplexModes = m_info.m_duplexModes; m_haveColorModes = m_info.m_haveColorModes; m_colorModes = m_info.m_colorModes; m_infoIndex = windowsDeviceLookup()->indexOf(m_info); } else { windowsDeviceLookup()->append(m_info); m_infoIndex = windowsDeviceLookup()->count() - 1; } } } } QWindowsPrintDevice::~QWindowsPrintDevice() { ClosePrinter(m_hPrinter); } bool QWindowsPrintDevice::isValid() const { return m_hPrinter; } bool QWindowsPrintDevice::isDefault() const { return m_id == defaultPrintDeviceId(); } QPrint::DeviceState QWindowsPrintDevice::state() const { DWORD needed = 0; GetPrinter(m_hPrinter, 6, 0, 0, &needed); QScopedArrayPointer buffer(new BYTE[needed]); if (GetPrinter(m_hPrinter, 6, buffer.data(), needed, &needed)) { PPRINTER_INFO_6 info = reinterpret_cast(buffer.data()); // TODO Check mapping if (info->dwStatus == 0 || (info->dwStatus & PRINTER_STATUS_WAITING) == PRINTER_STATUS_WAITING || (info->dwStatus & PRINTER_STATUS_POWER_SAVE) == PRINTER_STATUS_POWER_SAVE) { return QPrint::Idle; } else if ((info->dwStatus & PRINTER_STATUS_PRINTING) == PRINTER_STATUS_PRINTING || (info->dwStatus & PRINTER_STATUS_BUSY) == PRINTER_STATUS_BUSY || (info->dwStatus & PRINTER_STATUS_INITIALIZING) == PRINTER_STATUS_INITIALIZING || (info->dwStatus & PRINTER_STATUS_IO_ACTIVE) == PRINTER_STATUS_IO_ACTIVE || (info->dwStatus & PRINTER_STATUS_PROCESSING) == PRINTER_STATUS_PROCESSING || (info->dwStatus & PRINTER_STATUS_WARMING_UP) == PRINTER_STATUS_WARMING_UP) { return QPrint::Active; } } return QPrint::Error; } void QWindowsPrintDevice::loadPageSizes() const { // Get the number of paper sizes and check all 3 attributes have same count DWORD paperCount = DeviceCapabilities((LPWSTR)m_id.utf16(), NULL, DC_PAPERNAMES, NULL, NULL); if (int(paperCount) > 0 && DeviceCapabilities((LPWSTR)m_id.utf16(), NULL, DC_PAPERSIZE, NULL, NULL) == paperCount && DeviceCapabilities((LPWSTR)m_id.utf16(), NULL, DC_PAPERS, NULL, NULL) == paperCount) { QScopedArrayPointer paperNames(new wchar_t[paperCount*64]); QScopedArrayPointer winSizes(new POINT[paperCount]); QScopedArrayPointer papers(new wchar_t[paperCount]); // Get the details and match the default paper size if (DeviceCapabilities((LPWSTR)m_id.utf16(), NULL, DC_PAPERNAMES, paperNames.data(), NULL) == paperCount && DeviceCapabilities((LPWSTR)m_id.utf16(), NULL, DC_PAPERSIZE, (wchar_t *)winSizes.data(), NULL) == paperCount && DeviceCapabilities((LPWSTR)m_id.utf16(), NULL, DC_PAPERS, papers.data(), NULL) == paperCount) { // Returned size is in tenths of a millimeter const qreal multiplier = qt_pointMultiplier(QPageLayout::Millimeter); for (int i = 0; i < int(paperCount); ++i) { QSize size = QSize(qRound((winSizes[i].x / 10.0) * multiplier), qRound((winSizes[i].y / 10.0) * multiplier)); wchar_t *paper = paperNames.data() + (i * 64); QString name = QString::fromWCharArray(paper, qwcsnlen(paper, 64)); m_pageSizes.append(createPageSize(papers[i], size, name)); } } } m_havePageSizes = true; QWindowsPrinterInfo *info = windowsDeviceLookup()->data(); info[m_infoIndex].m_havePageSizes = true; info[m_infoIndex].m_pageSizes = m_pageSizes; } QPageSize QWindowsPrintDevice::defaultPageSize() const { if (!m_havePageSizes) loadPageSizes(); QPageSize pageSize; if (LPDEVMODE pDevMode = getDevmode(m_hPrinter, m_id)) { // Get the default paper size if (pDevMode->dmFields & DM_PAPERSIZE) { // Find the supported page size that matches, in theory default should be one of them foreach (const QPageSize &ps, m_pageSizes) { if (ps.windowsId() == pDevMode->dmPaperSize) { pageSize = ps; break; } } } // Clean-up free(pDevMode); } return pageSize; } QMarginsF QWindowsPrintDevice::printableMargins(const QPageSize &pageSize, QPageLayout::Orientation orientation, int resolution) const { // TODO This is slow, need to cache values or find better way! // Modify the DevMode to get the DC printable margins in device pixels QMarginsF margins = QMarginsF(0, 0, 0, 0); DWORD needed = 0; GetPrinter(m_hPrinter, 2, 0, 0, &needed); QScopedArrayPointer buffer(new BYTE[needed]); if (GetPrinter(m_hPrinter, 2, buffer.data(), needed, &needed)) { PPRINTER_INFO_2 info = reinterpret_cast(buffer.data()); LPDEVMODE devMode = info->pDevMode; bool separateDevMode = false; if (!devMode) { // GetPrinter() didn't include the DEVMODE. Get it a different way. devMode = getDevmode(m_hPrinter, m_id); if (!devMode) return margins; separateDevMode = true; } HDC pDC = CreateDC(NULL, (LPWSTR)m_id.utf16(), NULL, devMode); if (pageSize.id() == QPageSize::Custom || pageSize.windowsId() <= 0 || pageSize.windowsId() > DMPAPER_LAST) { devMode->dmPaperSize = 0; devMode->dmPaperWidth = pageSize.size(QPageSize::Millimeter).width() * 10.0; devMode->dmPaperLength = pageSize.size(QPageSize::Millimeter).height() * 10.0; } else { devMode->dmPaperSize = pageSize.windowsId(); } devMode->dmPrintQuality = resolution; devMode->dmOrientation = orientation == QPageLayout::Portrait ? DMORIENT_PORTRAIT : DMORIENT_LANDSCAPE; ResetDC(pDC, devMode); const int dpiWidth = GetDeviceCaps(pDC, LOGPIXELSX); const int dpiHeight = GetDeviceCaps(pDC, LOGPIXELSY); const qreal wMult = 72.0 / dpiWidth; const qreal hMult = 72.0 / dpiHeight; const qreal physicalWidth = GetDeviceCaps(pDC, PHYSICALWIDTH) * wMult; const qreal physicalHeight = GetDeviceCaps(pDC, PHYSICALHEIGHT) * hMult; const qreal printableWidth = GetDeviceCaps(pDC, HORZRES) * wMult; const qreal printableHeight = GetDeviceCaps(pDC, VERTRES) * hMult; const qreal leftMargin = GetDeviceCaps(pDC, PHYSICALOFFSETX)* wMult; const qreal topMargin = GetDeviceCaps(pDC, PHYSICALOFFSETY) * hMult; const qreal rightMargin = physicalWidth - leftMargin - printableWidth; const qreal bottomMargin = physicalHeight - topMargin - printableHeight; margins = QMarginsF(leftMargin, topMargin, rightMargin, bottomMargin); if (separateDevMode) free(devMode); DeleteDC(pDC); } return margins; } void QWindowsPrintDevice::loadResolutions() const { DWORD resCount = DeviceCapabilities((LPWSTR)m_id.utf16(), NULL, DC_ENUMRESOLUTIONS, NULL, NULL); if (int(resCount) > 0) { QScopedArrayPointer resolutions(new LONG[resCount*2]); // Get the details and match the default paper size if (DeviceCapabilities((LPWSTR)m_id.utf16(), NULL, DC_ENUMRESOLUTIONS, (LPWSTR)resolutions.data(), NULL) == resCount) { for (int i = 0; i < int(resCount * 2); i += 2) m_resolutions.append(resolutions[i+1]); } } m_haveResolutions = true; QWindowsPrinterInfo *info = windowsDeviceLookup()->data(); info[m_infoIndex].m_haveResolutions = true; info[m_infoIndex].m_resolutions = m_resolutions; } int QWindowsPrintDevice::defaultResolution() const { int resolution = 72; // TODO Set a sensible default? if (LPDEVMODE pDevMode = getDevmode(m_hPrinter, m_id)) { // Get the default resolution if (pDevMode->dmFields & DM_YRESOLUTION) { if (pDevMode->dmPrintQuality > 0) resolution = pDevMode->dmPrintQuality; else resolution = pDevMode->dmYResolution; } // Clean-up free(pDevMode); } return resolution; } void QWindowsPrintDevice::loadInputSlots() const { const auto printerId = wcharId(); DWORD binCount = DeviceCapabilities(printerId, nullptr, DC_BINS, nullptr, nullptr); if (int(binCount) > 0 && DeviceCapabilities(printerId, nullptr, DC_BINNAMES, nullptr, nullptr) == binCount) { QScopedArrayPointer bins(new WORD[binCount]); QScopedArrayPointer binNames(new wchar_t[binCount*24]); // Get the details and match the default paper size if (DeviceCapabilities(printerId, nullptr, DC_BINS, reinterpret_cast(bins.data()), nullptr) == binCount && DeviceCapabilities(printerId, nullptr, DC_BINNAMES, binNames.data(), nullptr) == binCount) { for (int i = 0; i < int(binCount); ++i) { wchar_t *binName = binNames.data() + (i * 24); QString name = QString::fromWCharArray(binName, qwcsnlen(binName, 24)); m_inputSlots.append(paperBinToInputSlot(bins[i], name)); } } } m_haveInputSlots = true; QWindowsPrinterInfo *info = windowsDeviceLookup()->data(); info[m_infoIndex].m_haveInputSlots = true; info[m_infoIndex].m_inputSlots = m_inputSlots; } QPrint::InputSlot QWindowsPrintDevice::defaultInputSlot() const { QPrint::InputSlot inputSlot = QPlatformPrintDevice::defaultInputSlot();; if (LPDEVMODE pDevMode = getDevmode(m_hPrinter, m_id)) { // Get the default input slot if (pDevMode->dmFields & DM_DEFAULTSOURCE) { QPrint::InputSlot tempSlot = paperBinToInputSlot(pDevMode->dmDefaultSource, QString()); foreach (const QPrint::InputSlot &slot, supportedInputSlots()) { if (slot.key == tempSlot.key) { inputSlot = slot; break; } } } // Clean-up free(pDevMode); } return inputSlot; } void QWindowsPrintDevice::loadOutputBins() const { m_outputBins.append(QPlatformPrintDevice::defaultOutputBin()); m_haveOutputBins = true; QWindowsPrinterInfo *info = windowsDeviceLookup()->data(); info[m_infoIndex].m_haveOutputBins = true; info[m_infoIndex].m_outputBins = m_outputBins; } void QWindowsPrintDevice::loadDuplexModes() const { m_duplexModes.append(QPrint::DuplexNone); DWORD duplex = DeviceCapabilities(wcharId(), nullptr, DC_DUPLEX, nullptr, nullptr); if (int(duplex) == 1) { // TODO Assume if duplex flag supports both modes m_duplexModes.append(QPrint::DuplexAuto); m_duplexModes.append(QPrint::DuplexLongSide); m_duplexModes.append(QPrint::DuplexShortSide); } m_haveDuplexModes = true; QWindowsPrinterInfo *info = windowsDeviceLookup()->data(); info[m_infoIndex].m_haveDuplexModes = true; info[m_infoIndex].m_duplexModes = m_duplexModes; } QPrint::DuplexMode QWindowsPrintDevice::defaultDuplexMode() const { QPrint::DuplexMode duplexMode = QPrint::DuplexNone; if (LPDEVMODE pDevMode = getDevmode(m_hPrinter, m_id)) { // Get the default duplex mode if (pDevMode->dmFields & DM_DUPLEX) { if (pDevMode->dmDuplex == DMDUP_VERTICAL) duplexMode = QPrint::DuplexLongSide; else if (pDevMode->dmDuplex == DMDUP_HORIZONTAL) duplexMode = QPrint::DuplexShortSide; } // Clean-up free(pDevMode); } return duplexMode; } void QWindowsPrintDevice::loadColorModes() const { m_colorModes.append(QPrint::GrayScale); DWORD color = DeviceCapabilities(wcharId(), nullptr, DC_COLORDEVICE, nullptr, nullptr); if (int(color) == 1) m_colorModes.append(QPrint::Color); m_haveColorModes = true; QWindowsPrinterInfo *info = windowsDeviceLookup()->data(); info[m_infoIndex].m_haveColorModes = true; info[m_infoIndex].m_colorModes = m_colorModes; } QPrint::ColorMode QWindowsPrintDevice::defaultColorMode() const { if (!m_haveColorModes) loadColorModes(); if (!m_colorModes.contains(QPrint::Color)) return QPrint::GrayScale; QPrint::ColorMode colorMode = QPrint::GrayScale; if (LPDEVMODE pDevMode = getDevmode(m_hPrinter, m_id)) { // Get the default color mode if (pDevMode->dmFields & DM_COLOR && pDevMode->dmColor == DMCOLOR_COLOR) colorMode = QPrint::Color; // Clean-up free(pDevMode); } return colorMode; } QStringList QWindowsPrintDevice::availablePrintDeviceIds() { QStringList list; DWORD needed = 0; DWORD returned = 0; if ((!EnumPrinters(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, NULL, 4, 0, 0, &needed, &returned) && GetLastError() != ERROR_INSUFFICIENT_BUFFER) || !needed) { return list; } QScopedArrayPointer buffer(new BYTE[needed]); if (!EnumPrinters(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, NULL, 4, buffer.data(), needed, &needed, &returned)) return list; PPRINTER_INFO_4 infoList = reinterpret_cast(buffer.data()); for (uint i = 0; i < returned; ++i) list.append(QString::fromWCharArray(infoList[i].pPrinterName)); return list; } QString QWindowsPrintDevice::defaultPrintDeviceId() { DWORD size = 0; if (GetDefaultPrinter(NULL, &size) == ERROR_FILE_NOT_FOUND) return QString(); QScopedArrayPointer name(new wchar_t[size]); GetDefaultPrinter(name.data(), &size); return QString::fromWCharArray(name.data()); } void QWindowsPrintDevice::loadCopiesSupport() const { auto printerId = wcharId(); m_supportsMultipleCopies = (DeviceCapabilities(printerId, NULL, DC_COPIES, NULL, NULL) > 1); m_supportsCollateCopies = DeviceCapabilities(printerId, NULL, DC_COLLATE, NULL, NULL); m_haveCopies = true; QWindowsPrinterInfo *info = windowsDeviceLookup()->data(); info[m_infoIndex].m_haveCopies = true; info[m_infoIndex].m_supportsMultipleCopies = m_supportsMultipleCopies; info[m_infoIndex].m_supportsCollateCopies = m_supportsCollateCopies; } bool QWindowsPrintDevice::supportsCollateCopies() const { if (!m_haveCopies) loadCopiesSupport(); return m_supportsCollateCopies; } bool QWindowsPrintDevice::supportsMultipleCopies() const { if (!m_haveCopies) loadCopiesSupport(); return m_supportsMultipleCopies; } bool QWindowsPrintDevice::supportsCustomPageSizes() const { if (!m_haveMinMaxPageSizes) loadMinMaxPageSizes(); return m_supportsCustomPageSizes; } QSize QWindowsPrintDevice::minimumPhysicalPageSize() const { if (!m_haveMinMaxPageSizes) loadMinMaxPageSizes(); return m_minimumPhysicalPageSize; } QSize QWindowsPrintDevice::maximumPhysicalPageSize() const { if (!m_haveMinMaxPageSizes) loadMinMaxPageSizes(); return m_maximumPhysicalPageSize; } void QWindowsPrintDevice::loadMinMaxPageSizes() const { // Min/Max custom size is in tenths of a millimeter const qreal multiplier = qt_pointMultiplier(QPageLayout::Millimeter); auto printerId = wcharId(); DWORD min = DeviceCapabilities(printerId, NULL, DC_MINEXTENT, NULL, NULL); m_minimumPhysicalPageSize = QSize((LOWORD(min) / 10.0) * multiplier, (HIWORD(min) / 10.0) * multiplier); DWORD max = DeviceCapabilities(printerId, NULL, DC_MAXEXTENT, NULL, NULL); m_maximumPhysicalPageSize = QSize((LOWORD(max) / 10.0) * multiplier, (HIWORD(max) / 10.0) * multiplier); m_supportsCustomPageSizes = (m_maximumPhysicalPageSize.width() > 0 && m_maximumPhysicalPageSize.height() > 0); m_haveMinMaxPageSizes = true; QWindowsPrinterInfo *info = windowsDeviceLookup()->data(); info[m_infoIndex].m_haveCopies = true; info[m_infoIndex].m_supportsMultipleCopies = m_supportsMultipleCopies; info[m_infoIndex].m_supportsCollateCopies = m_supportsCollateCopies; } QT_END_NAMESPACE