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"