1/****************************************************************************
2**
3** Copyright (C) 2014 John Layt <jlayt@kde.org>
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the plugins of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qppdprintdevice.h"
41
42#include "qcupsprintersupport_p.h"
43#include "private/qcups_p.h" // Only needed for PDPK_*
44
45#if QT_CONFIG(mimetype)
46#include <QtCore/QMimeDatabase>
47#endif
48
49#ifndef QT_LINUXBASE // LSB merges everything into cups.h
50#include <cups/language.h>
51#endif
52
53QT_BEGIN_NAMESPACE
54
55QPpdPrintDevice::QPpdPrintDevice(const QString &id)
56 : QPlatformPrintDevice(id),
57 m_cupsDest(0),
58 m_ppd(0)
59{
60 if (!id.isEmpty()) {
61
62 // TODO For now each dest is an individual device
63 const auto parts = id.splitRef(sep: QLatin1Char('/'));
64 m_cupsName = parts.at(i: 0).toUtf8();
65 if (parts.size() > 1)
66 m_cupsInstance = parts.at(i: 1).toUtf8();
67
68 // Get the print instance and PPD file
69 m_cupsDest = cupsGetNamedDest(CUPS_HTTP_DEFAULT, name: m_cupsName, instance: m_cupsInstance.isNull() ? nullptr : m_cupsInstance.constData());
70 if (m_cupsDest) {
71 const char *ppdFile = cupsGetPPD(name: m_cupsName);
72 if (ppdFile) {
73 m_ppd = ppdOpenFile(filename: ppdFile);
74 unlink(name: ppdFile);
75 }
76 if (m_ppd) {
77 ppdMarkDefaults(ppd: m_ppd);
78 cupsMarkOptions(ppd: m_ppd, num_options: m_cupsDest->num_options, options: m_cupsDest->options);
79 ppdLocalize(ppd: m_ppd);
80
81 m_minimumPhysicalPageSize = QSize(m_ppd->custom_min[0], m_ppd->custom_min[1]);
82 m_maximumPhysicalPageSize = QSize(m_ppd->custom_max[0], m_ppd->custom_max[1]);
83 m_customMargins = QMarginsF(m_ppd->custom_margins[0], m_ppd->custom_margins[3],
84 m_ppd->custom_margins[2], m_ppd->custom_margins[1]);
85 }
86
87 m_name = printerOption(key: "printer-info");
88 m_location = printerOption(key: "printer-location");
89 m_makeAndModel = printerOption(key: "printer-make-and-model");
90 cups_ptype_e type = printerTypeFlags();
91 m_isRemote = type & CUPS_PRINTER_REMOTE;
92 // Note this is if the hardware does multiple copies, not if Cups can
93 m_supportsMultipleCopies = type & CUPS_PRINTER_COPIES;
94 // Note this is if the hardware does collation, not if Cups can
95 m_supportsCollateCopies = type & CUPS_PRINTER_COLLATE;
96
97 // Custom Page Size support
98 // Cups cups_ptype_e CUPS_PRINTER_VARIABLE
99 // Cups ppd_file_t variable_sizes custom_min custom_max
100 // PPD MaxMediaWidth MaxMediaHeight
101 m_supportsCustomPageSizes = type & CUPS_PRINTER_VARIABLE;
102 }
103 }
104}
105
106QPpdPrintDevice::~QPpdPrintDevice()
107{
108 if (m_ppd)
109 ppdClose(ppd: m_ppd);
110 if (m_cupsDest)
111 cupsFreeDests(num_dests: 1, dests: m_cupsDest);
112 m_cupsDest = 0;
113 m_ppd = 0;
114}
115
116bool QPpdPrintDevice::isValid() const
117{
118 return m_cupsDest;
119}
120
121bool QPpdPrintDevice::isDefault() const
122{
123 // There seems to be a bug in cups in which printerTypeFlags
124 // returns CUPS_PRINTER_DEFAULT based only on system values, ignoring user lpoptions
125 // so we can't use that. And also there seems to be a bug in which dests returned
126 // by cupsGetNamedDest don't have is_default set at all so we can't use that either
127 // so go the long route and compare our id against the defaultPrintDeviceId
128 return id() == QCupsPrinterSupport::staticDefaultPrintDeviceId();
129}
130
131QPrint::DeviceState QPpdPrintDevice::state() const
132{
133 // 3 = idle, 4 = printing, 5 = stopped
134 // More details available from printer-state-message and printer-state-reasons
135 int state = printerOption(QStringLiteral("printer-state")).toInt();
136 if (state == 3)
137 return QPrint::Idle;
138 else if (state == 4)
139 return QPrint::Active;
140 else
141 return QPrint::Error;
142}
143
144void QPpdPrintDevice::loadPageSizes() const
145{
146 m_pageSizes.clear();
147 m_printableMargins.clear();
148
149 ppd_option_t *pageSizes = ppdFindOption(ppd: m_ppd, keyword: "PageSize");
150 if (pageSizes) {
151 for (int i = 0; i < pageSizes->num_choices; ++i) {
152 const ppd_size_t *ppdSize = ppdPageSize(ppd: m_ppd, name: pageSizes->choices[i].choice);
153 if (ppdSize) {
154 // Returned size is in points
155 QString key = QString::fromUtf8(str: ppdSize->name);
156 QSize size = QSize(qRound(d: ppdSize->width), qRound(d: ppdSize->length));
157 QString name = QString::fromUtf8(str: pageSizes->choices[i].text);
158 if (!size.isEmpty()) {
159 QPageSize ps = createPageSize(key, size, localizedName: name);
160 if (ps.isValid()) {
161 m_pageSizes.append(t: ps);
162 m_printableMargins.insert(akey: key, avalue: QMarginsF(ppdSize->left, ppdSize->length - ppdSize->top,
163 ppdSize->width - ppdSize->right, ppdSize->bottom));
164 }
165 }
166 }
167 }
168 }
169 m_havePageSizes = true;
170}
171
172QPageSize QPpdPrintDevice::defaultPageSize() const
173{
174 ppd_choice_t *defaultChoice = ppdFindMarkedChoice(ppd: m_ppd, keyword: "PageSize");
175 if (defaultChoice) {
176 ppd_size_t *ppdSize = ppdPageSize(ppd: m_ppd, name: defaultChoice->choice);
177 if (ppdSize) {
178 // Returned size is in points
179 QString key = QString::fromUtf8(str: ppdSize->name);
180 QSize size = QSize(qRound(d: ppdSize->width), qRound(d: ppdSize->length));
181 QString name = QString::fromUtf8(str: defaultChoice->text);
182 return createPageSize(key, size, localizedName: name);
183 }
184 }
185 return QPageSize();
186}
187
188QMarginsF QPpdPrintDevice::printableMargins(const QPageSize &pageSize,
189 QPageLayout::Orientation orientation,
190 int resolution) const
191{
192 Q_UNUSED(orientation)
193 Q_UNUSED(resolution)
194 if (!m_havePageSizes)
195 loadPageSizes();
196 // TODO Orientation?
197 if (m_printableMargins.contains(akey: pageSize.key()))
198 return m_printableMargins.value(akey: pageSize.key());
199 return m_customMargins;
200}
201
202void QPpdPrintDevice::loadResolutions() const
203{
204 m_resolutions.clear();
205
206 // Try load standard PPD options first
207 ppd_option_t *resolutions = ppdFindOption(ppd: m_ppd, keyword: "Resolution");
208 if (resolutions) {
209 for (int i = 0; i < resolutions->num_choices; ++i) {
210 int res = QPrintUtils::parsePpdResolution(value: resolutions->choices[i].choice);
211 if (res > 0)
212 m_resolutions.append(t: res);
213 }
214 }
215 // If no result, try just the default
216 if (m_resolutions.size() == 0) {
217 resolutions = ppdFindOption(ppd: m_ppd, keyword: "DefaultResolution");
218 if (resolutions) {
219 int res = QPrintUtils::parsePpdResolution(value: resolutions->choices[0].choice);
220 if (res > 0)
221 m_resolutions.append(t: res);
222 }
223 }
224 // If still no result, then try HP's custom options
225 if (m_resolutions.size() == 0) {
226 resolutions = ppdFindOption(ppd: m_ppd, keyword: "HPPrintQuality");
227 if (resolutions) {
228 for (int i = 0; i < resolutions->num_choices; ++i) {
229 int res = QPrintUtils::parsePpdResolution(value: resolutions->choices[i].choice);
230 if (res > 0)
231 m_resolutions.append(t: res);
232 }
233 }
234 }
235 if (m_resolutions.size() == 0) {
236 resolutions = ppdFindOption(ppd: m_ppd, keyword: "DefaultHPPrintQuality");
237 if (resolutions) {
238 int res = QPrintUtils::parsePpdResolution(value: resolutions->choices[0].choice);
239 if (res > 0)
240 m_resolutions.append(t: res);
241 }
242 }
243 m_haveResolutions = true;
244}
245
246int QPpdPrintDevice::defaultResolution() const
247{
248 // Try load standard PPD option first
249 ppd_option_t *resolution = ppdFindOption(ppd: m_ppd, keyword: "DefaultResolution");
250 if (resolution) {
251 int res = QPrintUtils::parsePpdResolution(value: resolution->choices[0].choice);
252 if (res > 0)
253 return res;
254 }
255 // If no result, then try a marked option
256 ppd_choice_t *defaultChoice = ppdFindMarkedChoice(ppd: m_ppd, keyword: "Resolution");
257 if (defaultChoice) {
258 int res = QPrintUtils::parsePpdResolution(value: defaultChoice->choice);
259 if (res > 0)
260 return res;
261 }
262 // If still no result, then try HP's custom options
263 resolution = ppdFindOption(ppd: m_ppd, keyword: "DefaultHPPrintQuality");
264 if (resolution) {
265 int res = QPrintUtils::parsePpdResolution(value: resolution->choices[0].choice);
266 if (res > 0)
267 return res;
268 }
269 defaultChoice = ppdFindMarkedChoice(ppd: m_ppd, keyword: "HPPrintQuality");
270 if (defaultChoice) {
271 int res = QPrintUtils::parsePpdResolution(value: defaultChoice->choice);
272 if (res > 0)
273 return res;
274 }
275 // Otherwise return a sensible default.
276 // TODO What is sensible? 150? 300?
277 return 72;
278}
279
280void QPpdPrintDevice::loadInputSlots() const
281{
282 // NOTE: Implemented in both CUPS and Mac plugins, please keep in sync
283 // TODO Deal with concatenated names like Tray1Manual or Tray1_Man,
284 // will currently show as CustomInputSlot
285 // TODO Deal with separate ManualFeed key
286 // Try load standard PPD options first
287 m_inputSlots.clear();
288 if (m_ppd) {
289 ppd_option_t *inputSlots = ppdFindOption(ppd: m_ppd, keyword: "InputSlot");
290 if (inputSlots) {
291 m_inputSlots.reserve(asize: inputSlots->num_choices);
292 for (int i = 0; i < inputSlots->num_choices; ++i)
293 m_inputSlots.append(t: QPrintUtils::ppdChoiceToInputSlot(choice: inputSlots->choices[i]));
294 }
295 // If no result, try just the default
296 if (m_inputSlots.size() == 0) {
297 inputSlots = ppdFindOption(ppd: m_ppd, keyword: "DefaultInputSlot");
298 if (inputSlots)
299 m_inputSlots.append(t: QPrintUtils::ppdChoiceToInputSlot(choice: inputSlots->choices[0]));
300 }
301 }
302 // If still no result, just use Auto
303 if (m_inputSlots.size() == 0)
304 m_inputSlots.append(t: QPlatformPrintDevice::defaultInputSlot());
305 m_haveInputSlots = true;
306}
307
308QPrint::InputSlot QPpdPrintDevice::defaultInputSlot() const
309{
310 // NOTE: Implemented in both CUPS and Mac plugins, please keep in sync
311 // Try load standard PPD option first
312 if (m_ppd) {
313 ppd_option_t *inputSlot = ppdFindOption(ppd: m_ppd, keyword: "DefaultInputSlot");
314 if (inputSlot)
315 return QPrintUtils::ppdChoiceToInputSlot(choice: inputSlot->choices[0]);
316 // If no result, then try a marked option
317 ppd_choice_t *defaultChoice = ppdFindMarkedChoice(ppd: m_ppd, keyword: "InputSlot");
318 if (defaultChoice)
319 return QPrintUtils::ppdChoiceToInputSlot(choice: *defaultChoice);
320 }
321 // Otherwise return Auto
322 return QPlatformPrintDevice::defaultInputSlot();
323}
324
325void QPpdPrintDevice::loadOutputBins() const
326{
327 // NOTE: Implemented in both CUPS and Mac plugins, please keep in sync
328 m_outputBins.clear();
329 if (m_ppd) {
330 ppd_option_t *outputBins = ppdFindOption(ppd: m_ppd, keyword: "OutputBin");
331 if (outputBins) {
332 m_outputBins.reserve(asize: outputBins->num_choices);
333 for (int i = 0; i < outputBins->num_choices; ++i)
334 m_outputBins.append(t: QPrintUtils::ppdChoiceToOutputBin(choice: outputBins->choices[i]));
335 }
336 // If no result, try just the default
337 if (m_outputBins.size() == 0) {
338 outputBins = ppdFindOption(ppd: m_ppd, keyword: "DefaultOutputBin");
339 if (outputBins)
340 m_outputBins.append(t: QPrintUtils::ppdChoiceToOutputBin(choice: outputBins->choices[0]));
341 }
342 }
343 // If still no result, just use Auto
344 if (m_outputBins.size() == 0)
345 m_outputBins.append(t: QPlatformPrintDevice::defaultOutputBin());
346 m_haveOutputBins = true;
347}
348
349QPrint::OutputBin QPpdPrintDevice::defaultOutputBin() const
350{
351 // NOTE: Implemented in both CUPS and Mac plugins, please keep in sync
352 // Try load standard PPD option first
353 if (m_ppd) {
354 ppd_option_t *outputBin = ppdFindOption(ppd: m_ppd, keyword: "DefaultOutputBin");
355 if (outputBin)
356 return QPrintUtils::ppdChoiceToOutputBin(choice: outputBin->choices[0]);
357 // If no result, then try a marked option
358 ppd_choice_t *defaultChoice = ppdFindMarkedChoice(ppd: m_ppd, keyword: "OutputBin");
359 if (defaultChoice)
360 return QPrintUtils::ppdChoiceToOutputBin(choice: *defaultChoice);
361 }
362 // Otherwise return AutoBin
363 return QPlatformPrintDevice::defaultOutputBin();
364}
365
366void QPpdPrintDevice::loadDuplexModes() const
367{
368 // NOTE: Implemented in both CUPS and Mac plugins, please keep in sync
369 // Try load standard PPD options first
370 m_duplexModes.clear();
371 if (m_ppd) {
372 ppd_option_t *duplexModes = ppdFindOption(ppd: m_ppd, keyword: "Duplex");
373 if (duplexModes) {
374 m_duplexModes.reserve(asize: duplexModes->num_choices);
375 for (int i = 0; i < duplexModes->num_choices; ++i) {
376 if (ppdInstallableConflict(ppd: m_ppd, option: duplexModes->keyword, choice: duplexModes->choices[i].choice) == 0) {
377 m_duplexModes.append(t: QPrintUtils::ppdChoiceToDuplexMode(choice: duplexModes->choices[i].choice));
378 }
379 }
380 }
381 // If no result, try just the default
382 if (m_duplexModes.size() == 0) {
383 duplexModes = ppdFindOption(ppd: m_ppd, keyword: "DefaultDuplex");
384 if (duplexModes && (ppdInstallableConflict(ppd: m_ppd, option: duplexModes->keyword, choice: duplexModes->choices[0].choice) == 0)) {
385 m_duplexModes.append(t: QPrintUtils::ppdChoiceToDuplexMode(choice: duplexModes->choices[0].choice));
386 }
387 }
388 }
389 // If still no result, or not added in PPD, then add None
390 if (m_duplexModes.size() == 0 || !m_duplexModes.contains(t: QPrint::DuplexNone))
391 m_duplexModes.append(t: QPrint::DuplexNone);
392 // If have both modes, then can support DuplexAuto
393 if (m_duplexModes.contains(t: QPrint::DuplexLongSide) && m_duplexModes.contains(t: QPrint::DuplexShortSide))
394 m_duplexModes.append(t: QPrint::DuplexAuto);
395 m_haveDuplexModes = true;
396}
397
398QPrint::DuplexMode QPpdPrintDevice::defaultDuplexMode() const
399{
400 // Try load standard PPD option first
401 if (m_ppd) {
402 ppd_option_t *inputSlot = ppdFindOption(ppd: m_ppd, keyword: "DefaultDuplex");
403 if (inputSlot)
404 return QPrintUtils::ppdChoiceToDuplexMode(choice: inputSlot->choices[0].choice);
405 // If no result, then try a marked option
406 ppd_choice_t *defaultChoice = ppdFindMarkedChoice(ppd: m_ppd, keyword: "Duplex");
407 if (defaultChoice)
408 return QPrintUtils::ppdChoiceToDuplexMode(choice: defaultChoice->choice);
409 }
410 // Otherwise return None
411 return QPrint::DuplexNone;
412}
413
414void QPpdPrintDevice::loadColorModes() const
415{
416 // Cups cups_ptype_e CUPS_PRINTER_BW CUPS_PRINTER_COLOR
417 // Cups ppd_file_t color_device
418 // PPD ColorDevice
419 m_colorModes.clear();
420 cups_ptype_e type = printerTypeFlags();
421 if (type & CUPS_PRINTER_BW)
422 m_colorModes.append(t: QPrint::GrayScale);
423 if (type & CUPS_PRINTER_COLOR)
424 m_colorModes.append(t: QPrint::Color);
425 m_haveColorModes = true;
426}
427
428QPrint::ColorMode QPpdPrintDevice::defaultColorMode() const
429{
430 // NOTE: Implemented in both CUPS and Mac plugins, please keep in sync
431 // Not a proper option, usually only know if supports color or not, but some
432 // users known to abuse ColorModel to always force GrayScale.
433 if (m_ppd && supportedColorModes().contains(t: QPrint::Color)) {
434 ppd_option_t *colorModel = ppdFindOption(ppd: m_ppd, keyword: "DefaultColorModel");
435 if (!colorModel)
436 colorModel = ppdFindOption(ppd: m_ppd, keyword: "ColorModel");
437 if (!colorModel || qstrcmp(str1: colorModel->defchoice, str2: "Gray") != 0)
438 return QPrint::Color;
439 }
440 return QPrint::GrayScale;
441}
442
443QVariant QPpdPrintDevice::property(QPrintDevice::PrintDevicePropertyKey key) const
444{
445 if (key == PDPK_PpdFile)
446 return QVariant::fromValue<ppd_file_t *>(value: m_ppd);
447 else if (key == PDPK_CupsJobPriority)
448 return printerOption(QStringLiteral("job-priority"));
449 else if (key == PDPK_CupsJobSheets)
450 return printerOption(QStringLiteral("job-sheets"));
451 else if (key == PDPK_CupsJobBilling)
452 return printerOption(QStringLiteral("job-billing"));
453 else if (key == PDPK_CupsJobHoldUntil)
454 return printerOption(QStringLiteral("job-hold-until"));
455
456 return QPlatformPrintDevice::property(key);
457}
458
459bool QPpdPrintDevice::setProperty(QPrintDevice::PrintDevicePropertyKey key, const QVariant &value)
460{
461 if (key == PDPK_PpdOption) {
462 const QStringList values = value.toStringList();
463 if (values.count() == 2) {
464 ppdMarkOption(ppd: m_ppd, keyword: values[0].toLatin1(), option: values[1].toLatin1());
465 return true;
466 }
467 }
468
469 return QPlatformPrintDevice::setProperty(key, value);
470}
471
472bool QPpdPrintDevice::isFeatureAvailable(QPrintDevice::PrintDevicePropertyKey key, const QVariant &params) const
473{
474 if (key == PDPK_PpdChoiceIsInstallableConflict) {
475 const QStringList values = params.toStringList();
476 if (values.count() == 2)
477 return ppdInstallableConflict(ppd: m_ppd, option: values[0].toLatin1(), choice: values[1].toLatin1());
478 }
479
480 return QPlatformPrintDevice::isFeatureAvailable(key, params);
481}
482
483#if QT_CONFIG(mimetype)
484void QPpdPrintDevice::loadMimeTypes() const
485{
486 // TODO No CUPS api? Need to manually load CUPS mime.types file?
487 // For now hard-code most common support types
488 QMimeDatabase db;
489 m_mimeTypes.append(t: db.mimeTypeForName(QStringLiteral("application/pdf")));
490 m_mimeTypes.append(t: db.mimeTypeForName(QStringLiteral("application/postscript")));
491 m_mimeTypes.append(t: db.mimeTypeForName(QStringLiteral("image/gif")));
492 m_mimeTypes.append(t: db.mimeTypeForName(QStringLiteral("image/png")));
493 m_mimeTypes.append(t: db.mimeTypeForName(QStringLiteral("image/jpeg")));
494 m_mimeTypes.append(t: db.mimeTypeForName(QStringLiteral("image/tiff")));
495 m_mimeTypes.append(t: db.mimeTypeForName(QStringLiteral("text/html")));
496 m_mimeTypes.append(t: db.mimeTypeForName(QStringLiteral("text/plain")));
497 m_haveMimeTypes = true;
498}
499#endif
500
501QString QPpdPrintDevice::printerOption(const QString &key) const
502{
503 return cupsGetOption(name: key.toUtf8(), num_options: m_cupsDest->num_options, options: m_cupsDest->options);
504}
505
506cups_ptype_e QPpdPrintDevice::printerTypeFlags() const
507{
508 return static_cast<cups_ptype_e>(printerOption(key: "printer-type").toUInt());
509}
510
511QT_END_NAMESPACE
512

source code of qtbase/src/plugins/printsupport/cups/qppdprintdevice.cpp