1// Copyright (C) 2016 The Qt Company Ltd.
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 "qcupsprintengine_p.h"
5
6#include <qpa/qplatformprintplugin.h>
7#include <qpa/qplatformprintersupport.h>
8
9#include <qiodevice.h>
10#include <qfile.h>
11#include <qdebug.h>
12#include <qbuffer.h>
13#include "private/qcups_p.h" // Only needed for PPK_CupsOptions
14#include <QtGui/qpagelayout.h>
15
16#include <cups/cups.h>
17
18#include "private/qcore_unix_p.h" // overrides QT_OPEN
19
20QT_BEGIN_NAMESPACE
21
22extern QMarginsF qt_convertMargins(const QMarginsF &margins, QPageLayout::Unit fromUnits, QPageLayout::Unit toUnits);
23
24QCupsPrintEngine::QCupsPrintEngine(QPrinter::PrinterMode m, const QString &deviceId)
25 : QPdfPrintEngine(*new QCupsPrintEnginePrivate(m))
26{
27 Q_D(QCupsPrintEngine);
28 d->changePrinter(newPrinter: deviceId);
29 state = QPrinter::Idle;
30}
31
32QCupsPrintEngine::~QCupsPrintEngine()
33{
34}
35
36void QCupsPrintEngine::setProperty(PrintEnginePropertyKey key, const QVariant &value)
37{
38 Q_D(QCupsPrintEngine);
39
40 switch (int(key)) {
41 case PPK_PageSize:
42 d->setPageSize(QPageSize(QPageSize::PageSizeId(value.toInt())));
43 break;
44 case PPK_WindowsPageSize:
45 d->setPageSize(QPageSize(QPageSize::id(windowsId: value.toInt())));
46 break;
47 case PPK_CustomPaperSize:
48 d->setPageSize(QPageSize(value.toSizeF(), QPageSize::Point));
49 break;
50 case PPK_PaperName:
51 // Get the named page size from the printer if supported
52 d->setPageSize(d->m_printDevice.supportedPageSize(pageName: value.toString()));
53 break;
54 case PPK_Duplex: {
55 QPrint::DuplexMode mode = QPrint::DuplexMode(value.toInt());
56 if (d->m_printDevice.supportedDuplexModes().contains(t: mode)) {
57 d->duplex = mode;
58 d->duplexRequestedExplicitly = true;
59 }
60 break;
61 }
62 case PPK_PrinterName:
63 d->changePrinter(newPrinter: value.toString());
64 break;
65 case PPK_CupsOptions:
66 d->cupsOptions = value.toStringList();
67 if (d->cupsOptions.size() % 2 == 1) {
68 qWarning(msg: "%s: malformed value for key = PPK_CupsOptions "
69 "(odd number of elements in the string-list; "
70 "appending an empty entry)", Q_FUNC_INFO);
71 d->cupsOptions.emplace_back();
72 }
73 break;
74 case PPK_QPageSize:
75 d->setPageSize(qvariant_cast<QPageSize>(v: value));
76 break;
77 case PPK_QPageLayout: {
78 QPageLayout pageLayout = qvariant_cast<QPageLayout>(v: value);
79 if (pageLayout.isValid() && (d->m_printDevice.isValidPageLayout(layout: pageLayout, resolution: d->resolution)
80 || d->m_printDevice.supportsCustomPageSizes()
81 || d->m_printDevice.supportedPageSizes().isEmpty())) {
82 // supportedPageSizes().isEmpty() because QPageSetupWidget::initPageSizes says
83 // "If no available printer page sizes, populate with all page sizes"
84 d->m_pageLayout = pageLayout;
85 d->setPageSize(pageLayout.pageSize());
86 }
87 break;
88 }
89 default:
90 QPdfPrintEngine::setProperty(key, value);
91 break;
92 }
93}
94
95QVariant QCupsPrintEngine::property(PrintEnginePropertyKey key) const
96{
97 Q_D(const QCupsPrintEngine);
98
99 QVariant ret;
100 switch (int(key)) {
101 case PPK_SupportsMultipleCopies:
102 // CUPS server always supports multiple copies, even if individual m_printDevice doesn't
103 ret = true;
104 break;
105 case PPK_NumberOfCopies:
106 ret = 1;
107 break;
108 case PPK_CupsOptions:
109 ret = d->cupsOptions;
110 break;
111 case PPK_Duplex:
112 ret = d->duplex;
113 break;
114 default:
115 ret = QPdfPrintEngine::property(key);
116 break;
117 }
118 return ret;
119}
120
121
122QCupsPrintEnginePrivate::QCupsPrintEnginePrivate(QPrinter::PrinterMode m)
123 : QPdfPrintEnginePrivate(m)
124 , duplex(QPrint::DuplexNone)
125{
126}
127
128QCupsPrintEnginePrivate::~QCupsPrintEnginePrivate()
129{
130}
131
132bool QCupsPrintEnginePrivate::openPrintDevice()
133{
134 if (outDevice)
135 return false;
136
137 if (!outputFileName.isEmpty()) {
138 QFile *file = new QFile(outputFileName);
139 if (! file->open(flags: QFile::WriteOnly|QFile::Truncate)) {
140 delete file;
141 return false;
142 }
143 outDevice = file;
144 } else {
145 char filename[512];
146 fd = cupsTempFd(filename, len: 512);
147 if (fd < 0) {
148 qWarning(msg: "QPdfPrinter: Could not open temporary file to print");
149 return false;
150 }
151 cupsTempFile = QString::fromLocal8Bit(ba: filename);
152 outDevice = new QFile();
153 if (!static_cast<QFile *>(outDevice)->open(fd, ioFlags: QIODevice::WriteOnly)) {
154 qWarning(msg: "QPdfPrinter: Could not open CUPS temporary file descriptor: %s",
155 qPrintable(outDevice->errorString()));
156 delete outDevice;
157 outDevice = nullptr;
158
159#if defined(Q_OS_WIN) && defined(Q_CC_MSVC)
160 ::_close(fd);
161#else
162 ::close(fd: fd);
163#endif
164 fd = -1;
165 return false;
166 }
167 }
168
169 return true;
170}
171
172void QCupsPrintEnginePrivate::closePrintDevice()
173{
174 QPdfPrintEnginePrivate::closePrintDevice();
175
176 if (!cupsTempFile.isEmpty()) {
177 QString tempFile = cupsTempFile;
178 cupsTempFile.clear();
179
180 // Should never have got here without a printer, but check anyway
181 if (printerName.isEmpty()) {
182 qWarning(msg: "Could not determine printer to print to");
183 QFile::remove(fileName: tempFile);
184 return;
185 }
186
187 // Set up print options.
188 QList<std::pair<QByteArray, QByteArray> > options;
189 QList<cups_option_t> cupsOptStruct;
190
191 options.emplace_back(args: "media", args: m_pageLayout.pageSize().key().toLocal8Bit());
192
193 if (copies > 1)
194 options.emplace_back(args: "copies", args: QString::number(copies).toLocal8Bit());
195
196 if (copies > 1 && collate)
197 options.emplace_back(args: "Collate", args: "True");
198
199 switch (duplex) {
200 case QPrint::DuplexNone:
201 options.emplace_back(args: "sides", args: "one-sided");
202 break;
203 case QPrint::DuplexAuto:
204 if (m_pageLayout.orientation() == QPageLayout::Portrait)
205 options.emplace_back(args: "sides", args: "two-sided-long-edge");
206 else
207 options.emplace_back(args: "sides", args: "two-sided-short-edge");
208 break;
209 case QPrint::DuplexLongSide:
210 options.emplace_back(args: "sides", args: "two-sided-long-edge");
211 break;
212 case QPrint::DuplexShortSide:
213 options.emplace_back(args: "sides", args: "two-sided-short-edge");
214 break;
215 }
216
217 if (m_pageLayout.orientation() == QPageLayout::Landscape)
218 options.emplace_back(args: "landscape", args: "");
219
220 QStringList::const_iterator it = cupsOptions.constBegin();
221 Q_ASSERT(cupsOptions.size() % 2 == 0);
222 while (it != cupsOptions.constEnd()) {
223 options.emplace_back(args: (*it).toLocal8Bit(), args: (*(it+1)).toLocal8Bit());
224 it += 2;
225 }
226
227 const int numOptions = options.size();
228 cupsOptStruct.reserve(asize: numOptions);
229 for (int c = 0; c < numOptions; ++c) {
230 cups_option_t opt;
231 opt.name = options[c].first.data();
232 opt.value = options[c].second.data();
233 cupsOptStruct.append(t: opt);
234 }
235
236 // Print the file
237 // Cups expect the printer original name without instance, the full name is used only to retrieve the configuration
238 const auto parts = QStringView{printerName}.split(sep: u'/');
239 const auto printerOriginalName = parts.at(i: 0);
240 cups_option_t* optPtr = cupsOptStruct.size() ? &cupsOptStruct.first() : 0;
241 cupsPrintFile(name: printerOriginalName.toLocal8Bit().constData(), filename: tempFile.toLocal8Bit().constData(),
242 title: title.toLocal8Bit().constData(), num_options: cupsOptStruct.size(), options: optPtr);
243
244 QFile::remove(fileName: tempFile);
245 }
246}
247
248void QCupsPrintEnginePrivate::changePrinter(const QString &newPrinter)
249{
250 // Don't waste time if same printer name
251 if (newPrinter == printerName)
252 return;
253
254 // Should never have reached here if no plugin available, but check just in case
255 QPlatformPrinterSupport *ps = QPlatformPrinterSupportPlugin::get();
256 if (!ps)
257 return;
258
259 // Try create the printer, only use it if it returns valid
260 QPrintDevice printDevice = ps->createPrintDevice(id: newPrinter);
261 if (!printDevice.isValid())
262 return;
263 m_printDevice.swap(other&: printDevice);
264 printerName = m_printDevice.id();
265
266 // in case a duplex value was explicitly set, check if new printer supports current value,
267 // otherwise use device default
268 if (!duplexRequestedExplicitly || !m_printDevice.supportedDuplexModes().contains(t: duplex)) {
269 duplex = m_printDevice.defaultDuplexMode();
270 duplexRequestedExplicitly = false;
271 }
272 QPrint::ColorMode colorMode = static_cast<QPrint::ColorMode>(printerColorMode());
273 if (!m_printDevice.supportedColorModes().contains(t: colorMode)) {
274 colorModel = (m_printDevice.defaultColorMode() == QPrint::GrayScale)
275 ? QPdfEngine::ColorModel::Grayscale
276 : QPdfEngine::ColorModel::RGB;
277 }
278
279 // Get the equivalent page size for this printer as supported names may be different
280 if (m_printDevice.supportedPageSize(pageSize: m_pageLayout.pageSize()).isValid())
281 setPageSize(m_pageLayout.pageSize());
282 else
283 setPageSize(QPageSize(m_pageLayout.pageSize().size(units: QPageSize::Point), QPageSize::Point));
284}
285
286void QCupsPrintEnginePrivate::setPageSize(const QPageSize &pageSize)
287{
288 if (pageSize.isValid()) {
289 // Find if the requested page size has a matching printer page size, if so use its defined name instead
290 QPageSize printerPageSize = m_printDevice.supportedPageSize(pageSize);
291 QPageSize usePageSize = printerPageSize.isValid() ? printerPageSize : pageSize;
292 QMarginsF printable = m_printDevice.printableMargins(pageSize: usePageSize, orientation: m_pageLayout.orientation(), resolution);
293 m_pageLayout.setPageSize(pageSize: usePageSize, minMargins: qt_convertMargins(margins: printable, fromUnits: QPageLayout::Point, toUnits: m_pageLayout.units()));
294 }
295}
296
297QT_END_NAMESPACE
298

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