File indexing completed on 2025-01-19 05:06:23

0001 /**
0002  * SPDX-FileCopyrightText: 2010-2018 Daniel Nicoletti <dantti12@gmail.com>
0003  * SPDX-FileCopyrightText: 2022 Nicolas Fella <nicolas.fella@gmx.de>
0004  * SPDX-FileCopyrightText: 2023 Mike Noe <noeerover@gmail.com>
0005  * SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "printermanager.h"
0009 #include "pmkcm_log.h"
0010 
0011 #include <QDBusConnection>
0012 #include <QDBusPendingCallWatcher>
0013 #include <QDBusPendingReply>
0014 #include <QStringDecoder>
0015 
0016 #include <KLocalizedString>
0017 
0018 #include <KCupsRequest.h>
0019 #include <PPDModel.h>
0020 #include <cups/adminutil.h>
0021 #include <cups/ppd.h>
0022 
0023 using namespace Qt::StringLiterals;
0024 
0025 K_PLUGIN_CLASS_WITH_JSON(PrinterManager, "kcm_printer_manager.json")
0026 
0027 QDBusArgument &operator<<(QDBusArgument &argument, const DriverMatch &driverMatch)
0028 {
0029     argument.beginStructure();
0030     argument << driverMatch.ppd << driverMatch.match;
0031     argument.endStructure();
0032     return argument;
0033 }
0034 
0035 const QDBusArgument &operator>>(const QDBusArgument &argument, DriverMatch &driverMatch)
0036 {
0037     argument.beginStructure();
0038     argument >> driverMatch.ppd >> driverMatch.match;
0039     argument.endStructure();
0040     return argument;
0041 }
0042 
0043 PrinterManager::PrinterManager(QObject *parent, const KPluginMetaData &metaData)
0044     : KQuickConfigModule(parent, metaData)
0045     , m_serverSettings({{QLatin1String(CUPS_SERVER_USER_CANCEL_ANY), false},
0046                         {QLatin1String(CUPS_SERVER_SHARE_PRINTERS), false},
0047                         {QLatin1String(CUPS_SERVER_REMOTE_ANY), false},
0048                         {QLatin1String(CUPS_SERVER_REMOTE_ADMIN), false}})
0049 {
0050     setButtons(KQuickConfigModule::NoAdditionalButton);
0051 
0052     // Make sure we update our server settings if the user changes anything on
0053     // another interface
0054     connect(KCupsConnection::global(), &KCupsConnection::serverAudit, this, [this](const QString &msg) {
0055         serverEvent(u"AUDIT"_s, false, msg);
0056     });
0057     connect(KCupsConnection::global(), &KCupsConnection::serverStarted, this, [this](const QString &msg) {
0058         serverEvent(u"STARTED"_s, true, msg);
0059     });
0060     connect(KCupsConnection::global(), &KCupsConnection::serverStopped, this, [this](const QString &msg) {
0061         serverEvent(u"STOPPED"_s, false, msg);
0062     });
0063     connect(KCupsConnection::global(), &KCupsConnection::serverRestarted, this, [this](const QString &msg) {
0064         serverEvent(u"RESTARTED"_s, true, msg);
0065     });
0066 
0067     qmlRegisterUncreatableMetaObject(PMTypes::staticMetaObject,
0068                                      "org.kde.plasma.printmanager", // use same namespace as kcupslib
0069                                      1,
0070                                      0,
0071                                      "PPDType", // QML qualifier
0072                                      u"Error: for only enums"_s);
0073 }
0074 
0075 void PrinterManager::serverEvent(const QString &event, bool reload, const QString &msg)
0076 {
0077     qCWarning(PMKCM) << "SERVER" << event << msg << reload;
0078     if (reload) {
0079         QTimer::singleShot(500, this, &PrinterManager::getServerSettings);
0080     } else {
0081         m_serverSettingsLoaded = false;
0082     }
0083 }
0084 
0085 void PrinterManager::getRemotePrinters(const QString &uri, const QString &uriScheme)
0086 {
0087     QUrl url(QUrl::fromUserInput(uri));
0088     if (url.host().isEmpty() && !uri.contains(u"://"_s)) {
0089         url = QUrl();
0090         // URI might be scsi, network or anything that didn't match before
0091         if (uriScheme != u"other"_s) {
0092             url.setScheme(uriScheme);
0093         }
0094         url.setAuthority(uri);
0095     }
0096 
0097     qCWarning(PMKCM) << "Finding Printers for URL:" << url.toDisplayString();
0098 
0099     m_remotePrinters.clear();
0100     auto conn = new KCupsConnection(url, this);
0101     auto request = new KCupsRequest(conn);
0102 
0103     request->getPrinters({KCUPS_PRINTER_NAME,
0104                           KCUPS_PRINTER_STATE,
0105                           KCUPS_PRINTER_IS_SHARED,
0106                           KCUPS_PRINTER_IS_ACCEPTING_JOBS,
0107                           KCUPS_PRINTER_TYPE,
0108                           KCUPS_PRINTER_LOCATION,
0109                           KCUPS_PRINTER_INFO,
0110                           KCUPS_PRINTER_MAKE_AND_MODEL});
0111 
0112     request->waitTillFinished();
0113 
0114     if (request) {
0115         url.setScheme(u"ipp"_s);
0116         const auto printers = request->printers();
0117         if (request->hasError()) {
0118             Q_EMIT requestError(request->errorMsg());
0119         } else {
0120             for (const auto &p : printers) {
0121                 const auto mm = p.makeAndModel();
0122                 const auto make = mm.split(u" "_s).at(0);
0123                 m_remotePrinters.append(QVariantMap({{KCUPS_DEVICE_URI, QVariant::fromValue(QString(url.url() + u"/printers/"_s + p.name()))},
0124                                                      {u"printer-is-class"_s, p.isClass()},
0125                                                      {u"iconName"_s, p.iconName()},
0126                                                      {u"remote"_s, QVariant::fromValue<bool>(p.type() & CUPS_PRINTER_REMOTE)},
0127                                                      {KCUPS_PRINTER_NAME, p.name()},
0128                                                      {KCUPS_PRINTER_STATE, p.state()},
0129                                                      {KCUPS_PRINTER_IS_SHARED, p.isShared()},
0130                                                      {KCUPS_PRINTER_IS_ACCEPTING_JOBS, p.isAcceptingJobs()},
0131                                                      {KCUPS_PRINTER_TYPE, p.type()},
0132                                                      {KCUPS_PRINTER_LOCATION, p.location()},
0133                                                      {KCUPS_PRINTER_INFO, p.info()},
0134                                                      {u"printer-make"_s, make},
0135                                                      {KCUPS_PRINTER_MAKE_AND_MODEL, mm}}));
0136             }
0137         }
0138 
0139         Q_EMIT remotePrintersLoaded();
0140         request->deleteLater();
0141     }
0142 
0143     conn->deleteLater();
0144 }
0145 
0146 void PrinterManager::clearRemotePrinters()
0147 {
0148     m_remotePrinters.clear();
0149 }
0150 
0151 void PrinterManager::clearRecommendedDrivers()
0152 {
0153     m_recommendedDrivers.clear();
0154 }
0155 
0156 void PrinterManager::getDriversFinished(const QDBusMessage &message)
0157 {
0158     if (message.type() == QDBusMessage::ReplyMessage && message.arguments().size() == 1) {
0159         const auto args = message.arguments();
0160         const auto arg = args.first().value<QDBusArgument>();
0161         const DriverMatchList driverMatchList = qdbus_cast<DriverMatchList>(arg);
0162         for (const DriverMatch &driverMatch : driverMatchList) {
0163             if (driverMatch.match == u"none"_s) {
0164                 continue;
0165             }
0166 
0167             m_recommendedDrivers.append(QVariantMap({{u"match"_s, driverMatch.match}, {u"ppd-name"_s, driverMatch.ppd}, {u"ppd-type"_s, PMTypes::Auto}}));
0168         }
0169     } else {
0170         qCWarning(PMKCM) << "Unexpected message" << message;
0171     }
0172     Q_EMIT recommendedDriversLoaded();
0173 }
0174 
0175 void PrinterManager::getDriversFailed(const QDBusError &error, const QDBusMessage &message)
0176 {
0177     qCWarning(PMKCM) << "Failed to get best drivers" << error << message;
0178     Q_EMIT recommendedDriversLoaded();
0179 }
0180 
0181 void PrinterManager::savePrinter(const QString &name, const QVariantMap &saveArgs, bool isClass)
0182 {
0183     QVariantMap args = saveArgs;
0184     QString fileName;
0185 
0186     if (args.contains(u"ppd-type"_s)) {
0187         const auto ppdType = args.take(u"ppd-type"_s).toInt();
0188         if (ppdType == PMTypes::Manual) {
0189             // local file, remove key, use filename
0190             fileName = args.take(u"ppd-name"_s).toString();
0191         }
0192     }
0193 
0194     bool isDefault = args.take(u"isDefault"_s).toBool();
0195     bool autoConfig = args.take(u"autoConfig"_s).toBool();
0196     // add mode
0197     if (args.take(u"add"_s).toBool()) {
0198         args[KCUPS_PRINTER_STATE] = IPP_PRINTER_IDLE;
0199     }
0200 
0201     qCWarning(PMKCM) << "Saving printer settings" << Qt::endl << name << fileName << Qt::endl << args;
0202 
0203     QPointer<KCupsRequest> request = new KCupsRequest;
0204     if (isClass) {
0205         // Member list is a QVariantList, kcupslib wants to see
0206         // a QStringList
0207         const auto list = args.take(KCUPS_MEMBER_URIS);
0208         if (!list.value<QVariantList>().empty()) {
0209             args.insert(KCUPS_MEMBER_URIS, list.toStringList());
0210         }
0211         request->addOrModifyClass(name, args);
0212     } else {
0213         request->addOrModifyPrinter(name, args, fileName);
0214     }
0215     request->waitTillFinished();
0216 
0217     // Printer isDefault is exclusive
0218     if (isDefault) {
0219         request->setDefaultPrinter(name);
0220         request->waitTillFinished();
0221     }
0222     if (autoConfig) {
0223         request->printCommand(name, u"AutoConfigure"_s, i18n("Set Default Options"));
0224         request->waitTillFinished();
0225     }
0226 
0227     if (request) {
0228         if (!request->hasError()) {
0229             Q_EMIT saveDone();
0230         } else {
0231             Q_EMIT requestError((isClass ? i18nc("@info", "Failed to configure class: ") : i18nc("@info", "Failed to configure printer: "))
0232                                 + request->errorMsg());
0233             qCWarning(PMKCM) << "Failed to save printer/class" << name << request->errorMsg();
0234         }
0235         request->deleteLater();
0236     }
0237 }
0238 
0239 QVariantMap PrinterManager::getPrinterPPD(const QString &name)
0240 {
0241     QPointer<KCupsRequest> request = new KCupsRequest;
0242     request->getPrinterPPD(name);
0243     request->waitTillFinished();
0244     if (!request) {
0245         return {};
0246     }
0247 
0248     const auto filename = request->printerPPD();
0249     const auto err = request->errorMsg();
0250     request->deleteLater();
0251 
0252     ppd_file_t *ppd = nullptr;
0253     if (!filename.isEmpty()) {
0254         ppd = ppdOpenFile(qUtf8Printable(filename));
0255         unlink(qUtf8Printable(filename));
0256     }
0257 
0258     if (ppd == nullptr) {
0259         qCWarning(PMKCM) << "Could not open ppd file:" << filename << err;
0260         return {};
0261     }
0262 
0263     ppdLocalize(ppd);
0264     // select the default options on the ppd file
0265     ppdMarkDefaults(ppd);
0266 
0267     const char *lang_encoding;
0268     lang_encoding = ppd->lang_encoding;
0269     QStringDecoder codec;
0270 
0271     if (lang_encoding && !strcasecmp(lang_encoding, "UTF-8")) {
0272         codec = QStringDecoder(QStringDecoder::Utf8);
0273     } else if (lang_encoding && !strcasecmp(lang_encoding, "ISOLatin1")) {
0274         codec = QStringDecoder(QStringDecoder::Latin1);
0275     } else if (lang_encoding && !strcasecmp(lang_encoding, "ISOLatin2")) {
0276         codec = QStringDecoder("ISO-8859-2");
0277     } else if (lang_encoding && !strcasecmp(lang_encoding, "ISOLatin5")) {
0278         codec = QStringDecoder("ISO-8859-5");
0279     } else if (lang_encoding && !strcasecmp(lang_encoding, "JIS83-RKSJ")) {
0280         codec = QStringDecoder("SHIFT-JIS");
0281     } else if (lang_encoding && !strcasecmp(lang_encoding, "MacStandard")) {
0282         codec = QStringDecoder("MACINTOSH");
0283     } else if (lang_encoding && !strcasecmp(lang_encoding, "WindowsANSI")) {
0284         codec = QStringDecoder("WINDOWS-1252");
0285     } else {
0286         qCWarning(PMKCM) << "Unknown ENCODING:" << lang_encoding;
0287         codec = QStringDecoder(lang_encoding);
0288     }
0289     // Fallback
0290     if (!codec.isValid()) {
0291         codec = QStringDecoder(QStringDecoder::Utf8);
0292     }
0293 
0294     qCWarning(PMKCM) << codec(ppd->pcfilename) << codec(ppd->modelname) << codec(ppd->shortnickname);
0295 
0296     QString make, makeAndModel, file;
0297     if (ppd->manufacturer) {
0298         make = codec(ppd->manufacturer);
0299     }
0300 
0301     if (ppd->nickname) {
0302         makeAndModel = codec(ppd->nickname);
0303     }
0304 
0305     if (ppd->pcfilename) {
0306         file = codec(ppd->pcfilename);
0307     }
0308 
0309     ppd_attr_t *ppdattr;
0310     bool autoConfig = false;
0311     if (ppd->num_filters == 0
0312         || ((ppdattr = ppdFindAttr(ppd, "cupsCommands", nullptr)) != nullptr && ppdattr->value && strstr(ppdattr->value, "AutoConfigure"))) {
0313         autoConfig = true;
0314     } else {
0315         for (int i = 0; i < ppd->num_filters; ++i) {
0316             if (!strncmp(ppd->filters[i], "application/vnd.cups-postscript", 31)) {
0317                 autoConfig = true;
0318                 break;
0319             }
0320         }
0321     }
0322 
0323     return {{u"autoConfig"_s, autoConfig},
0324             {u"file"_s, QString()},
0325             {u"pcfile"_s, file},
0326             {u"type"_s, QVariant(PMTypes::PPDType::Custom)},
0327             {u"make"_s, make},
0328             {u"makeModel"_s, makeAndModel}};
0329 }
0330 
0331 void PrinterManager::pausePrinter(const QString &name)
0332 {
0333     const auto request = setupRequest();
0334     request->pausePrinter(name);
0335 }
0336 
0337 void PrinterManager::resumePrinter(const QString &name)
0338 {
0339     const auto request = setupRequest();
0340     request->resumePrinter(name);
0341 }
0342 
0343 void PrinterManager::getRecommendedDrivers(const QString &deviceId, const QString &makeAndModel, const QString &deviceUri)
0344 {
0345     qCDebug(PMKCM) << deviceId << makeAndModel << deviceUri;
0346 
0347     m_recommendedDrivers.clear();
0348     QDBusMessage message;
0349     message = QDBusMessage::createMethodCall(u"org.fedoraproject.Config.Printing"_s,
0350                                              u"/org/fedoraproject/Config/Printing"_s,
0351                                              u"org.fedoraproject.Config.Printing"_s,
0352                                              u"GetBestDrivers"_s);
0353     message << deviceId;
0354     message << makeAndModel;
0355     message << deviceUri;
0356     QDBusConnection::sessionBus().callWithCallback(message, this, SLOT(getDriversFinished(QDBusMessage)), SLOT(getDriversFailed(QDBusError, QDBusMessage)));
0357 }
0358 
0359 KCupsRequest *PrinterManager::setupRequest(std::function<void()> finished)
0360 {
0361     auto request = new KCupsRequest;
0362     connect(request, &KCupsRequest::finished, this, [this, finished](KCupsRequest *r) {
0363         if (r->hasError()) {
0364             Q_EMIT requestError(i18n("Failed to perform request: %1", r->errorMsg()));
0365         } else {
0366             finished();
0367         }
0368         r->deleteLater();
0369     });
0370 
0371     return request;
0372 }
0373 
0374 QVariantList PrinterManager::remotePrinters() const
0375 {
0376     return m_remotePrinters;
0377 }
0378 
0379 QVariantList PrinterManager::recommendedDrivers() const
0380 {
0381     return m_recommendedDrivers;
0382 }
0383 
0384 QVariantMap PrinterManager::serverSettings() const
0385 {
0386     return m_serverSettings;
0387 }
0388 
0389 bool PrinterManager::serverSettingsLoaded() const
0390 {
0391     return m_serverSettingsLoaded;
0392 }
0393 
0394 void PrinterManager::removePrinter(const QString &name)
0395 {
0396     const auto request = setupRequest([this]() -> void {
0397         Q_EMIT removeDone();
0398     });
0399     request->deletePrinter(name);
0400 }
0401 
0402 void PrinterManager::makePrinterDefault(const QString &name)
0403 {
0404     const auto request = setupRequest();
0405     request->setDefaultPrinter(name);
0406 }
0407 
0408 void PrinterManager::getServerSettings()
0409 {
0410     const auto request = new KCupsRequest();
0411     connect(request, &KCupsRequest::finished, this, [this](KCupsRequest *r) {
0412         // When we don't have any destinations, error is set to IPP_NOT_FOUND
0413         // we can safely ignore the error since it DOES bring the server
0414         // settings
0415         if (r->hasError() && r->error() != IPP_NOT_FOUND) {
0416             Q_EMIT requestError(i18nc("@info", "Failed to get server settings: %1", r->errorMsg()));
0417             m_serverSettingsLoaded = false;
0418         } else {
0419             KCupsServer server = r->serverSettings();
0420             m_serverSettings[QLatin1String(CUPS_SERVER_USER_CANCEL_ANY)] = server.allowUserCancelAnyJobs();
0421             m_serverSettings[QLatin1String(CUPS_SERVER_SHARE_PRINTERS)] = server.sharePrinters();
0422             m_serverSettings[QLatin1String(CUPS_SERVER_REMOTE_ANY)] = server.allowPrintingFromInternet();
0423             m_serverSettings[QLatin1String(CUPS_SERVER_REMOTE_ADMIN)] = server.allowRemoteAdmin();
0424 
0425             m_serverSettingsLoaded = true;
0426             Q_EMIT serverSettingsChanged();
0427         }
0428         r->deleteLater();
0429     });
0430 
0431     request->getServerSettings();
0432 }
0433 
0434 void PrinterManager::saveServerSettings(const QVariantMap &settings)
0435 {
0436     KCupsServer server;
0437     server.setSharePrinters(settings.value(QLatin1String(CUPS_SERVER_SHARE_PRINTERS), false).toBool());
0438     server.setAllowUserCancelAnyJobs(settings.value(QLatin1String(CUPS_SERVER_USER_CANCEL_ANY), false).toBool());
0439     server.setAllowRemoteAdmin(settings.value(QLatin1String(CUPS_SERVER_REMOTE_ADMIN), false).toBool());
0440     server.setAllowPrintingFromInternet(settings.value(QLatin1String(CUPS_SERVER_REMOTE_ANY), false).toBool());
0441 
0442     auto request = new KCupsRequest;
0443     request->setServerSettings(server);
0444     request->waitTillFinished();
0445 
0446     if (request->hasError()) {
0447         if (request->error() == IPP_SERVICE_UNAVAILABLE || request->error() == IPP_INTERNAL_ERROR || request->error() == IPP_AUTHENTICATION_CANCELED) {
0448             qCWarning(PMKCM) << "Server error:" << request->serverError() << request->errorMsg();
0449             // Server is restarting, or auth was canceled, update the settings in one second
0450             //            QTimer::singleShot(1000, this, &PrinterManager::getServerSettings);
0451         } else {
0452             // Force the settings to be retrieved again
0453             getServerSettings();
0454             Q_EMIT requestError(i18nc("@info", "Server error: (%1): %2", request->serverError(), request->errorMsg()));
0455         }
0456     } else {
0457         qCWarning(PMKCM) << "SERVER SETTINGS SET!" << settings;
0458         m_serverSettings = settings;
0459     }
0460     request->deleteLater();
0461 }
0462 
0463 bool PrinterManager::shareConnectedPrinters() const
0464 {
0465     return m_serverSettings.value(QLatin1String(CUPS_SERVER_SHARE_PRINTERS), false).toBool();
0466 }
0467 
0468 bool PrinterManager::allowPrintingFromInternet() const
0469 {
0470     return m_serverSettings.value(QLatin1String(CUPS_SERVER_REMOTE_ANY), false).toBool();
0471 }
0472 
0473 bool PrinterManager::allowRemoteAdmin() const
0474 {
0475     return m_serverSettings.value(QLatin1String(CUPS_SERVER_REMOTE_ADMIN), false).toBool();
0476 }
0477 
0478 bool PrinterManager::allowUserCancelAnyJobs() const
0479 {
0480     return m_serverSettings.value(QLatin1String(CUPS_SERVER_USER_CANCEL_ANY), false).toBool();
0481 }
0482 
0483 void PrinterManager::makePrinterShared(const QString &name, bool shared, bool isClass)
0484 {
0485     const auto request = setupRequest();
0486     request->setShared(name, isClass, shared);
0487 }
0488 
0489 void PrinterManager::makePrinterRejectJobs(const QString &name, bool reject)
0490 {
0491     const auto request = setupRequest();
0492 
0493     if (reject) {
0494         request->rejectJobs(name);
0495     } else {
0496         request->acceptJobs(name);
0497     }
0498 }
0499 
0500 void PrinterManager::printTestPage(const QString &name, bool isClass)
0501 {
0502     const auto request = setupRequest();
0503     request->printTestPage(name, isClass);
0504 }
0505 
0506 void PrinterManager::printSelfTestPage(const QString &name)
0507 {
0508     const auto request = setupRequest();
0509     request->printCommand(name, u"PrintSelfTestPage"_s, i18n("Print Self-Test Page"));
0510 }
0511 
0512 void PrinterManager::cleanPrintHeads(const QString &name)
0513 {
0514     const auto request = setupRequest();
0515     request->printCommand(name, u"Clean all"_s, i18n("Clean Print Heads"));
0516 }
0517 
0518 #include "printermanager.moc"