Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qcocoaprintdevice.mm
Go to the documentation of this file.
1// Copyright (C) 2014 John Layt <jlayt@kde.org>
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include <ApplicationServices/ApplicationServices.h>
6
8
9#if QT_CONFIG(mimetype)
10#include <QtCore/qmimedatabase.h>
11#endif
12#include <qdebug.h>
13
14#include <QtCore/private/qcore_mac_p.h>
15
17
18#ifndef QT_NO_PRINTER
19
20// The CUPS PPD APIs were deprecated in CUPS 1.6/macOS 10.8, but
21// the replacement APIs are unfortunately not sufficient. See:
22// https://bugreports.qt.io/browse/QTBUG-56545
23#pragma clang diagnostic push
24#pragma clang diagnostic ignored "-Wdeprecated-declarations"
25
26static QPrint::DuplexMode macToDuplexMode(const PMDuplexMode &mode)
27{
28 if (mode == kPMDuplexTumble)
29 return QPrint::DuplexShortSide;
30 else if (mode == kPMDuplexNoTumble)
31 return QPrint::DuplexLongSide;
32 else // kPMDuplexNone or kPMSimplexTumble
33 return QPrint::DuplexNone;
34}
35
36QCocoaPrintDevice::QCocoaPrintDevice()
37 : QPlatformPrintDevice(),
38 m_printer(nullptr),
39 m_session(nullptr),
40 m_ppd(nullptr)
41{
42}
43
44QCocoaPrintDevice::QCocoaPrintDevice(const QString &id)
45 : QPlatformPrintDevice(id),
46 m_printer(nullptr),
47 m_session(nullptr),
48 m_ppd(nullptr)
49{
50 if (!id.isEmpty()) {
51 m_printer = PMPrinterCreateFromPrinterID(id.toCFString());
52 if (m_printer) {
53 m_name = QString::fromCFString(PMPrinterGetName(m_printer));
54 m_location = QString::fromCFString(PMPrinterGetLocation(m_printer));
55 CFStringRef cfMakeAndModel;
56 if (PMPrinterGetMakeAndModelName(m_printer, &cfMakeAndModel) == noErr)
57 m_makeAndModel = QString::fromCFString(cfMakeAndModel);
58 Boolean isRemote;
59 if (PMPrinterIsRemote(m_printer, &isRemote) == noErr)
60 m_isRemote = isRemote;
61 if (PMCreateSession(&m_session) == noErr)
62 PMSessionSetCurrentPMPrinter(m_session, m_printer);
63
64 // No native api to query these options, need to use PPD directly, note is deprecated from 1.6 onwards
65 if (openPpdFile()) {
66 // Note this is if the hardware does multiple copies, not if Cups can
67 m_supportsMultipleCopies = !m_ppd->manual_copies;
68 // Note this is if the hardware does collation, not if Cups can
69 ppd_option_t *collate = ppdFindOption(m_ppd, "Collate");
70 if (collate)
71 m_supportsCollateCopies = true;
72 m_supportsCustomPageSizes = m_ppd->custom_max[0] > 0 && m_ppd->custom_max[1] > 0;
73 m_minimumPhysicalPageSize = QSize(m_ppd->custom_min[0], m_ppd->custom_min[1]);
74 m_maximumPhysicalPageSize = QSize(m_ppd->custom_max[0], m_ppd->custom_max[1]);
75 m_customMargins = QMarginsF(m_ppd->custom_margins[0], m_ppd->custom_margins[3],
76 m_ppd->custom_margins[2], m_ppd->custom_margins[1]);
77 }
78 }
79 }
80}
81
82QCocoaPrintDevice::~QCocoaPrintDevice()
83{
84 if (m_ppd)
85 ppdClose(m_ppd);
86 for (PMPaper paper : m_macPapers)
87 PMRelease(paper);
88 // Releasing the session appears to also release the printer
89 if (m_session)
90 PMRelease(m_session);
91 else if (m_printer)
92 PMRelease(m_printer);
93}
94
95bool QCocoaPrintDevice::isValid() const
96{
97 return m_printer ? true : false;
98}
99
100bool QCocoaPrintDevice::isDefault() const
101{
102 return PMPrinterIsDefault(m_printer);
103}
104
105QPrint::DeviceState QCocoaPrintDevice::state() const
106{
107 PMPrinterState state;
108 if (PMPrinterGetState(m_printer, &state) == noErr) {
109 if (state == kPMPrinterIdle)
110 return QPrint::Idle;
111 else if (state == kPMPrinterProcessing)
112 return QPrint::Active;
113 else if (state == kPMPrinterStopped)
114 return QPrint::Error;
115 }
116 return QPrint::Error;
117}
118
119QPageSize QCocoaPrintDevice::createPageSize(const PMPaper &paper) const
120{
121 CFStringRef key;
122 double width;
123 double height;
124 CFStringRef localizedName;
125 if (PMPaperGetPPDPaperName(paper, &key) == noErr
126 && PMPaperGetWidth(paper, &width) == noErr
127 && PMPaperGetHeight(paper, &height) == noErr
128 && PMPaperCreateLocalizedName(paper, m_printer, &localizedName) == noErr) {
129 QPageSize pageSize = QPlatformPrintDevice::createPageSize(QString::fromCFString(key),QSize(width, height),
130 QString::fromCFString(localizedName));
131 CFRelease(localizedName);
132 return pageSize;
133 }
134 return QPageSize();
135}
136
137void QCocoaPrintDevice::loadPageSizes() const
138{
139 m_pageSizes.clear();
140 for (PMPaper paper : m_macPapers)
141 PMRelease(paper);
142 m_macPapers.clear();
143 m_printableMargins.clear();
144 CFArrayRef paperSizes;
145 if (PMPrinterGetPaperList(m_printer, &paperSizes) == noErr) {
146 int count = CFArrayGetCount(paperSizes);
147 for (int i = 0; i < count; ++i) {
148 PMPaper paper = static_cast<PMPaper>(const_cast<void *>(CFArrayGetValueAtIndex(paperSizes, i)));
149 QPageSize pageSize = createPageSize(paper);
150 if (pageSize.isValid()) {
151 m_pageSizes.append(pageSize);
152 PMRetain(paper);
153 m_macPapers.insert(pageSize.key(), paper);
154 PMPaperMargins printMargins;
155 PMPaperGetMargins(paper, &printMargins);
156 m_printableMargins.insert(pageSize.key(), QMarginsF(printMargins.left, printMargins.top,
157 printMargins.right, printMargins.bottom));
158 }
159 }
160 }
161 m_havePageSizes = true;
162}
163
164QPageSize QCocoaPrintDevice::defaultPageSize() const
165{
166 QPageSize pageSize;
167 PMPageFormat pageFormat;
168 PMPaper paper;
169 if (PMCreatePageFormat(&pageFormat) == noErr) {
170 if (PMSessionDefaultPageFormat(m_session, pageFormat) == noErr
171 && PMGetPageFormatPaper(pageFormat, &paper) == noErr) {
172 pageSize = createPageSize(paper);
173 }
174 PMRelease(pageFormat);
175 }
176 return pageSize;
177}
178
179QMarginsF QCocoaPrintDevice::printableMargins(const QPageSize &pageSize,
180 QPageLayout::Orientation orientation,
181 int resolution) const
182{
183 Q_UNUSED(orientation);
184 Q_UNUSED(resolution);
185 if (!m_havePageSizes)
186 loadPageSizes();
187 if (m_printableMargins.contains(pageSize.key()))
188 return m_printableMargins.value(pageSize.key());
189 return m_customMargins;
190}
191
192void QCocoaPrintDevice::loadResolutions() const
193{
194 m_resolutions.clear();
195 UInt32 count;
196 if (PMPrinterGetPrinterResolutionCount(m_printer, &count) == noErr) {
197 // 1-based index
198 for (UInt32 i = 1; i <= count; ++i) {
199 PMResolution resolution;
200 if (PMPrinterGetIndexedPrinterResolution(m_printer, i, &resolution) == noErr)
201 m_resolutions.append(int(resolution.hRes));
202 }
203 }
204 m_haveResolutions = true;
205}
206
207int QCocoaPrintDevice::defaultResolution() const
208{
209 int defaultResolution = 72;
210 PMPrintSettings settings;
211 if (PMCreatePrintSettings(&settings) == noErr) {
212 PMResolution resolution;
213 if (PMSessionDefaultPrintSettings(m_session, settings) == noErr
214 && PMPrinterGetOutputResolution(m_printer, settings, &resolution) == noErr) {
215 // PMPrinterGetOutputResolution usually fails with -9589 kPMKeyNotFound as not set in PPD
216 defaultResolution = int(resolution.hRes);
217 }
218 PMRelease(settings);
219 }
220 // If no value returned (usually means not set in PPD) then use supported resolutions which
221 // OSX will have populated with at least one default value (but why not returned by call?)
222 if (defaultResolution <= 0) {
223 if (!m_haveResolutions)
224 loadResolutions();
225 if (m_resolutions.count() > 0)
226 return m_resolutions.at(0); // First value or highest? Only likely to be one anyway.
227 return 72; // TDOD More sensible default value???
228 }
229 return defaultResolution;
230}
231
232void QCocoaPrintDevice::loadInputSlots() const
233{
234 // NOTE: Implemented in both CUPS and Mac plugins, please keep in sync
235 // TODO Deal with concatenated names like Tray1Manual or Tray1_Man,
236 // will currently show as CustomInputSlot
237 // TODO Deal with separate ManualFeed key
238 // Try load standard PPD options first
239 m_inputSlots.clear();
240 if (m_ppd) {
241 ppd_option_t *inputSlots = ppdFindOption(m_ppd, "InputSlot");
242 if (inputSlots) {
243 for (int i = 0; i < inputSlots->num_choices; ++i)
244 m_inputSlots.append(QPrintUtils::ppdChoiceToInputSlot(inputSlots->choices[i]));
245 }
246 // If no result, try just the default
247 if (m_inputSlots.size() == 0) {
248 inputSlots = ppdFindOption(m_ppd, "DefaultInputSlot");
249 if (inputSlots)
250 m_inputSlots.append(QPrintUtils::ppdChoiceToInputSlot(inputSlots->choices[0]));
251 }
252 }
253 // If still no result, just use Auto
254 if (m_inputSlots.size() == 0)
255 m_inputSlots.append(QPlatformPrintDevice::defaultInputSlot());
256 m_haveInputSlots = true;
257}
258
259QPrint::InputSlot QCocoaPrintDevice::defaultInputSlot() const
260{
261 // No native api to query, use PPD directly
262 // NOTE: Implemented in both CUPS and Mac plugins, please keep in sync
263 // Try load standard PPD option first
264 if (m_ppd) {
265 ppd_option_t *inputSlot = ppdFindOption(m_ppd, "DefaultInputSlot");
266 if (inputSlot)
267 return QPrintUtils::ppdChoiceToInputSlot(inputSlot->choices[0]);
268 // If no result, then try a marked option
269 ppd_choice_t *defaultChoice = ppdFindMarkedChoice(m_ppd, "InputSlot");
270 if (defaultChoice)
271 return QPrintUtils::ppdChoiceToInputSlot(*defaultChoice);
272 }
273 // Otherwise return Auto
274 return QPlatformPrintDevice::defaultInputSlot();
275}
276
277void QCocoaPrintDevice::loadOutputBins() const
278{
279 // No native api to query, use PPD directly
280 // NOTE: Implemented in both CUPS and Mac plugins, please keep in sync
281 m_outputBins.clear();
282 if (m_ppd) {
283 ppd_option_t *outputBins = ppdFindOption(m_ppd, "OutputBin");
284 if (outputBins) {
285 for (int i = 0; i < outputBins->num_choices; ++i)
286 m_outputBins.append(QPrintUtils::ppdChoiceToOutputBin(outputBins->choices[i]));
287 }
288 // If no result, try just the default
289 if (m_outputBins.size() == 0) {
290 outputBins = ppdFindOption(m_ppd, "DefaultOutputBin");
291 if (outputBins)
292 m_outputBins.append(QPrintUtils::ppdChoiceToOutputBin(outputBins->choices[0]));
293 }
294 }
295 // If still no result, just use Auto
296 if (m_outputBins.size() == 0)
297 m_outputBins.append(QPlatformPrintDevice::defaultOutputBin());
298 m_haveOutputBins = true;
299}
300
301QPrint::OutputBin QCocoaPrintDevice::defaultOutputBin() const
302{
303 // No native api to query, use PPD directly
304 // NOTE: Implemented in both CUPS and Mac plugins, please keep in sync
305 // Try load standard PPD option first
306 if (m_ppd) {
307 ppd_option_t *outputBin = ppdFindOption(m_ppd, "DefaultOutputBin");
308 if (outputBin)
309 return QPrintUtils::ppdChoiceToOutputBin(outputBin->choices[0]);
310 // If no result, then try a marked option
311 ppd_choice_t *defaultChoice = ppdFindMarkedChoice(m_ppd, "OutputBin");
312 if (defaultChoice)
313 return QPrintUtils::ppdChoiceToOutputBin(*defaultChoice);
314 }
315 // Otherwise return AutoBin
316 return QPlatformPrintDevice::defaultOutputBin();
317}
318
319void QCocoaPrintDevice::loadDuplexModes() const
320{
321 // No native api to query, use PPD directly
322 // NOTE: Implemented in both CUPS and Mac plugins, please keep in sync
323 // Try load standard PPD options first
324 m_duplexModes.clear();
325 if (m_ppd) {
326 ppd_option_t *duplexModes = ppdFindOption(m_ppd, "Duplex");
327 if (duplexModes) {
328 for (int i = 0; i < duplexModes->num_choices; ++i)
329 m_duplexModes.append(QPrintUtils::ppdChoiceToDuplexMode(duplexModes->choices[i].choice));
330 }
331 // If no result, try just the default
332 if (m_duplexModes.size() == 0) {
333 duplexModes = ppdFindOption(m_ppd, "DefaultDuplex");
334 if (duplexModes)
335 m_duplexModes.append(QPrintUtils::ppdChoiceToDuplexMode(duplexModes->choices[0].choice));
336 }
337 }
338 // If still no result, or not added in PPD, then add None
339 if (m_duplexModes.size() == 0 || !m_duplexModes.contains(QPrint::DuplexNone))
340 m_duplexModes.append(QPrint::DuplexNone);
341 // If have both modes, then can support DuplexAuto
342 if (m_duplexModes.contains(QPrint::DuplexLongSide) && m_duplexModes.contains(QPrint::DuplexShortSide))
343 m_duplexModes.append(QPrint::DuplexAuto);
344 m_haveDuplexModes = true;
345}
346
347QPrint::DuplexMode QCocoaPrintDevice::defaultDuplexMode() const
348{
349 QPrint::DuplexMode defaultMode = QPrint::DuplexNone;
350 PMPrintSettings settings;
351 if (PMCreatePrintSettings(&settings) == noErr) {
352 PMDuplexMode duplexMode;
353 if (PMSessionDefaultPrintSettings(m_session, settings) == noErr
354 && PMGetDuplex(settings, &duplexMode) == noErr) {
355 defaultMode = macToDuplexMode(duplexMode);
356 }
357 PMRelease(settings);
358 }
359 return defaultMode;
360}
361
362void QCocoaPrintDevice::loadColorModes() const
363{
364 // No native api to query, use PPD directly
365 m_colorModes.clear();
366 m_colorModes.append(QPrint::GrayScale);
367 if (!m_ppd || (m_ppd && m_ppd->color_device))
368 m_colorModes.append(QPrint::Color);
369 m_haveColorModes = true;
370}
371
372QPrint::ColorMode QCocoaPrintDevice::defaultColorMode() const
373{
374 // No native api to query, use PPD directly
375 // NOTE: Implemented in both CUPS and Mac plugins, please keep in sync
376 // Not a proper option, usually only know if supports color or not, but some
377 // users known to abuse ColorModel to always force GrayScale.
378 if (m_ppd && supportedColorModes().contains(QPrint::Color)) {
379 ppd_option_t *colorModel = ppdFindOption(m_ppd, "DefaultColorModel");
380 if (!colorModel)
381 colorModel = ppdFindOption(m_ppd, "ColorModel");
382 if (!colorModel || qstrcmp(colorModel->defchoice, "Gray") != 0)
383 return QPrint::Color;
384 }
385 return QPrint::GrayScale;
386}
387
388#if QT_CONFIG(mimetype)
389void QCocoaPrintDevice::loadMimeTypes() const
390{
391 // TODO Check how settings affect returned list
392 m_mimeTypes.clear();
393 QMimeDatabase db;
394 PMPrintSettings settings;
395 if (PMCreatePrintSettings(&settings) == noErr) {
396 CFArrayRef mimeTypes;
397 if (PMPrinterGetMimeTypes(m_printer, settings, &mimeTypes) == noErr) {
398 int count = CFArrayGetCount(mimeTypes);
399 for (int i = 0; i < count; ++i) {
400 CFStringRef mimeName = static_cast<CFStringRef>(const_cast<void *>(CFArrayGetValueAtIndex(mimeTypes, i)));
401 QMimeType mimeType = db.mimeTypeForName(QString::fromCFString(mimeName));
402 if (mimeType.isValid())
403 m_mimeTypes.append(mimeType);
404 }
405 }
406 PMRelease(settings);
407 }
408 m_haveMimeTypes = true;
409}
410#endif // mimetype
411
412bool QCocoaPrintDevice::openPpdFile()
413{
414 if (m_ppd)
415 ppdClose(m_ppd);
416 m_ppd = nullptr;
417 CFURLRef ppdURL = nullptr;
418 char ppdPath[MAXPATHLEN];
419 if (PMPrinterCopyDescriptionURL(m_printer, kPMPPDDescriptionType, &ppdURL) == noErr
420 && ppdURL) {
421 if (CFURLGetFileSystemRepresentation(ppdURL, true, (UInt8*)ppdPath, sizeof(ppdPath)))
422 m_ppd = ppdOpenFile(ppdPath);
423 CFRelease(ppdURL);
424 }
425 return m_ppd ? true : false;
426}
427
428PMPrinter QCocoaPrintDevice::macPrinter() const
429{
430 return m_printer;
431}
432
433// Returns a cached printer PMPaper, or creates and caches a new custom PMPaper
434// Caller should never release a cached PMPaper!
435PMPaper QCocoaPrintDevice::macPaper(const QPageSize &pageSize) const
436{
437 if (!m_havePageSizes)
438 loadPageSizes();
439 // If keys match, then is a supported size or an existing custom size
440 if (m_macPapers.contains(pageSize.key()))
441 return m_macPapers.value(pageSize.key());
442 // For any other page size, whether custom or just unsupported, needs to be a custom PMPaper
443 PMPaper paper = nullptr;
444 PMPaperMargins paperMargins;
445 paperMargins.left = m_customMargins.left();
446 paperMargins.right = m_customMargins.right();
447 paperMargins.top = m_customMargins.top();
448 paperMargins.bottom = m_customMargins.bottom();
449 PMPaperCreateCustom(m_printer, QCFString(pageSize.key()), QCFString(pageSize.name()),
450 pageSize.sizePoints().width(), pageSize.sizePoints().height(),
451 &paperMargins, &paper);
452 m_macPapers.insert(pageSize.key(), paper);
453 return paper;
454}
455
456#pragma clang diagnostic pop
457
458#endif // QT_NO_PRINTER
459
460QT_END_NAMESPACE
static QT_BEGIN_NAMESPACE QPrint::DuplexMode macToDuplexMode(const PMDuplexMode &mode)