File indexing completed on 2024-04-28 05:36:52

0001 /*
0002  * SPDX-FileCopyrightText: 2016-2017 Jan Grulich <jgrulich@redhat.com>
0003  * SPDX-FileCopyrightText: 2007, 2010 John Layt <john@layt.net>
0004  * SPDX-FileCopyrightText: 2007 Alex Merry <huntedhacker@tiscali.co.uk>
0005  * SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0006  *
0007  * SPDX-License-Identifier: LGPL-2.0-or-later
0008  *
0009  */
0010 
0011 #include "print.h"
0012 #include "print_debug.h"
0013 #include "request.h"
0014 #include "utils.h"
0015 
0016 #include <KProcess>
0017 
0018 #include <QFile>
0019 #include <QPrintDialog>
0020 #include <QPrintEngine>
0021 #include <QStandardPaths>
0022 // #include <QtPrintSupport/private/qprint_p.h>
0023 #include <QTemporaryFile>
0024 #include <QUrl>
0025 #include <QtPrintSupport/private/qcups_p.h>
0026 
0027 // INFO: code below is copied from Qt as there is no public API for converting key to PageSizeId
0028 
0029 // Standard sizes data
0030 struct StandardPageSize {
0031     QPageSize::PageSizeId id;
0032     const char *mediaOption; // PPD standard mediaOption ID
0033 };
0034 
0035 // Standard page sizes taken from the Postscript PPD Standard v4.3
0036 // See https://www-cdf.fnal.gov/offline/PostScript/5003.PPD_Spec_v4.3.pdf
0037 // Excludes all Transverse and Rotated sizes
0038 // NB!This table needs to be in sync with QPageSize::PageSizeId
0039 const static StandardPageSize qt_pageSizes[] = {
0040 
0041     // Existing Qt sizes including ISO, US, ANSI and other standards
0042     {QPageSize::A4, "A4"},
0043     {QPageSize::B5, "ISOB5"},
0044     {QPageSize::Letter, "Letter"},
0045     {QPageSize::Legal, "Legal"},
0046     {QPageSize::Executive, "Executive.7.5x10in"}, // Qt size differs from Postscript / Windows
0047     {QPageSize::A0, "A0"},
0048     {QPageSize::A1, "A1"},
0049     {QPageSize::A2, "A2"},
0050     {QPageSize::A3, "A3"},
0051     {QPageSize::A5, "A5"},
0052     {QPageSize::A6, "A6"},
0053     {QPageSize::A7, "A7"},
0054     {QPageSize::A8, "A8"},
0055     {QPageSize::A9, "A9"},
0056     {QPageSize::B0, "ISOB0"},
0057     {QPageSize::B1, "ISOB1"},
0058     {QPageSize::B10, "ISOB10"},
0059     {QPageSize::B2, "ISOB2"},
0060     {QPageSize::B3, "ISOB3"},
0061     {QPageSize::B4, "ISOB4"},
0062     {QPageSize::B6, "ISOB6"},
0063     {QPageSize::B7, "ISOB7"},
0064     {QPageSize::B8, "ISOB8"},
0065     {QPageSize::B9, "ISOB9"},
0066     {QPageSize::C5E, "EnvC5"},
0067     {QPageSize::Comm10E, "Env10"},
0068     {QPageSize::DLE, "EnvDL"},
0069     {QPageSize::Folio, "Folio"},
0070     {QPageSize::Ledger, "Ledger"},
0071     {QPageSize::Tabloid, "Tabloid"},
0072     {QPageSize::Custom, "Custom"}, // Special case to keep in sync with QPageSize::PageSizeId
0073 
0074     // ISO Standard Sizes
0075     {QPageSize::A10, "A10"},
0076     {QPageSize::A3Extra, "A3Extra"},
0077     {QPageSize::A4Extra, "A4Extra"},
0078     {QPageSize::A4Plus, "A4Plus"},
0079     {QPageSize::A4Small, "A4Small"},
0080     {QPageSize::A5Extra, "A5Extra"},
0081     {QPageSize::B5Extra, "ISOB5Extra"},
0082 
0083     // JIS Standard Sizes
0084     {QPageSize::JisB0, "B0"},
0085     {QPageSize::JisB1, "B1"},
0086     {QPageSize::JisB2, "B2"},
0087     {QPageSize::JisB3, "B3"},
0088     {QPageSize::JisB4, "B4"},
0089     {QPageSize::JisB5, "B5"},
0090     {QPageSize::JisB6, "B6"},
0091     {QPageSize::JisB7, "B7"},
0092     {QPageSize::JisB8, "B8"},
0093     {QPageSize::JisB9, "B9"},
0094     {QPageSize::JisB10, "B10"},
0095 
0096     // ANSI / US Standard sizes
0097     {QPageSize::AnsiC, "AnsiC"},
0098     {QPageSize::AnsiD, "AnsiD"},
0099     {QPageSize::AnsiE, "AnsiE"},
0100     {QPageSize::LegalExtra, "LegalExtra"},
0101     {QPageSize::LetterExtra, "LetterExtra"},
0102     {QPageSize::LetterPlus, "LetterPlus"},
0103     {QPageSize::LetterSmall, "LetterSmall"},
0104     {QPageSize::TabloidExtra, "TabloidExtra"},
0105 
0106     // Architectural sizes
0107     {QPageSize::ArchA, "ARCHA"},
0108     {QPageSize::ArchB, "ARCHB"},
0109     {QPageSize::ArchC, "ARCHC"},
0110     {QPageSize::ArchD, "ARCHD"},
0111     {QPageSize::ArchE, "ARCHE"},
0112 
0113     // Inch-based Sizes
0114     {QPageSize::Imperial7x9, "7x9"},
0115     {QPageSize::Imperial8x10, "8x10"},
0116     {QPageSize::Imperial9x11, "9x11"},
0117     {QPageSize::Imperial9x12, "9x12"},
0118     {QPageSize::Imperial10x11, "10x11"},
0119     {QPageSize::Imperial10x13, "10x13"},
0120     {QPageSize::Imperial10x14, "10x14"},
0121     {QPageSize::Imperial12x11, "12x11"},
0122     {QPageSize::Imperial15x11, "15x11"},
0123 
0124     // Other Page Sizes
0125     {QPageSize::ExecutiveStandard, "Executive"}, // Qt size differs from Postscript / Windows
0126     {QPageSize::Note, "Note"},
0127     {QPageSize::Quarto, "Quarto"},
0128     {QPageSize::Statement, "Statement"},
0129     {QPageSize::SuperA, "SuperA"},
0130     {QPageSize::SuperB, "SuperB"},
0131     {QPageSize::Postcard, "Postcard"},
0132     {QPageSize::DoublePostcard, "DoublePostcard"},
0133     {QPageSize::Prc16K, "PRC16K"},
0134     {QPageSize::Prc32K, "PRC32K"},
0135     {QPageSize::Prc32KBig, "PRC32KBig"},
0136 
0137     // Fan Fold Sizes
0138     {QPageSize::FanFoldUS, "FanFoldUS"},
0139     {QPageSize::FanFoldGerman, "FanFoldGerman"},
0140     {QPageSize::FanFoldGermanLegal, "FanFoldGermanLegal"},
0141 
0142     // ISO Envelopes
0143     {QPageSize::EnvelopeB4, "EnvISOB4"},
0144     {QPageSize::EnvelopeB5, "EnvISOB5"},
0145     {QPageSize::EnvelopeB6, "EnvISOB6"},
0146     {QPageSize::EnvelopeC0, "EnvC0"},
0147     {QPageSize::EnvelopeC1, "EnvC1"},
0148     {QPageSize::EnvelopeC2, "EnvC2"},
0149     {QPageSize::EnvelopeC3, "EnvC3"},
0150     {QPageSize::EnvelopeC4, "EnvC4"},
0151     {QPageSize::EnvelopeC6, "EnvC6"},
0152     {QPageSize::EnvelopeC65, "EnvC65"},
0153     {QPageSize::EnvelopeC7, "EnvC7"},
0154 
0155     // US Envelopes
0156     {QPageSize::Envelope9, "Env9"},
0157     {QPageSize::Envelope11, "Env11"},
0158     {QPageSize::Envelope12, "Env12"},
0159     {QPageSize::Envelope14, "Env14"},
0160     {QPageSize::EnvelopeMonarch, "EnvMonarch"},
0161     {QPageSize::EnvelopePersonal, "EnvPersonal"},
0162 
0163     // Other Envelopes
0164     {QPageSize::EnvelopeChou3, "EnvChou3"},
0165     {QPageSize::EnvelopeChou4, "EnvChou4"},
0166     {QPageSize::EnvelopeInvite, "EnvInvite"},
0167     {QPageSize::EnvelopeItalian, "EnvItalian"},
0168     {QPageSize::EnvelopeKaku2, "EnvKaku2"},
0169     {QPageSize::EnvelopeKaku3, "EnvKaku3"},
0170     {QPageSize::EnvelopePrc1, "EnvPRC1"},
0171     {QPageSize::EnvelopePrc2, "EnvPRC2"},
0172     {QPageSize::EnvelopePrc3, "EnvPRC3"},
0173     {QPageSize::EnvelopePrc4, "EnvPRC4"},
0174     {QPageSize::EnvelopePrc5, "EnvPRC5"},
0175     {QPageSize::EnvelopePrc6, "EnvPRC6"},
0176     {QPageSize::EnvelopePrc7, "EnvPRC7"},
0177     {QPageSize::EnvelopePrc8, "EnvPRC8"},
0178     {QPageSize::EnvelopePrc9, "EnvPRC9"},
0179     {QPageSize::EnvelopePrc10, "EnvPRC10"},
0180     {QPageSize::EnvelopeYou4, "EnvYou4"}};
0181 
0182 // Return key name for PageSize
0183 static QString qt_keyForPageSizeId(QPageSize::PageSizeId id)
0184 {
0185     return QString::fromLatin1(qt_pageSizes[id].mediaOption);
0186 }
0187 
0188 // Return id name for PPD Key
0189 static QPageSize::PageSizeId qt_idForPpdKey(const QString &ppdKey)
0190 {
0191     if (ppdKey.isEmpty()) {
0192         return QPageSize::Custom;
0193     }
0194 
0195     for (int i = 0; i <= int(QPageSize::LastPageSize); ++i) {
0196         if (QLatin1String(qt_pageSizes[i].mediaOption) == ppdKey) {
0197             return qt_pageSizes[i].id;
0198         }
0199     }
0200     return QPageSize::Custom;
0201 }
0202 
0203 PrintPortal::PrintPortal(QObject *parent)
0204     : QDBusAbstractAdaptor(parent)
0205 {
0206 }
0207 
0208 uint PrintPortal::Print(const QDBusObjectPath &handle,
0209                         const QString &app_id,
0210                         const QString &parent_window,
0211                         const QString &title,
0212                         const QDBusUnixFileDescriptor &fd,
0213                         const QVariantMap &options,
0214                         QVariantMap &results)
0215 {
0216     Q_UNUSED(results)
0217     qCDebug(XdgDesktopPortalKdePrint) << "Print called with parameters:";
0218     qCDebug(XdgDesktopPortalKdePrint) << "    handle: " << handle.path();
0219     qCDebug(XdgDesktopPortalKdePrint) << "    app_id: " << app_id;
0220     qCDebug(XdgDesktopPortalKdePrint) << "    parent_window: " << parent_window;
0221     qCDebug(XdgDesktopPortalKdePrint) << "    title: " << title;
0222     qCDebug(XdgDesktopPortalKdePrint) << "    fd: " << fd.fileDescriptor();
0223     qCDebug(XdgDesktopPortalKdePrint) << "    options: " << options;
0224 
0225     QFile fileToPrint;
0226     if (fileToPrint.open(fd.fileDescriptor(), QIODevice::ReadOnly)) {
0227         QPrinter *printer = nullptr;
0228         // Use printer associated with token if possible
0229         if (options.contains(QStringLiteral("token"))) {
0230             printer = m_printers.value(options.value(QStringLiteral("token")).toUInt());
0231         } else {
0232             // Use the last configured printer otherwise
0233             if (m_printers.count()) {
0234                 printer = m_printers.last();
0235             }
0236         }
0237 
0238         if (!printer) {
0239             qCDebug(XdgDesktopPortalKdePrint) << "Failed to print: no QPrinter what can be used for printing";
0240             return 1;
0241         }
0242 
0243         // We are going to print to a file
0244         if (!printer->outputFileName().isEmpty()) {
0245             if (QFile::exists(printer->outputFileName())) {
0246                 QFile::remove(printer->outputFileName());
0247             }
0248 
0249             QByteArray pdfContent = fileToPrint.readAll();
0250             QFile outputFile(printer->outputFileName());
0251             if (outputFile.open(QIODevice::ReadWrite)) {
0252                 outputFile.write(pdfContent);
0253                 outputFile.close();
0254             } else {
0255                 qCDebug(XdgDesktopPortalKdePrint) << "Failed to print: couldn't open the file for writing";
0256             }
0257 
0258             fileToPrint.close();
0259 
0260             return 0;
0261 
0262             // TODO poscript support?
0263 
0264             // Print to a printer via lpr command
0265         } else {
0266             // The code below is copied from Okular
0267             bool useCupsOptions = cupsAvailable();
0268             QString exe;
0269             QStringList argList;
0270 
0271             // Decide what executable to use to print with, need the CUPS version of lpr if available
0272             // Some distros name the CUPS version of lpr as lpr-cups or lpr.cups so try those first
0273             // before default to lpr, or failing that to lp
0274             if (!QStandardPaths::findExecutable(QStringLiteral("lpr-cups")).isEmpty()) {
0275                 exe = QStringLiteral("lpr-cups");
0276             } else if (!QStandardPaths::findExecutable(QStringLiteral("lpr.cups")).isEmpty()) {
0277                 exe = QStringLiteral("lpr.cups");
0278             } else if (!QStandardPaths::findExecutable(QStringLiteral("lpr")).isEmpty()) {
0279                 exe = QStringLiteral("lpr");
0280             } else if (!QStandardPaths::findExecutable(QStringLiteral("lp")).isEmpty()) {
0281                 exe = QStringLiteral("lp");
0282             } else {
0283                 qCDebug(XdgDesktopPortalKdePrint) << "Failed to print: couldn't run lpr command for printing";
0284                 return 1;
0285             }
0286 
0287             QTemporaryFile tempFile;
0288             if (tempFile.open()) {
0289                 tempFile.write(fileToPrint.readAll());
0290                 tempFile.close();
0291             } else {
0292                 qCDebug(XdgDesktopPortalKdePrint) << "Failed to print: couldn't create temporary file for printing";
0293                 return 1;
0294             }
0295 
0296             argList = printArguments(printer, useCupsOptions, exe, printer->pageLayout().orientation()) << tempFile.fileName();
0297             // qCDebug(XdgDesktopPortalKdePrint) << "Executing" << exe << "with arguments" << argList << tempFile.fileName();
0298             int retValue = KProcess::execute(exe, argList);
0299 
0300             if (retValue <= 0) {
0301                 qCDebug(XdgDesktopPortalKdePrint) << "Failed to print: running KProcess failed";
0302                 return 1;
0303             }
0304 
0305             return retValue;
0306         }
0307     } else {
0308         qCDebug(XdgDesktopPortalKdePrint) << "Failed to print: couldn't not read from fd";
0309         return 1;
0310     }
0311 
0312     return 0;
0313 }
0314 
0315 uint PrintPortal::PreparePrint(const QDBusObjectPath &handle,
0316                                const QString &app_id,
0317                                const QString &parent_window,
0318                                const QString &title,
0319                                const QVariantMap &settings,
0320                                const QVariantMap &page_setup,
0321                                const QVariantMap &options,
0322                                QVariantMap &results)
0323 {
0324     qCDebug(XdgDesktopPortalKdePrint) << "PreparePrint called with parameters:";
0325     qCDebug(XdgDesktopPortalKdePrint) << "    handle: " << handle.path();
0326     qCDebug(XdgDesktopPortalKdePrint) << "    app_id: " << app_id;
0327     qCDebug(XdgDesktopPortalKdePrint) << "    parent_window: " << parent_window;
0328     qCDebug(XdgDesktopPortalKdePrint) << "    title: " << title;
0329     qCDebug(XdgDesktopPortalKdePrint) << "    settings: " << settings;
0330     qCDebug(XdgDesktopPortalKdePrint) << "    page_setup: " << page_setup;
0331     qCDebug(XdgDesktopPortalKdePrint) << "    options: " << options;
0332 
0333     // Create new one
0334     QPrinter *printer = new QPrinter();
0335 
0336     // First we have to load pre-configured options
0337 
0338     // Process settings (used by printer)
0339     QCUPSSupport::PagesPerSheet pagesPerSheet = QCUPSSupport::OnePagePerSheet;
0340     QCUPSSupport::PagesPerSheetLayout pagesPerSheetLayout = QCUPSSupport::LeftToRightTopToBottom;
0341     for (auto it = settings.constBegin(); it != settings.constEnd(); ++it) {
0342         if (it.key() == QStringLiteral("orientation")) {
0343             const QString orientation = it.value().toString();
0344             if (orientation == QLatin1String("landscape") || orientation == QLatin1String("reverse_landscape")) {
0345                 printer->setPageOrientation(QPageLayout::Landscape);
0346             } else if (orientation == QLatin1String("portrait") || orientation == QLatin1String("reverse_portrait")) {
0347                 printer->setPageOrientation(QPageLayout::Portrait);
0348             }
0349         } else if (it.key() == QStringLiteral("n-copies")) {
0350             printer->setCopyCount(it.value().toString().toInt());
0351         } else if (it.key() == QStringLiteral("resolution")) {
0352             printer->setResolution(it.value().toString().toInt());
0353         } else if (it.key() == QStringLiteral("use-color")) {
0354             printer->setColorMode(it.value().toString() == QLatin1String("yes") ? QPrinter::Color : QPrinter::GrayScale);
0355         } else if (it.key() == QStringLiteral("duplex")) {
0356             const QString duplex = it.value().toString();
0357             if (duplex == QLatin1String("simplex")) {
0358                 printer->setDuplex(QPrinter::DuplexNone);
0359             } else if (duplex == QLatin1String("horizontal")) {
0360                 printer->setDuplex(QPrinter::DuplexShortSide);
0361             } else if (duplex == QLatin1String("vertical")) {
0362                 printer->setDuplex(QPrinter::DuplexLongSide);
0363             }
0364         } else if (it.key() == QStringLiteral("collate")) {
0365             printer->setCollateCopies(it.value().toString() == QLatin1String("yes"));
0366         } else if (it.key() == QStringLiteral("reverse")) {
0367             printer->setPageOrder(it.value().toString() == QLatin1String("yes") ? QPrinter::LastPageFirst : QPrinter::FirstPageFirst);
0368         } else if (it.key() == QStringLiteral("print-pages")) {
0369             const QString printPages = it.value().toString();
0370             if (printPages == QLatin1String("all")) {
0371                 printer->setPrintRange(QPrinter::AllPages);
0372             } else if (printPages == QLatin1String("selection")) {
0373                 printer->setPrintRange(QPrinter::Selection);
0374             } else if (printPages == QLatin1String("current")) {
0375                 printer->setPrintRange(QPrinter::CurrentPage);
0376             } else if (printPages == QLatin1String("ranges")) {
0377                 printer->setPrintRange(QPrinter::PageRange);
0378             }
0379         } else if (it.key() == QStringLiteral("page-ranges")) {
0380             const QString range = it.value().toString();
0381             // Gnome supports format like 1-5,7,9,11-15, however Qt support only e.g.1-15
0382             // so get the first and the last value
0383             const QStringList ranges = range.split(QLatin1Char(','));
0384             if (ranges.count()) {
0385                 QStringList firstRangeValues = ranges.first().split(QLatin1Char('-'));
0386                 QStringList lastRangeValues = ranges.last().split(QLatin1Char('-'));
0387                 printer->setFromTo(firstRangeValues.first().toInt(), lastRangeValues.last().toInt());
0388             }
0389         } else if (it.key() == QStringLiteral("page-set")) {
0390             // WARNING Qt internal private API, anyway the print dialog doesn't seem to
0391             // read these properties, but I'll leave it here in case this changes in future
0392             const QString pageSet = it.value().toString();
0393             if (pageSet == QLatin1String("all")) {
0394                 QCUPSSupport::setPageSet(printer, QCUPSSupport::AllPages);
0395             } else if (pageSet == QLatin1String("even")) {
0396                 QCUPSSupport::setPageSet(printer, QCUPSSupport::EvenPages);
0397             } else if (pageSet == QLatin1String("odd")) {
0398                 QCUPSSupport::setPageSet(printer, QCUPSSupport::OddPages);
0399             }
0400         } else if (it.key() == QStringLiteral("number-up")) {
0401             // WARNING Qt internal private API, anyway the print dialog doesn't seem to
0402             // read these properties, but I'll leave it here in case this changes in future
0403             const QString numberUp = it.value().toString();
0404             if (numberUp == QLatin1String("1")) {
0405                 pagesPerSheet = QCUPSSupport::OnePagePerSheet;
0406             } else if (numberUp == QLatin1String("2")) {
0407                 pagesPerSheet = QCUPSSupport::TwoPagesPerSheet;
0408             } else if (numberUp == QLatin1String("4")) {
0409                 pagesPerSheet = QCUPSSupport::FourPagesPerSheet;
0410             } else if (numberUp == QLatin1String("6")) {
0411                 pagesPerSheet = QCUPSSupport::SixPagesPerSheet;
0412             } else if (numberUp == QLatin1String("9")) {
0413                 pagesPerSheet = QCUPSSupport::NinePagesPerSheet;
0414             } else if (numberUp == QLatin1String("16")) {
0415                 pagesPerSheet = QCUPSSupport::SixteenPagesPerSheet;
0416             }
0417         } else if (it.key() == QStringLiteral("number-up-layout")) {
0418             // WARNING Qt internal private API, anyway the print dialog doesn't seem to
0419             // read these properties, but I'll leave it here in case this changes in future
0420             const QString layout = it.value().toString();
0421             if (layout == QLatin1String("lrtb")) {
0422                 pagesPerSheetLayout = QCUPSSupport::LeftToRightTopToBottom;
0423             } else if (layout == QLatin1String("lrbt")) {
0424                 pagesPerSheetLayout = QCUPSSupport::LeftToRightBottomToTop;
0425             } else if (layout == QLatin1String("rltb")) {
0426                 pagesPerSheetLayout = QCUPSSupport::RightToLeftTopToBottom;
0427             } else if (layout == QLatin1String("rlbt")) {
0428                 pagesPerSheetLayout = QCUPSSupport::RightToLeftBottomToTop;
0429             } else if (layout == QLatin1String("tblr")) {
0430                 pagesPerSheetLayout = QCUPSSupport::TopToBottomLeftToRight;
0431             } else if (layout == QLatin1String("tbrl")) {
0432                 pagesPerSheetLayout = QCUPSSupport::TopToBottomRightToLeft;
0433             } else if (layout == QLatin1String("btlr")) {
0434                 pagesPerSheetLayout = QCUPSSupport::BottomToTopLeftToRight;
0435             } else if (layout == QLatin1String("btrl")) {
0436                 pagesPerSheetLayout = QCUPSSupport::BottomToTopRightToLeft;
0437             }
0438         } else if (it.key() == QStringLiteral("output-file-format")) {
0439             // TODO only PDF supported by Qt so when printing to file we can use PDF only
0440             // btw. this should be already set automatically because we set output file name
0441             printer->setOutputFormat(QPrinter::PdfFormat);
0442         } else if (it.key() == QStringLiteral("output-uri")) {
0443             const QUrl uri = QUrl(it.value().toString());
0444             // Check whether the uri is not just a directory name and whether we don't need to
0445             // append output-basename
0446             if (settings.contains(QStringLiteral("output-basename"))) {
0447                 const QString basename = settings.value(QStringLiteral("output-basename")).toString();
0448                 if (!uri.toDisplayString().endsWith(basename) && uri.toDisplayString().endsWith(QLatin1Char('/'))) {
0449                     printer->setOutputFileName(uri.toDisplayString() + basename);
0450                 }
0451             } else {
0452                 printer->setOutputFileName(uri.toDisplayString());
0453             }
0454         } else {
0455             qCDebug(XdgDesktopPortalKdePrint) << "Unhandled printer setting: " << it.key();
0456         }
0457     }
0458 
0459     QCUPSSupport::setPagesPerSheetLayout(printer, pagesPerSheet, pagesPerSheetLayout);
0460 
0461     // Process page setup
0462     QSizeF paperSize;
0463     QString ppdName;
0464     QString name;
0465     QMarginsF pageMargins = printer->pageLayout().margins(QPageLayout::Millimeter);
0466     for (auto it = page_setup.constBegin(); it != page_setup.constEnd(); ++it) {
0467         if (it.key() == QStringLiteral("PPDName")) {
0468             ppdName = it.value().toString();
0469         } else if (it.key() == QStringLiteral("Name")) {
0470             name = it.value().toString();
0471         }
0472 
0473         if (it.key() == QStringLiteral("Width")) {
0474             paperSize.setWidth(it.value().toReal());
0475         } else if (it.key() == QStringLiteral("Height")) {
0476             paperSize.setHeight(it.value().toReal());
0477         } else if (it.key() == QStringLiteral("MarginTop")) {
0478             pageMargins.setTop(it.value().toReal());
0479         } else if (it.key() == QStringLiteral("MarginBottom")) {
0480             pageMargins.setBottom(it.value().toReal());
0481         } else if (it.key() == QStringLiteral("MarginLeft")) {
0482             pageMargins.setLeft(it.value().toReal());
0483         } else if (it.key() == QStringLiteral("MarginRight")) {
0484             pageMargins.setRight(it.value().toReal());
0485         } else if (it.key() == QStringLiteral("Orientation")) {
0486             const QString orientation = it.value().toString();
0487             if (orientation == QLatin1String("landscape") || orientation == QLatin1String("reverse_landscape")) {
0488                 printer->setPageOrientation(QPageLayout::Landscape);
0489             } else if (orientation == QLatin1String("portrait") || orientation == QLatin1String("reverse_portrait")) {
0490                 printer->setPageOrientation(QPageLayout::Portrait);
0491             }
0492         } else {
0493             qCDebug(XdgDesktopPortalKdePrint) << "Unhandled page setup: " << it.key();
0494         }
0495     }
0496 
0497     if (!ppdName.isEmpty()) {
0498         printer->setPageSize(QPageSize(qt_idForPpdKey(ppdName)));
0499     } else if (!name.isEmpty()) {
0500         printer->setPageSize(QPageSize(paperSize, QPageSize::Millimeter, name));
0501     }
0502     printer->setPageMargins(pageMargins, QPageLayout::Millimeter);
0503 
0504     QPrintDialog *printDialog = new QPrintDialog(printer);
0505     Utils::setParentWindow(printDialog, parent_window);
0506     Request::makeClosableDialogRequest(handle, printDialog);
0507 
0508     // Process options
0509     if (options.contains(QStringLiteral("modal"))) {
0510         printDialog->setModal(options.value(QStringLiteral("modal")).toBool());
0511     }
0512 
0513     // Pass back what we configured
0514     if (printDialog->exec() == QDialog::Accepted) {
0515         QVariantMap resultingSettings;
0516         QVariantMap resultingPageSetup;
0517 
0518         // Process back printer settings
0519         resultingSettings.insert(QStringLiteral("n-copies"), QString::number(printer->copyCount()));
0520         resultingSettings.insert(QStringLiteral("resolution"), QString::number(printer->resolution()));
0521         resultingSettings.insert(QStringLiteral("use-color"), printer->colorMode() == QPrinter::Color ? QLatin1String("yes") : QLatin1String("no"));
0522         if (printer->duplex() == QPrinter::DuplexNone) {
0523             resultingSettings.insert(QStringLiteral("duplex"), QLatin1String("simplex"));
0524         } else if (printer->duplex() == QPrinter::DuplexShortSide) {
0525             resultingSettings.insert(QStringLiteral("duplex"), QLatin1String("horizontal"));
0526         } else if (printer->duplex() == QPrinter::DuplexLongSide) {
0527             resultingSettings.insert(QStringLiteral("duplex"), QLatin1String("vertical"));
0528         }
0529         resultingSettings.insert(QStringLiteral("collate"), printer->collateCopies() ? QLatin1String("yes") : QLatin1String("no"));
0530         resultingSettings.insert(QStringLiteral("reverse"), printer->pageOrder() == QPrinter::LastPageFirst ? QLatin1String("yes") : QLatin1String("no"));
0531         if (printer->printRange() == QPrinter::AllPages) {
0532             resultingSettings.insert(QStringLiteral("print-pages"), QLatin1String("all"));
0533         } else if (printer->printRange() == QPrinter::Selection) {
0534             resultingSettings.insert(QStringLiteral("print-pages"), QLatin1String("selection"));
0535         } else if (printer->printRange() == QPrinter::CurrentPage) {
0536             resultingSettings.insert(QStringLiteral("print-pages"), QLatin1String("current"));
0537         } else if (printer->printRange() == QPrinter::PageRange) {
0538             resultingSettings.insert(QStringLiteral("print-pages"), QLatin1String("ranges"));
0539             const int fromPageToIndex = printer->fromPage() ? printer->fromPage() - 1 : printer->fromPage();
0540             const int toPageToIndex = printer->toPage() ? printer->toPage() - 1 : printer->toPage();
0541             resultingSettings.insert(QStringLiteral("page-ranges"), QStringLiteral("%1-%2").arg(fromPageToIndex).arg(toPageToIndex));
0542         }
0543         // Set cups specific properties
0544         const QStringList cupsOptions = printer->printEngine()->property(PPK_CupsOptions).toStringList();
0545         qCDebug(XdgDesktopPortalKdePrint) << cupsOptions;
0546         if (cupsOptions.contains(QLatin1String("page-set"))) {
0547             resultingSettings.insert(QStringLiteral("page-set"), cupsOptions.at(cupsOptions.indexOf(QStringLiteral("page-set")) + 1));
0548         }
0549         if (cupsOptions.contains(QLatin1String("number-up"))) {
0550             resultingSettings.insert(QStringLiteral("number-up"), cupsOptions.at(cupsOptions.indexOf(QStringLiteral("number-up")) + 1));
0551         }
0552         if (cupsOptions.contains(QLatin1String("number-up-layout"))) {
0553             resultingSettings.insert(QStringLiteral("number-up-layout"), cupsOptions.at(cupsOptions.indexOf(QStringLiteral("number-up-layout")) + 1));
0554         }
0555 
0556         if (printer->outputFormat() == QPrinter::PdfFormat) {
0557             resultingSettings.insert(QStringLiteral("output-file-format"), QLatin1String("pdf"));
0558         }
0559 
0560         if (!printer->outputFileName().isEmpty()) {
0561             resultingSettings.insert(QStringLiteral("output-uri"), QUrl::fromLocalFile(printer->outputFileName()).toDisplayString());
0562         }
0563 
0564         // Process back page setup
0565         if (printer->pageLayout().pageSize().id() == QPageSize::Custom) {
0566             resultingPageSetup.insert(QStringLiteral("Name"), printer->pageLayout().pageSize().name());
0567             resultingPageSetup.insert(QStringLiteral("DisplayName"), printer->pageLayout().pageSize().name());
0568         } else {
0569             resultingPageSetup.insert(QStringLiteral("PPDName"), qt_keyForPageSizeId(printer->pageLayout().pageSize().id()));
0570             resultingPageSetup.insert(QStringLiteral("DisplayName"), qt_keyForPageSizeId(printer->pageLayout().pageSize().id()));
0571         }
0572         resultingPageSetup.insert(QStringLiteral("Width"), printer->pageLayout().pageSize().size(QPageSize::Millimeter).width());
0573         resultingPageSetup.insert(QStringLiteral("Height"), printer->pageLayout().pageSize().size(QPageSize::Millimeter).height());
0574         resultingPageSetup.insert(QStringLiteral("MarginTop"), printer->pageLayout().margins(QPageLayout::Millimeter).top());
0575         resultingPageSetup.insert(QStringLiteral("MarginBottom"), printer->pageLayout().margins(QPageLayout::Millimeter).bottom());
0576         resultingPageSetup.insert(QStringLiteral("MarginLeft"), printer->pageLayout().margins(QPageLayout::Millimeter).left());
0577         resultingPageSetup.insert(QStringLiteral("MarginRight"), printer->pageLayout().margins(QPageLayout::Millimeter).right());
0578         resultingPageSetup.insert(QStringLiteral("Orientation"),
0579                                   printer->pageLayout().orientation() == QPageLayout::Landscape ? QLatin1String("landscape") : QLatin1String("portrait"));
0580 
0581         qCDebug(XdgDesktopPortalKdePrint) << "Settings: ";
0582         qCDebug(XdgDesktopPortalKdePrint) << "---------------------------";
0583         qCDebug(XdgDesktopPortalKdePrint) << resultingSettings;
0584         qCDebug(XdgDesktopPortalKdePrint) << "Page setup: ";
0585         qCDebug(XdgDesktopPortalKdePrint) << "---------------------------";
0586         qCDebug(XdgDesktopPortalKdePrint) << resultingPageSetup;
0587 
0588         uint token = QDateTime::currentDateTime().toSecsSinceEpoch();
0589         results.insert(QStringLiteral("settings"), resultingSettings);
0590         results.insert(QStringLiteral("page-setup"), resultingPageSetup);
0591         results.insert(QStringLiteral("token"), token);
0592 
0593         m_printers.insert(token, printer);
0594 
0595         printDialog->deleteLater();
0596         return 0;
0597     } else {
0598         printDialog->deleteLater();
0599         return 1;
0600     }
0601 
0602     return 0;
0603 }
0604 
0605 QStringList PrintPortal::destination(const QPrinter *printer, const QString &version)
0606 {
0607     if (version == QLatin1String("lp")) {
0608         return QStringList(QStringLiteral("-d")) << printer->printerName();
0609     }
0610 
0611     if (version.startsWith(QLatin1String("lpr"))) {
0612         return QStringList(QStringLiteral("-P")) << printer->printerName();
0613     }
0614 
0615     return QStringList();
0616 }
0617 
0618 QStringList PrintPortal::copies(const QPrinter *printer, const QString &version)
0619 {
0620     int cp = printer->copyCount();
0621 
0622     if (version == QLatin1String("lp")) {
0623         return QStringList(QStringLiteral("-n")) << QStringLiteral("%1").arg(cp);
0624     }
0625 
0626     if (version.startsWith(QLatin1String("lpr"))) {
0627         return QStringList() << QStringLiteral("-#%1").arg(cp);
0628     }
0629 
0630     return QStringList();
0631 }
0632 
0633 QStringList PrintPortal::jobname(const QPrinter *printer, const QString &version)
0634 {
0635     if (!printer->docName().isEmpty()) {
0636         if (version == QLatin1String("lp")) {
0637             return QStringList(QStringLiteral("-t")) << printer->docName();
0638         }
0639 
0640         if (version.startsWith(QLatin1String("lpr"))) {
0641             const QString shortenedDocName = QString::fromUtf8(printer->docName().toUtf8().left(255));
0642             return QStringList(QStringLiteral("-J")) << shortenedDocName;
0643         }
0644     }
0645 
0646     return QStringList();
0647 }
0648 
0649 // What about Upper and MultiPurpose?  And others in PPD???
0650 QString PrintPortal::mediaPaperSource(const QPrinter *printer)
0651 {
0652     switch (printer->paperSource()) {
0653     case QPrinter::Auto:
0654         return QString();
0655     case QPrinter::Cassette:
0656         return QStringLiteral("Cassette");
0657     case QPrinter::Envelope:
0658         return QStringLiteral("Envelope");
0659     case QPrinter::EnvelopeManual:
0660         return QStringLiteral("EnvelopeManual");
0661     case QPrinter::FormSource:
0662         return QStringLiteral("FormSource");
0663     case QPrinter::LargeCapacity:
0664         return QStringLiteral("LargeCapacity");
0665     case QPrinter::LargeFormat:
0666         return QStringLiteral("LargeFormat");
0667     case QPrinter::Lower:
0668         return QStringLiteral("Lower");
0669     case QPrinter::MaxPageSource:
0670         return QStringLiteral("MaxPageSource");
0671     case QPrinter::Middle:
0672         return QStringLiteral("Middle");
0673     case QPrinter::Manual:
0674         return QStringLiteral("Manual");
0675     case QPrinter::OnlyOne:
0676         return QStringLiteral("OnlyOne");
0677     case QPrinter::Tractor:
0678         return QStringLiteral("Tractor");
0679     case QPrinter::SmallFormat:
0680         return QStringLiteral("SmallFormat");
0681     default:
0682         return QString();
0683     }
0684 }
0685 
0686 QStringList PrintPortal::optionOrientation(const QPrinter *printer, QPageLayout::Orientation documentOrientation)
0687 {
0688     // portrait and landscape options rotate the document according to the document orientation
0689     // If we want to print a landscape document as one would expect it, we have to pass the
0690     // portrait option so that the document is not rotated additionally
0691     if (printer->pageLayout().orientation() == documentOrientation) {
0692         // the user wants the document printed as is
0693         return QStringList(QStringLiteral("-o")) << QStringLiteral("portrait");
0694     } else {
0695         // the user expects the document being rotated by 90 degrees
0696         return QStringList(QStringLiteral("-o")) << QStringLiteral("landscape");
0697     }
0698 }
0699 
0700 QStringList PrintPortal::optionDoubleSidedPrinting(const QPrinter *printer)
0701 {
0702     switch (printer->duplex()) {
0703     case QPrinter::DuplexNone:
0704         return QStringList(QStringLiteral("-o")) << QStringLiteral("sides=one-sided");
0705     case QPrinter::DuplexAuto:
0706         if (printer->pageLayout().orientation() == QPageLayout::Landscape) {
0707             return QStringList(QStringLiteral("-o")) << QStringLiteral("sides=two-sided-short-edge");
0708         } else {
0709             return QStringList(QStringLiteral("-o")) << QStringLiteral("sides=two-sided-long-edge");
0710         }
0711     case QPrinter::DuplexLongSide:
0712         return QStringList(QStringLiteral("-o")) << QStringLiteral("sides=two-sided-long-edge");
0713     case QPrinter::DuplexShortSide:
0714         return QStringList(QStringLiteral("-o")) << QStringLiteral("sides=two-sided-short-edge");
0715     default:
0716         return QStringList(); // Use printer default
0717     }
0718 }
0719 
0720 QStringList PrintPortal::optionPageOrder(const QPrinter *printer)
0721 {
0722     if (printer->pageOrder() == QPrinter::LastPageFirst) {
0723         return QStringList(QStringLiteral("-o")) << QStringLiteral("outputorder=reverse");
0724     }
0725     return QStringList(QStringLiteral("-o")) << QStringLiteral("outputorder=normal");
0726 }
0727 
0728 QStringList PrintPortal::optionCollateCopies(const QPrinter *printer)
0729 {
0730     if (printer->collateCopies()) {
0731         return QStringList(QStringLiteral("-o")) << QStringLiteral("Collate=True");
0732     }
0733     return QStringList(QStringLiteral("-o")) << QStringLiteral("Collate=False");
0734 }
0735 
0736 QStringList PrintPortal::optionPageMargins(const QPrinter *printer)
0737 {
0738     if (printer->printEngine()->property(QPrintEngine::PPK_PageMargins).isNull()) {
0739         return QStringList();
0740     } else {
0741         qreal l, t, r, b;
0742         QMarginsF m = printer->pageLayout().margins();
0743         l = m.left();
0744         t = m.top();
0745         r = m.right();
0746         b = m.bottom();
0747         return QStringList{
0748             QStringLiteral("-o"),
0749             QStringLiteral("page-left=%1").arg(l),
0750             QStringLiteral("-o"),
0751             QStringLiteral("page-top=%1").arg(t),
0752             QStringLiteral("-o"),
0753             QStringLiteral("page-right=%1").arg(r),
0754             QStringLiteral("-o"),
0755             QStringLiteral("page-bottom=%1").arg(b),
0756             QStringLiteral("-o"),
0757             QStringLiteral("fit-to-page"),
0758         };
0759     }
0760 }
0761 
0762 QStringList PrintPortal::optionCupsProperties(const QPrinter *printer)
0763 {
0764     QStringList dialogOptions = printer->printEngine()->property(QPrintEngine::PrintEnginePropertyKey(0xfe00)).toStringList();
0765     QStringList cupsOptions;
0766 
0767     for (int i = 0; i < dialogOptions.count(); i = i + 2) {
0768         // Ignore some cups properties as the pdf we get is already formatted using these
0769         if (dialogOptions[i] == QLatin1String("number-up") || dialogOptions[i] == QLatin1String("number-up-layout")) {
0770             continue;
0771         }
0772 
0773         if (dialogOptions[i + 1].isEmpty()) {
0774             cupsOptions << QStringLiteral("-o") << dialogOptions[i];
0775         } else {
0776             cupsOptions << QStringLiteral("-o") << dialogOptions[i] + QLatin1Char('=') + dialogOptions[i + 1];
0777         }
0778     }
0779 
0780     return cupsOptions;
0781 }
0782 
0783 QStringList PrintPortal::optionMedia(const QPrinter *printer)
0784 {
0785     if (!qt_keyForPageSizeId(printer->pageLayout().pageSize().id()).isEmpty() && !mediaPaperSource(printer).isEmpty()) {
0786         return QStringList(QStringLiteral("-o"))
0787             << QStringLiteral("media=%1,%2").arg(qt_keyForPageSizeId(printer->pageLayout().pageSize().id()), mediaPaperSource(printer));
0788     }
0789 
0790     if (!qt_keyForPageSizeId(printer->pageLayout().pageSize().id()).isEmpty()) {
0791         return QStringList(QStringLiteral("-o")) << QStringLiteral("media=%1").arg(qt_keyForPageSizeId(printer->pageLayout().pageSize().id()));
0792     }
0793 
0794     if (!mediaPaperSource(printer).isEmpty()) {
0795         return QStringList(QStringLiteral("-o")) << QStringLiteral("media=%1").arg(mediaPaperSource(printer));
0796     }
0797 
0798     return QStringList();
0799 }
0800 
0801 QStringList PrintPortal::pages(const QPrinter *printer, bool useCupsOptions, const QString &version)
0802 {
0803     if (printer->printRange() == QPrinter::PageRange) {
0804         if (version == QLatin1String("lp")) {
0805             return QStringList(QStringLiteral("-P")) << QStringLiteral("%1-%2").arg(printer->fromPage()).arg(printer->toPage());
0806         }
0807 
0808         if (version.startsWith(QLatin1String("lpr")) && useCupsOptions) {
0809             return QStringList(QStringLiteral("-o")) << QStringLiteral("page-ranges=%1-%2").arg(printer->fromPage()).arg(printer->toPage());
0810         }
0811     }
0812 
0813     return QStringList(); // AllPages
0814 }
0815 
0816 QStringList PrintPortal::cupsOptions(const QPrinter *printer, QPageLayout::Orientation documentOrientation)
0817 {
0818     Q_UNUSED(documentOrientation)
0819     QStringList optionList;
0820 
0821     // if (!optionMedia(printer).isEmpty()) {
0822     //     optionList << optionMedia(printer);
0823     // }
0824 
0825     // if (!optionOrientation(printer, documentOrientation).isEmpty()) {
0826     //     optionList << optionOrientation(printer, documentOrientation);
0827     // }
0828 
0829     if (!optionDoubleSidedPrinting(printer).isEmpty()) {
0830         optionList << optionDoubleSidedPrinting(printer);
0831     }
0832 
0833     if (!optionPageOrder(printer).isEmpty()) {
0834         optionList << optionPageOrder(printer);
0835     }
0836 
0837     if (!optionCollateCopies(printer).isEmpty()) {
0838         optionList << optionCollateCopies(printer);
0839     }
0840 
0841     // if (!optionPageMargins(printer).isEmpty()) {
0842     //     optionList << optionPageMargins(printer);
0843     // }
0844 
0845     optionList << optionCupsProperties(printer);
0846 
0847     return optionList;
0848 }
0849 
0850 QStringList PrintPortal::printArguments(const QPrinter *printer, bool useCupsOptions, const QString &version, QPageLayout::Orientation documentOrientation)
0851 {
0852     QStringList argList;
0853 
0854     if (!destination(printer, version).isEmpty()) {
0855         argList << destination(printer, version);
0856     }
0857 
0858     if (!copies(printer, version).isEmpty()) {
0859         argList << copies(printer, version);
0860     }
0861 
0862     if (!jobname(printer, version).isEmpty()) {
0863         argList << jobname(printer, version);
0864     }
0865 
0866     // if (!pages(printer, useCupsOptions, version).isEmpty()) {
0867     //     argList << pages(printer, useCupsOptions, version);
0868     // }
0869 
0870     if (useCupsOptions && !cupsOptions(printer, documentOrientation).isEmpty()) {
0871         argList << cupsOptions(printer, documentOrientation);
0872     }
0873 
0874     if (version == QLatin1String("lp")) {
0875         argList << QStringLiteral("--");
0876     }
0877 
0878     return argList;
0879 }
0880 
0881 bool PrintPortal::cupsAvailable()
0882 {
0883     // Ideally we would have access to the private Qt method
0884     // QCUPSSupport::cupsAvailable() to do this as it is very complex routine.
0885     // However, if CUPS is available then QPrinter::numCopies() will always return 1
0886     // whereas if CUPS is not available it will return the real number of copies.
0887     // This behaviour is guaranteed never to change, so we can use it as a reliable substitute.
0888     QPrinter testPrinter;
0889     return testPrinter.supportsMultipleCopies();
0890 }