summaryrefslogtreecommitdiffstats
path: root/src/printsupport/platform/macos/qcocoaprintdevice.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/printsupport/platform/macos/qcocoaprintdevice.mm')
-rw-r--r--src/printsupport/platform/macos/qcocoaprintdevice.mm498
1 files changed, 498 insertions, 0 deletions
diff --git a/src/printsupport/platform/macos/qcocoaprintdevice.mm b/src/printsupport/platform/macos/qcocoaprintdevice.mm
new file mode 100644
index 0000000000..118adc0b96
--- /dev/null
+++ b/src/printsupport/platform/macos/qcocoaprintdevice.mm
@@ -0,0 +1,498 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 John Layt <jlayt@kde.org>
+** 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 <ApplicationServices/ApplicationServices.h>
+
+#include "qcocoaprintdevice_p.h"
+
+#if QT_CONFIG(mimetype)
+#include <QtCore/qmimedatabase.h>
+#endif
+#include <qdebug.h>
+
+#include <QtCore/private/qcore_mac_p.h>
+
+QT_BEGIN_NAMESPACE
+
+#ifndef QT_NO_PRINTER
+
+// The CUPS PPD APIs were deprecated in CUPS 1.6/macOS 10.8, but
+// as long as we're supporting RHEL 6, which still ships CUPS 1.4
+// we're not going to rewrite this, as we want to share the code
+// between macOS and Linux for the CUPS-bits. See discussion in
+// https://bugreports.qt.io/browse/QTBUG-56545
+#pragma message "Disabling CUPS PPD deprecation warnings. This should be fixed once we drop support for RHEL6 (QTBUG-56545)"
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+
+static QPrint::DuplexMode macToDuplexMode(const PMDuplexMode &mode)
+{
+ if (mode == kPMDuplexTumble)
+ return QPrint::DuplexShortSide;
+ else if (mode == kPMDuplexNoTumble)
+ return QPrint::DuplexLongSide;
+ else // kPMDuplexNone or kPMSimplexTumble
+ return QPrint::DuplexNone;
+}
+
+QCocoaPrintDevice::QCocoaPrintDevice()
+ : QPlatformPrintDevice(),
+ m_printer(nullptr),
+ m_session(nullptr),
+ m_ppd(nullptr)
+{
+}
+
+QCocoaPrintDevice::QCocoaPrintDevice(const QString &id)
+ : QPlatformPrintDevice(id),
+ m_printer(nullptr),
+ m_session(nullptr),
+ m_ppd(nullptr)
+{
+ if (!id.isEmpty()) {
+ m_printer = PMPrinterCreateFromPrinterID(id.toCFString());
+ if (m_printer) {
+ m_name = QString::fromCFString(PMPrinterGetName(m_printer));
+ m_location = QString::fromCFString(PMPrinterGetLocation(m_printer));
+ CFStringRef cfMakeAndModel;
+ if (PMPrinterGetMakeAndModelName(m_printer, &cfMakeAndModel) == noErr)
+ m_makeAndModel = QString::fromCFString(cfMakeAndModel);
+ Boolean isRemote;
+ if (PMPrinterIsRemote(m_printer, &isRemote) == noErr)
+ m_isRemote = isRemote;
+ if (PMCreateSession(&m_session) == noErr)
+ PMSessionSetCurrentPMPrinter(m_session, m_printer);
+
+ // No native api to query these options, need to use PPD directly, note is deprecated from 1.6 onwards
+ if (openPpdFile()) {
+ // Note this is if the hardware does multiple copies, not if Cups can
+ m_supportsMultipleCopies = !m_ppd->manual_copies;
+ // Note this is if the hardware does collation, not if Cups can
+ ppd_option_t *collate = ppdFindOption(m_ppd, "Collate");
+ if (collate)
+ m_supportsCollateCopies = true;
+ m_supportsCustomPageSizes = m_ppd->custom_max[0] > 0 && m_ppd->custom_max[1] > 0;
+ m_minimumPhysicalPageSize = QSize(m_ppd->custom_min[0], m_ppd->custom_min[1]);
+ m_maximumPhysicalPageSize = QSize(m_ppd->custom_max[0], m_ppd->custom_max[1]);
+ m_customMargins = QMarginsF(m_ppd->custom_margins[0], m_ppd->custom_margins[3],
+ m_ppd->custom_margins[2], m_ppd->custom_margins[1]);
+ }
+ }
+ }
+}
+
+QCocoaPrintDevice::~QCocoaPrintDevice()
+{
+ if (m_ppd)
+ ppdClose(m_ppd);
+ for (PMPaper paper : m_macPapers)
+ PMRelease(paper);
+ // Releasing the session appears to also release the printer
+ if (m_session)
+ PMRelease(m_session);
+ else if (m_printer)
+ PMRelease(m_printer);
+}
+
+bool QCocoaPrintDevice::isValid() const
+{
+ return m_printer ? true : false;
+}
+
+bool QCocoaPrintDevice::isDefault() const
+{
+ return PMPrinterIsDefault(m_printer);
+}
+
+QPrint::DeviceState QCocoaPrintDevice::state() const
+{
+ PMPrinterState state;
+ if (PMPrinterGetState(m_printer, &state) == noErr) {
+ if (state == kPMPrinterIdle)
+ return QPrint::Idle;
+ else if (state == kPMPrinterProcessing)
+ return QPrint::Active;
+ else if (state == kPMPrinterStopped)
+ return QPrint::Error;
+ }
+ return QPrint::Error;
+}
+
+QPageSize QCocoaPrintDevice::createPageSize(const PMPaper &paper) const
+{
+ CFStringRef key;
+ double width;
+ double height;
+ CFStringRef localizedName;
+ if (PMPaperGetPPDPaperName(paper, &key) == noErr
+ && PMPaperGetWidth(paper, &width) == noErr
+ && PMPaperGetHeight(paper, &height) == noErr
+ && PMPaperCreateLocalizedName(paper, m_printer, &localizedName) == noErr) {
+ QPageSize pageSize = QPlatformPrintDevice::createPageSize(QString::fromCFString(key),QSize(width, height),
+ QString::fromCFString(localizedName));
+ CFRelease(localizedName);
+ return pageSize;
+ }
+ return QPageSize();
+}
+
+void QCocoaPrintDevice::loadPageSizes() const
+{
+ m_pageSizes.clear();
+ for (PMPaper paper : m_macPapers)
+ PMRelease(paper);
+ m_macPapers.clear();
+ m_printableMargins.clear();
+ CFArrayRef paperSizes;
+ if (PMPrinterGetPaperList(m_printer, &paperSizes) == noErr) {
+ int count = CFArrayGetCount(paperSizes);
+ for (int i = 0; i < count; ++i) {
+ PMPaper paper = static_cast<PMPaper>(const_cast<void *>(CFArrayGetValueAtIndex(paperSizes, i)));
+ QPageSize pageSize = createPageSize(paper);
+ if (pageSize.isValid()) {
+ m_pageSizes.append(pageSize);
+ PMRetain(paper);
+ m_macPapers.insert(pageSize.key(), paper);
+ PMPaperMargins printMargins;
+ PMPaperGetMargins(paper, &printMargins);
+ m_printableMargins.insert(pageSize.key(), QMarginsF(printMargins.left, printMargins.top,
+ printMargins.right, printMargins.bottom));
+ }
+ }
+ }
+ m_havePageSizes = true;
+}
+
+QPageSize QCocoaPrintDevice::defaultPageSize() const
+{
+ QPageSize pageSize;
+ PMPageFormat pageFormat;
+ PMPaper paper;
+ if (PMCreatePageFormat(&pageFormat) == noErr) {
+ if (PMSessionDefaultPageFormat(m_session, pageFormat) == noErr
+ && PMGetPageFormatPaper(pageFormat, &paper) == noErr) {
+ pageSize = createPageSize(paper);
+ }
+ PMRelease(pageFormat);
+ }
+ return pageSize;
+}
+
+QMarginsF QCocoaPrintDevice::printableMargins(const QPageSize &pageSize,
+ QPageLayout::Orientation orientation,
+ int resolution) const
+{
+ Q_UNUSED(orientation)
+ Q_UNUSED(resolution)
+ if (!m_havePageSizes)
+ loadPageSizes();
+ if (m_printableMargins.contains(pageSize.key()))
+ return m_printableMargins.value(pageSize.key());
+ return m_customMargins;
+}
+
+void QCocoaPrintDevice::loadResolutions() const
+{
+ m_resolutions.clear();
+ UInt32 count;
+ if (PMPrinterGetPrinterResolutionCount(m_printer, &count) == noErr) {
+ // 1-based index
+ for (UInt32 i = 1; i <= count; ++i) {
+ PMResolution resolution;
+ if (PMPrinterGetIndexedPrinterResolution(m_printer, i, &resolution) == noErr)
+ m_resolutions.append(int(resolution.hRes));
+ }
+ }
+ m_haveResolutions = true;
+}
+
+int QCocoaPrintDevice::defaultResolution() const
+{
+ int defaultResolution = 72;
+ PMPrintSettings settings;
+ if (PMCreatePrintSettings(&settings) == noErr) {
+ PMResolution resolution;
+ if (PMSessionDefaultPrintSettings(m_session, settings) == noErr
+ && PMPrinterGetOutputResolution(m_printer, settings, &resolution) == noErr) {
+ // PMPrinterGetOutputResolution usually fails with -9589 kPMKeyNotFound as not set in PPD
+ defaultResolution = int(resolution.hRes);
+ }
+ PMRelease(settings);
+ }
+ // If no value returned (usually means not set in PPD) then use supported resolutions which
+ // OSX will have populated with at least one default value (but why not returned by call?)
+ if (defaultResolution <= 0) {
+ if (!m_haveResolutions)
+ loadResolutions();
+ if (m_resolutions.count() > 0)
+ return m_resolutions.at(0); // First value or highest? Only likely to be one anyway.
+ return 72; // TDOD More sensible default value???
+ }
+ return defaultResolution;
+}
+
+void QCocoaPrintDevice::loadInputSlots() const
+{
+ // NOTE: Implemented in both CUPS and Mac plugins, please keep in sync
+ // TODO Deal with concatenated names like Tray1Manual or Tray1_Man,
+ // will currently show as CustomInputSlot
+ // TODO Deal with separate ManualFeed key
+ // Try load standard PPD options first
+ m_inputSlots.clear();
+ if (m_ppd) {
+ ppd_option_t *inputSlots = ppdFindOption(m_ppd, "InputSlot");
+ if (inputSlots) {
+ for (int i = 0; i < inputSlots->num_choices; ++i)
+ m_inputSlots.append(QPrintUtils::ppdChoiceToInputSlot(inputSlots->choices[i]));
+ }
+ // If no result, try just the default
+ if (m_inputSlots.size() == 0) {
+ inputSlots = ppdFindOption(m_ppd, "DefaultInputSlot");
+ if (inputSlots)
+ m_inputSlots.append(QPrintUtils::ppdChoiceToInputSlot(inputSlots->choices[0]));
+ }
+ }
+ // If still no result, just use Auto
+ if (m_inputSlots.size() == 0)
+ m_inputSlots.append(QPlatformPrintDevice::defaultInputSlot());
+ m_haveInputSlots = true;
+}
+
+QPrint::InputSlot QCocoaPrintDevice::defaultInputSlot() const
+{
+ // No native api to query, use PPD directly
+ // NOTE: Implemented in both CUPS and Mac plugins, please keep in sync
+ // Try load standard PPD option first
+ if (m_ppd) {
+ ppd_option_t *inputSlot = ppdFindOption(m_ppd, "DefaultInputSlot");
+ if (inputSlot)
+ return QPrintUtils::ppdChoiceToInputSlot(inputSlot->choices[0]);
+ // If no result, then try a marked option
+ ppd_choice_t *defaultChoice = ppdFindMarkedChoice(m_ppd, "InputSlot");
+ if (defaultChoice)
+ return QPrintUtils::ppdChoiceToInputSlot(*defaultChoice);
+ }
+ // Otherwise return Auto
+ return QPlatformPrintDevice::defaultInputSlot();
+}
+
+void QCocoaPrintDevice::loadOutputBins() const
+{
+ // No native api to query, use PPD directly
+ // NOTE: Implemented in both CUPS and Mac plugins, please keep in sync
+ m_outputBins.clear();
+ if (m_ppd) {
+ ppd_option_t *outputBins = ppdFindOption(m_ppd, "OutputBin");
+ if (outputBins) {
+ for (int i = 0; i < outputBins->num_choices; ++i)
+ m_outputBins.append(QPrintUtils::ppdChoiceToOutputBin(outputBins->choices[i]));
+ }
+ // If no result, try just the default
+ if (m_outputBins.size() == 0) {
+ outputBins = ppdFindOption(m_ppd, "DefaultOutputBin");
+ if (outputBins)
+ m_outputBins.append(QPrintUtils::ppdChoiceToOutputBin(outputBins->choices[0]));
+ }
+ }
+ // If still no result, just use Auto
+ if (m_outputBins.size() == 0)
+ m_outputBins.append(QPlatformPrintDevice::defaultOutputBin());
+ m_haveOutputBins = true;
+}
+
+QPrint::OutputBin QCocoaPrintDevice::defaultOutputBin() const
+{
+ // No native api to query, use PPD directly
+ // NOTE: Implemented in both CUPS and Mac plugins, please keep in sync
+ // Try load standard PPD option first
+ if (m_ppd) {
+ ppd_option_t *outputBin = ppdFindOption(m_ppd, "DefaultOutputBin");
+ if (outputBin)
+ return QPrintUtils::ppdChoiceToOutputBin(outputBin->choices[0]);
+ // If no result, then try a marked option
+ ppd_choice_t *defaultChoice = ppdFindMarkedChoice(m_ppd, "OutputBin");
+ if (defaultChoice)
+ return QPrintUtils::ppdChoiceToOutputBin(*defaultChoice);
+ }
+ // Otherwise return AutoBin
+ return QPlatformPrintDevice::defaultOutputBin();
+}
+
+void QCocoaPrintDevice::loadDuplexModes() const
+{
+ // No native api to query, use PPD directly
+ // NOTE: Implemented in both CUPS and Mac plugins, please keep in sync
+ // Try load standard PPD options first
+ m_duplexModes.clear();
+ if (m_ppd) {
+ ppd_option_t *duplexModes = ppdFindOption(m_ppd, "Duplex");
+ if (duplexModes) {
+ for (int i = 0; i < duplexModes->num_choices; ++i)
+ m_duplexModes.append(QPrintUtils::ppdChoiceToDuplexMode(duplexModes->choices[i].choice));
+ }
+ // If no result, try just the default
+ if (m_duplexModes.size() == 0) {
+ duplexModes = ppdFindOption(m_ppd, "DefaultDuplex");
+ if (duplexModes)
+ m_duplexModes.append(QPrintUtils::ppdChoiceToDuplexMode(duplexModes->choices[0].choice));
+ }
+ }
+ // If still no result, or not added in PPD, then add None
+ if (m_duplexModes.size() == 0 || !m_duplexModes.contains(QPrint::DuplexNone))
+ m_duplexModes.append(QPrint::DuplexNone);
+ // If have both modes, then can support DuplexAuto
+ if (m_duplexModes.contains(QPrint::DuplexLongSide) && m_duplexModes.contains(QPrint::DuplexShortSide))
+ m_duplexModes.append(QPrint::DuplexAuto);
+ m_haveDuplexModes = true;
+}
+
+QPrint::DuplexMode QCocoaPrintDevice::defaultDuplexMode() const
+{
+ QPrint::DuplexMode defaultMode = QPrint::DuplexNone;
+ PMPrintSettings settings;
+ if (PMCreatePrintSettings(&settings) == noErr) {
+ PMDuplexMode duplexMode;
+ if (PMSessionDefaultPrintSettings(m_session, settings) == noErr
+ && PMGetDuplex(settings, &duplexMode) == noErr) {
+ defaultMode = macToDuplexMode(duplexMode);
+ }
+ PMRelease(settings);
+ }
+ return defaultMode;
+}
+
+void QCocoaPrintDevice::loadColorModes() const
+{
+ // No native api to query, use PPD directly
+ m_colorModes.clear();
+ m_colorModes.append(QPrint::GrayScale);
+ if (!m_ppd || (m_ppd && m_ppd->color_device))
+ m_colorModes.append(QPrint::Color);
+ m_haveColorModes = true;
+}
+
+QPrint::ColorMode QCocoaPrintDevice::defaultColorMode() const
+{
+ // No native api to query, use PPD directly
+ // NOTE: Implemented in both CUPS and Mac plugins, please keep in sync
+ // Not a proper option, usually only know if supports color or not, but some
+ // users known to abuse ColorModel to always force GrayScale.
+ if (m_ppd && supportedColorModes().contains(QPrint::Color)) {
+ ppd_option_t *colorModel = ppdFindOption(m_ppd, "DefaultColorModel");
+ if (!colorModel)
+ colorModel = ppdFindOption(m_ppd, "ColorModel");
+ if (!colorModel || qstrcmp(colorModel->defchoice, "Gray") != 0)
+ return QPrint::Color;
+ }
+ return QPrint::GrayScale;
+}
+
+#if QT_CONFIG(mimetype)
+void QCocoaPrintDevice::loadMimeTypes() const
+{
+ // TODO Check how settings affect returned list
+ m_mimeTypes.clear();
+ QMimeDatabase db;
+ PMPrintSettings settings;
+ if (PMCreatePrintSettings(&settings) == noErr) {
+ CFArrayRef mimeTypes;
+ if (PMPrinterGetMimeTypes(m_printer, settings, &mimeTypes) == noErr) {
+ int count = CFArrayGetCount(mimeTypes);
+ for (int i = 0; i < count; ++i) {
+ CFStringRef mimeName = static_cast<CFStringRef>(const_cast<void *>(CFArrayGetValueAtIndex(mimeTypes, i)));
+ QMimeType mimeType = db.mimeTypeForName(QString::fromCFString(mimeName));
+ if (mimeType.isValid())
+ m_mimeTypes.append(mimeType);
+ }
+ }
+ PMRelease(settings);
+ }
+ m_haveMimeTypes = true;
+}
+#endif // mimetype
+
+bool QCocoaPrintDevice::openPpdFile()
+{
+ if (m_ppd)
+ ppdClose(m_ppd);
+ m_ppd = nullptr;
+ CFURLRef ppdURL = nullptr;
+ char ppdPath[MAXPATHLEN];
+ if (PMPrinterCopyDescriptionURL(m_printer, kPMPPDDescriptionType, &ppdURL) == noErr
+ && ppdURL) {
+ if (CFURLGetFileSystemRepresentation(ppdURL, true, (UInt8*)ppdPath, sizeof(ppdPath)))
+ m_ppd = ppdOpenFile(ppdPath);
+ CFRelease(ppdURL);
+ }
+ return m_ppd ? true : false;
+}
+
+PMPrinter QCocoaPrintDevice::macPrinter() const
+{
+ return m_printer;
+}
+
+// Returns a cached printer PMPaper, or creates and caches a new custom PMPaper
+// Caller should never release a cached PMPaper!
+PMPaper QCocoaPrintDevice::macPaper(const QPageSize &pageSize) const
+{
+ if (!m_havePageSizes)
+ loadPageSizes();
+ // If keys match, then is a supported size or an existing custom size
+ if (m_macPapers.contains(pageSize.key()))
+ return m_macPapers.value(pageSize.key());
+ // For any other page size, whether custom or just unsupported, needs to be a custom PMPaper
+ PMPaper paper = nullptr;
+ PMPaperMargins paperMargins;
+ paperMargins.left = m_customMargins.left();
+ paperMargins.right = m_customMargins.right();
+ paperMargins.top = m_customMargins.top();
+ paperMargins.bottom = m_customMargins.bottom();
+ PMPaperCreateCustom(m_printer, QCFString(pageSize.key()), QCFString(pageSize.name()),
+ pageSize.sizePoints().width(), pageSize.sizePoints().height(),
+ &paperMargins, &paper);
+ m_macPapers.insert(pageSize.key(), paper);
+ return paper;
+}
+
+#pragma clang diagnostic pop
+
+#endif // QT_NO_PRINTER
+
+QT_END_NAMESPACE