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

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