blob: 2f3a80d4d4e16e184573ea99296d752951aad252 [file] [log] [blame]
/****************************************************************************
**
** 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.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
// the replacement APIs are unfortunately not sufficient. See:
// https://bugreports.qt.io/browse/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