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 static_cast<QFile *>(outDevice)->open(fd, ioFlags: QIODevice::WriteOnly);
148 }
149
150 return true;
151}
152
153void QCupsPrintEnginePrivate::closePrintDevice()
154{
155 QPdfPrintEnginePrivate::closePrintDevice();
156
157 if (!cupsTempFile.isEmpty()) {
158 QString tempFile = cupsTempFile;
159 cupsTempFile.clear();
160
161 // Should never have got here without a printer, but check anyway
162 if (printerName.isEmpty()) {
163 qWarning(msg: "Could not determine printer to print to");
164 QFile::remove(fileName: tempFile);
165 return;
166 }
167
168 // Set up print options.
169 QList<QPair<QByteArray, QByteArray> > options;
170 QList<cups_option_t> cupsOptStruct;
171
172 options.append(t: QPair<QByteArray, QByteArray>("media", m_pageLayout.pageSize().key().toLocal8Bit()));
173
174 if (copies > 1)
175 options.append(t: QPair<QByteArray, QByteArray>("copies", QString::number(copies).toLocal8Bit()));
176
177 if (copies > 1 && collate)
178 options.append(t: QPair<QByteArray, QByteArray>("Collate", "True"));
179
180 switch (duplex) {
181 case QPrint::DuplexNone:
182 options.append(t: QPair<QByteArray, QByteArray>("sides", "one-sided"));
183 break;
184 case QPrint::DuplexAuto:
185 if (m_pageLayout.orientation() == QPageLayout::Portrait)
186 options.append(t: QPair<QByteArray, QByteArray>("sides", "two-sided-long-edge"));
187 else
188 options.append(t: QPair<QByteArray, QByteArray>("sides", "two-sided-short-edge"));
189 break;
190 case QPrint::DuplexLongSide:
191 options.append(t: QPair<QByteArray, QByteArray>("sides", "two-sided-long-edge"));
192 break;
193 case QPrint::DuplexShortSide:
194 options.append(t: QPair<QByteArray, QByteArray>("sides", "two-sided-short-edge"));
195 break;
196 }
197
198 if (m_pageLayout.orientation() == QPageLayout::Landscape)
199 options.append(t: QPair<QByteArray, QByteArray>("landscape", ""));
200
201 QStringList::const_iterator it = cupsOptions.constBegin();
202 while (it != cupsOptions.constEnd()) {
203 options.append(t: QPair<QByteArray, QByteArray>((*it).toLocal8Bit(), (*(it+1)).toLocal8Bit()));
204 it += 2;
205 }
206
207 const int numOptions = options.size();
208 cupsOptStruct.reserve(asize: numOptions);
209 for (int c = 0; c < numOptions; ++c) {
210 cups_option_t opt;
211 opt.name = options[c].first.data();
212 opt.value = options[c].second.data();
213 cupsOptStruct.append(t: opt);
214 }
215
216 // Print the file
217 // Cups expect the printer original name without instance, the full name is used only to retrieve the configuration
218 const auto parts = QStringView{printerName}.split(sep: u'/');
219 const auto printerOriginalName = parts.at(i: 0);
220 cups_option_t* optPtr = cupsOptStruct.size() ? &cupsOptStruct.first() : 0;
221 cupsPrintFile(name: printerOriginalName.toLocal8Bit().constData(), filename: tempFile.toLocal8Bit().constData(),
222 title: title.toLocal8Bit().constData(), num_options: cupsOptStruct.size(), options: optPtr);
223
224 QFile::remove(fileName: tempFile);
225 }
226}
227
228void QCupsPrintEnginePrivate::changePrinter(const QString &newPrinter)
229{
230 // Don't waste time if same printer name
231 if (newPrinter == printerName)
232 return;
233
234 // Should never have reached here if no plugin available, but check just in case
235 QPlatformPrinterSupport *ps = QPlatformPrinterSupportPlugin::get();
236 if (!ps)
237 return;
238
239 // Try create the printer, only use it if it returns valid
240 QPrintDevice printDevice = ps->createPrintDevice(id: newPrinter);
241 if (!printDevice.isValid())
242 return;
243 m_printDevice.swap(other&: printDevice);
244 printerName = m_printDevice.id();
245
246 // in case a duplex value was explicitly set, check if new printer supports current value,
247 // otherwise use device default
248 if (!duplexRequestedExplicitly || !m_printDevice.supportedDuplexModes().contains(t: duplex)) {
249 duplex = m_printDevice.defaultDuplexMode();
250 duplexRequestedExplicitly = false;
251 }
252 QPrint::ColorMode colorMode = grayscale ? QPrint::GrayScale : QPrint::Color;
253 if (!m_printDevice.supportedColorModes().contains(t: colorMode))
254 grayscale = m_printDevice.defaultColorMode() == QPrint::GrayScale;
255
256 // Get the equivalent page size for this printer as supported names may be different
257 if (m_printDevice.supportedPageSize(pageSize: m_pageLayout.pageSize()).isValid())
258 setPageSize(m_pageLayout.pageSize());
259 else
260 setPageSize(QPageSize(m_pageLayout.pageSize().size(units: QPageSize::Point), QPageSize::Point));
261}
262
263void QCupsPrintEnginePrivate::setPageSize(const QPageSize &pageSize)
264{
265 if (pageSize.isValid()) {
266 // Find if the requested page size has a matching printer page size, if so use its defined name instead
267 QPageSize printerPageSize = m_printDevice.supportedPageSize(pageSize);
268 QPageSize usePageSize = printerPageSize.isValid() ? printerPageSize : pageSize;
269 QMarginsF printable = m_printDevice.printableMargins(pageSize: usePageSize, orientation: m_pageLayout.orientation(), resolution);
270 m_pageLayout.setPageSize(pageSize: usePageSize, minMargins: qt_convertMargins(margins: printable, fromUnits: QPageLayout::Point, toUnits: m_pageLayout.units()));
271 }
272}
273
274QT_END_NAMESPACE
275

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