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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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