[plasma/print-manager] src: libkcups/kded: Consolidate marker level helpers into KCupsPrinter
Mike Noe <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit c9aad2cacd0eb7baced440291bbb4bcb6ab8eca7 by Mike Noe.
Committed on 20/07/2026 at 13:50.
Pushed by noee into branch 'master'.
libkcups/kded: Consolidate marker level helpers into KCupsPrinter
And simplify the consumables checker logic
- Add markers* getters/helpers
- Use KCupsPrinter::marker-* helpers from kded
- Check negative level earlier
- Remove not-needed div-by-zero check and ratio calculation
- Compare level to CUPS-reported low value directly
- Clean up and expand debug prints a bit
M +38 -204 src/kded/MarkerLevelChecker.cpp
M +2 -1 src/kded/MarkerLevelChecker.h
M +230 -28 src/libkcups/KCupsPrinter.cpp
M +30 -4 src/libkcups/KCupsPrinter.h
https://invent.kde.org/plasma/print-manager/-/commit/c9aad2cacd0eb7baced440291bbb4bcb6ab8eca7
diff --git a/src/kded/MarkerLevelChecker.cpp b/src/kded/MarkerLevelChecker.cpp
index 3d9095fa..45405571 100644
--- a/src/kded/MarkerLevelChecker.cpp
+++ b/src/kded/MarkerLevelChecker.cpp
@@ -33,8 +33,38 @@ MarkerLevelChecker::MarkerLevelChecker(QObject *parent)
connect(KCupsConnection::global(), &KCupsConnection::jobCreated, this, &MarkerLevelChecker::jobHandler);
}
-void MarkerLevelChecker::checkMarkerLevels(const QString &printerName)
+void MarkerLevelChecker::checkMarkerLevels(const KCupsPrinter &printer)
{
+ const auto msgs = printer.checkMarkerLevels();
+
+ if (!msgs.isEmpty()) {
+ auto notify = new KNotification(u"MarkerLevel"_s, KNotification::Persistent);
+ notify->setComponentName(u"printmanager"_s);
+ notify->setTitle(printer.info());
+ notify->setText(msgs.join(u"\n"_s));
+
+ auto checkMarkers = notify->addDefaultAction(i18nc("@action:button check printer ink levels", "Check Levels…"));
+ connect(checkMarkers, &KNotificationAction::activated, this, [pn = printer.name(), token = notify->xdgActivationToken().toUtf8()]() {
+ ProcessRunner::kcmConfigurePrinter(pn, token);
+ });
+
+ notify->sendEvent();
+ }
+}
+
+void MarkerLevelChecker::jobHandler([[maybe_unused]] const QString &text,
+ [[maybe_unused]] const QString &printerUri,
+ [[maybe_unused]] const QString &printerName,
+ [[maybe_unused]] uint printerState,
+ [[maybe_unused]] const QString &printerStateReasons,
+ [[maybe_unused]] bool printerIsAcceptingJobs,
+ [[maybe_unused]] uint jobId,
+ [[maybe_unused]] uint jobState,
+ [[maybe_unused]] const QString &jobStateReasons,
+ [[maybe_unused]] const QString &jobName,
+ [[maybe_unused]] uint jobImpressionsCompleted)
+{
+ qCDebug(PMKDED) << text << printerName << jobId << jobStateReasons;
static const QStringList s_attrs({KCUPS_MARKER_NAMES,
KCUPS_MARKER_LEVELS,
KCUPS_MARKER_HIGH_LEVELS,
@@ -43,215 +73,19 @@ void MarkerLevelChecker::checkMarkerLevels(const QString &printerName)
KCUPS_PRINTER_INFO,
KCUPS_PRINTER_TYPE});
- /**
- * Marker low level indicates the near empty warning value, but it can be 0.
- * Let's be more conservative and warn if actual level is at a higher pct
- * https://openprinting.github.io/cups/doc/spec-ipp.html#marker-low-levels
- */
- static const int s_threshold = 3;
-
- qCDebug(PMKDED) << "checkMarkerLevels: getting printer attributes" << printerName;
-
const auto request = new KCupsRequest;
connect(request, &KCupsRequest::finished, this, [this](KCupsRequest *req) {
- // let's make sure to not leave dangling requests
- auto cleanupReq = qScopeGuard([req] {
- req->deleteLater();
- });
-
- // it's possible attributes from a temporary queue could be empty
- if (req->printers().isEmpty()) {
- qCDebug(PMKDED) << "No printers found for marker level check";
- return;
- }
-
- const auto printer = req->printers().at(0);
-
- // QVariant::toList converts an int to a null list
- // So, create a QList<int> if only one entry (int)
- const auto getLevels = [printer](const QString &key) -> QList<QVariant> {
- QList<QVariant> list;
- const auto levels = printer.argument(key);
- if (levels.isValid()) {
- if (levels.canConvert<QList<int>>()) {
- list = levels.toList();
- qCDebug(PMKDED) << "Valid levels list" << key << list;
- } else {
- list = QList<QVariant>{levels};
- qCDebug(PMKDED) << "Valid level value set to a list" << key << list;
- }
- } else {
- // Valid levels entry not found
- qCDebug(PMKDED) << "Invalid levels entry:" << key;
- }
-
- return list;
- };
-
- const auto currentLevels = getLevels(KCUPS_MARKER_LEVELS);
- const auto lowLevels = getLevels(KCUPS_MARKER_LOW_LEVELS);
- const auto highLevels = getLevels(KCUPS_MARKER_HIGH_LEVELS);
- const auto typesList = printer.argument(KCUPS_MARKER_TYPES).toStringList();
- const auto namesList = printer.argument(KCUPS_MARKER_NAMES).toStringList();
-
- // All lists should have values for a valid marker check
- if (currentLevels.isEmpty() || highLevels.isEmpty() || lowLevels.isEmpty() || typesList.isEmpty() || namesList.isEmpty()) {
- qCWarning(PMKDED) << "At least one marker level attribute is empty, aborting level check" << currentLevels << lowLevels << highLevels << typesList
- << namesList;
- return;
- }
-
- // We address all lists by index so they had better be of uniform length.
- const auto levelCounts = {currentLevels.count(), lowLevels.count(), highLevels.count(), typesList.count(), namesList.count()};
- const auto levelsMaxCount = std::max(levelCounts);
- if (std::ranges::any_of(levelCounts, [levelsMaxCount](auto count) {
- return count != levelsMaxCount;
- })) {
- QString message;
- QDebug(&message) << "Marker level lists have different counts, aborting level check" << currentLevels << lowLevels << highLevels << typesList
- << namesList;
- qCWarning(PMKDED) << message;
- // This is a fairly serious problem we should inspect. Fail an assertion on it!
- // Can be safely demoted when we have some more confidence that we understand why the check may fail.
- Q_ASSERT_X(false, Q_FUNC_INFO, message.toUtf8().constData());
- return;
- }
-
- /** CUPS supports devices with both types of markers, consumables and receptacles
- *
- * https://openprinting.github.io/cups/doc/spec-ipp.html#marker-types
- *
- * Consumables are generally toners/cartridges
- * levels decrease from 100 => 0 (high to low)
- *
- * Receptacles are generally waste containters
- * levels increase from 0 => 100 (low to high)
- */
- QStringList msgs;
- constexpr int empty = 0;
- constexpr int full = 100;
- // For each marker
- for (uint i = 0; i < namesList.count(); ++i) {
- int levelIndex = -1;
- int checkerValue = 0;
-
- const auto level = currentLevels.at(i).toInt();
- const auto low = lowLevels.at(i).toInt();
- const auto high = highLevels.at(i).toInt();
- // per CUPS
- // low-level == 0 && high-level != 0 && high-level < 100 => waste receptacle
- // high-level == 100 && low-level != 100 && low-level >= 0 => consumable
- bool consumable = false;
- if (high == full && low != full && low >= empty) {
- consumable = true;
- qCDebug(PMKDED) << "Found consumable, checking" << typesList.at(i);
- } else if (low == empty && high != empty && high < full) {
- consumable = false;
- qCDebug(PMKDED) << "Found receptacle, checking" << typesList.at(i);
- } else {
- qCDebug(PMKDED) << "low/high range invalid (" << low << "," << high << "), failure to determine marker type:" << typesList.at(i);
- continue;
- }
-
- if (consumable) {
- // Because printers, level can be 0 and low boundary can be > zero
- // Also, level can be < low and not zero, ie. low=2, level=1
- // level < 0 is unknown or unavailable, so ignore
- if (level == empty || (level <= low && level > empty)) {
- levelIndex = i;
- if (level <= low) {
- checkerValue = level;
- }
- } else {
- // CUPS seems to handle this, but just in case don't divide by zero
- if (high == empty) {
- qCWarning(PMKDED) << "Found a high boundary == 0";
- break;
- }
- if (level > low && level <= high) {
- auto result = div((level - low) * 100, high);
- if (result.quot <= s_threshold) {
- levelIndex = i;
- checkerValue = level;
- }
- }
- }
- // Receptacle
- } else {
- if (level >= high) {
- levelIndex = i;
- checkerValue = level;
- }
- }
-
- // found a marker level at or below the threshold pct
- if (levelIndex >= 0) {
- const auto name = namesList.at(levelIndex);
- const auto type = typesList.at(levelIndex);
-
- if (consumable) {
- if (checkerValue == 0) {
- msgs << i18nc("@info:usagetip The name and type of the ink cartridge", "%1 (%2) appears to be empty.", name, type);
- } else {
- msgs << i18nc("@info:usagetip The name and type of the ink cartridge and percent ink remaining",
- "%1 (%2) appears to be low (%3% remaining).",
- name,
- type,
- checkerValue);
- }
- } else {
- msgs << i18nc("@info:usagetip The name, type, and percentage full of the waste receptacle",
- "%1 (%2) is almost full (%3%)",
- name,
- type,
- checkerValue);
- }
- qCDebug(PMKDED) << "Found marker-level at threshold" << type << name << checkerValue;
- }
- }
-
- if (!msgs.isEmpty()) {
- auto notify = new KNotification(u"MarkerLevel"_s, KNotification::Persistent);
- notify->setComponentName(u"printmanager"_s);
- notify->setTitle(printer.info());
- notify->setText(msgs.join(u"\n"_s));
-
- auto checkMarkers = notify->addDefaultAction(i18nc("@action:button check printer ink levels", "Check Levels…"));
- connect(checkMarkers, &KNotificationAction::activated, this, [pn = printer.name()]() {
- ProcessRunner::kcmConfigurePrinter(pn);
- });
-
- notify->sendEvent();
+ if (!req->printers().isEmpty()) {
+ const auto printer = req->printers().at(0);
+ checkMarkerLevels(printer);
+ } else {
+ // it's possible attributes from a temporary queue could be empty
+ qCDebug(PMKDED) << "No printers found for checking";
}
+ req->deleteLater();
});
request->getPrinterAttributes(printerName, false, s_attrs);
}
-void MarkerLevelChecker::jobHandler(const QString &text,
- const QString &printerUri,
- const QString &printerName,
- uint printerState,
- const QString &printerStateReasons,
- bool printerIsAcceptingJobs,
- uint jobId,
- uint jobState,
- const QString &jobStateReasons,
- const QString &jobName,
- uint jobImpressionsCompleted)
-{
- Q_UNUSED(text)
- Q_UNUSED(printerUri)
- Q_UNUSED(printerState)
- Q_UNUSED(printerStateReasons)
- Q_UNUSED(printerIsAcceptingJobs)
- Q_UNUSED(jobId)
- Q_UNUSED(jobState)
- Q_UNUSED(jobStateReasons)
- Q_UNUSED(jobName)
- Q_UNUSED(jobImpressionsCompleted)
-
- checkMarkerLevels(printerName);
-}
-
#include "moc_MarkerLevelChecker.cpp"
diff --git a/src/kded/MarkerLevelChecker.h b/src/kded/MarkerLevelChecker.h
index 3d3ba41a..8f24653c 100644
--- a/src/kded/MarkerLevelChecker.h
+++ b/src/kded/MarkerLevelChecker.h
@@ -8,6 +8,7 @@
#include <QObject>
+class KCupsPrinter;
/**
* Check for at least one marker level being below the threshold.
*
@@ -27,7 +28,7 @@ public:
~MarkerLevelChecker() = default;
private:
- void checkMarkerLevels(const QString &printerName);
+ void checkMarkerLevels(const KCupsPrinter &printer);
/**
* @brief Generic handler for job signals
diff --git a/src/libkcups/KCupsPrinter.cpp b/src/libkcups/KCupsPrinter.cpp
index 1ef8f4bc..3705d1b5 100644
--- a/src/libkcups/KCupsPrinter.cpp
+++ b/src/libkcups/KCupsPrinter.cpp
@@ -1,10 +1,14 @@
/*
SPDX-FileCopyrightText: 2010-2018 Daniel Nicoletti <[email protected]>
+ SPDX-FileCopyrightText: 2026 Mike Noe <[email protected]>
SPDX-License-Identifier: GPL-2.0-or-later
*/
+#include "kcupslib_log.h"
+
#include "KCupsPrinter.h"
+#include <KLocalizedString>
KCupsPrinter::KCupsPrinter()
: m_isClass(false)
@@ -17,11 +21,154 @@ KCupsPrinter::KCupsPrinter(const QString &printer, bool isClass)
{
}
-KCupsPrinter::KCupsPrinter(const QVariantMap &arguments)
- : m_printer(arguments[QLatin1String(KCUPS_PRINTER_NAME)].toString())
- , m_isClass(arguments[QLatin1String(KCUPS_PRINTER_TYPE)].toInt() & CUPS_PRINTER_CLASS)
- , m_arguments(arguments)
+KCupsPrinter::KCupsPrinter(const QVariantMap &attributes)
+ : m_printer(attributes[KCUPS_PRINTER_NAME].toString())
+ , m_isClass(attributes[KCUPS_PRINTER_TYPE].toInt() & CUPS_PRINTER_CLASS)
+ , m_attributes(attributes)
+{
+}
+
+QStringList KCupsPrinter::checkMarkerLevels() const
{
+ const auto currentLevels = markerLevels();
+ const auto lowLevels = markerLowLevels();
+ const auto highLevels = markerHighLevels();
+ const auto typesList = markerTypes();
+ const auto namesList = markerNames();
+
+ // All lists should have values for a valid marker check
+ if (currentLevels.isEmpty() || highLevels.isEmpty() || lowLevels.isEmpty() || typesList.isEmpty() || namesList.isEmpty()) {
+ qCWarning(LIBKCUPS) << "At least one marker level attribute is empty, aborting level check" << currentLevels << lowLevels << highLevels << typesList
+ << namesList;
+ return {};
+ }
+
+ // We address all lists by index so they had better be of uniform length.
+ const auto levelCounts = {currentLevels.count(), lowLevels.count(), highLevels.count(), typesList.count(), namesList.count()};
+ const auto levelsMaxCount = std::max(levelCounts);
+ if (std::ranges::any_of(levelCounts, [levelsMaxCount](auto count) {
+ return count != levelsMaxCount;
+ })) {
+ QString message;
+ QDebug(&message) << "Marker level lists have different counts, aborting level check" << currentLevels << lowLevels << highLevels << typesList
+ << namesList;
+ qCWarning(LIBKCUPS) << message;
+ // This is a fairly serious problem we should inspect. Fail an assertion on it!
+ // Can be safely demoted when we have some more confidence that we understand why the check may fail.
+ Q_ASSERT_X(false, Q_FUNC_INFO, message.toUtf8().constData());
+ return {};
+ }
+
+ QStringList msgs;
+ constexpr int empty = 0;
+ constexpr int nearEmpty = 3;
+ constexpr int full = 100;
+ // For each marker
+ for (uint i = 0; i < namesList.count(); ++i) {
+ // Per OpenPrinting, level < 0 indicates unknown or unavailable
+ const auto level = currentLevels.at(i);
+ if (level < empty) {
+ qCDebug(LIBKCUPS, "Invalid level (%d), ignore checking: %s, %s", level, qPrintable(typesList.at(i)), qPrintable(namesList.at(i)));
+ continue;
+ }
+
+ int levelIndex = -1;
+ int checkerValue = 0;
+
+ auto low = lowLevels.at(i);
+ auto high = highLevels.at(i);
+
+ /**
+ * Per OpenPrinting
+ * marker-low-levels = 0 AND marker-high-levels < 100 -> receptacle
+ * "near full" warning at marker-high-levels value
+ *
+ * marker-low-levels > 0 AND marker-high-levels = 100 -> consumable
+ * "near empty"/"low" warning at marker-low-levels value
+ *
+ * marker-low-levels = 0 AND marker-high-levels = 100 is a firmware bug
+ * assume marker-low-levels = 3 (nearEmpty) which turns it into a consumable
+ * and a "near empty"/"low" warning is issued when 3% of it is left.
+ *
+ * marker-low-levels > 0 AND marker-high-levels < 100 is also a firmware bug
+ * assume marker-high-levels = 100 which turns it into a consumable and a
+ * "near empty"/"low" warning is issued when its level reaches the marker-low-levels value
+ *
+ * This assures that in case of correct firmware with correct supply-level reporting
+ * everything is done exactly as the printer manufacturer expects it and in case of
+ * buggy firmware which reports supply level info which does not allow to determine
+ * whether a supply is a consumable or a receptacle we assume that it is a consumable
+ * as most supplies are consumables.
+ */
+ bool consumable = false;
+
+ // probable firmware bugs
+ if (low == empty && high == full) {
+ // default low, set as a consumable
+ qCDebug(LIBKCUPS, "%s: possible firmware bug (%d,%d): setting low-level to: %d", qPrintable(namesList.at(i)), low, high, nearEmpty);
+ low = nearEmpty;
+ } else if (low > empty && high < full) {
+ // default high, set as a consumable
+ qCDebug(LIBKCUPS, "%s: possible firmware bug (%d,%d): setting high-level to: %d", qPrintable(namesList.at(i)), low, high, full);
+ high = full;
+ }
+
+ // first, check for receptacle
+ if (low == empty && high < full) {
+ // receptacle
+ consumable = false;
+ qCDebug(LIBKCUPS, "Found receptacle: checking %s, current level %d => (%d,%d)", qPrintable(namesList.at(i)), level, low, high);
+ if (level >= high) {
+ levelIndex = i;
+ checkerValue = level;
+ }
+ } else if (low > empty && high == full) {
+ // consumable
+ consumable = true;
+ qCDebug(LIBKCUPS, "Found consumable: checking %s, current level %d => (%d,%d)", qPrintable(namesList.at(i)), level, low, high);
+ if (level <= low) {
+ levelIndex = i;
+ checkerValue = level;
+ }
+ } else {
+ qCDebug(LIBKCUPS, "low/high range invalid (%d,%d): failure to determine marker type for: %s", low, high, qPrintable(typesList.at(i)));
+ continue;
+ }
+
+ // found a marker level at or below the warning boundary
+ if (levelIndex >= 0) {
+ const auto name = namesList.at(levelIndex);
+ const auto type = typesList.at(levelIndex);
+
+ if (consumable) {
+ if (checkerValue == 0) {
+ msgs << i18nc("@info:usagetip The name and type of the ink cartridge", "%1 (%2) appears to be empty.", name, type);
+ } else {
+ msgs << i18nc("@info:usagetip The name and type of the ink cartridge and percent ink remaining",
+ "%1 (%2) appears to be low (%3% remaining).",
+ name,
+ type,
+ checkerValue);
+ }
+ } else {
+ msgs << i18nc("@info:usagetip The name, type, and percentage full of the waste receptacle",
+ "%1 (%2) is almost full (%3%)",
+ name,
+ type,
+ checkerValue);
+ }
+ qCDebug(LIBKCUPS, "Send level notify on: %s, %s, level=%d", qPrintable(type), qPrintable(name), checkerValue);
+ }
+ }
+
+ return msgs;
+}
+
+void KCupsPrinter::setAttribute(const QString &key, const QVariant &value)
+{
+ if (!key.isEmpty()) {
+ m_attributes[key] = value;
+ }
}
QString KCupsPrinter::name() const
@@ -36,32 +183,32 @@ bool KCupsPrinter::isClass() const
bool KCupsPrinter::isDefault() const
{
- return m_arguments[QLatin1String(KCUPS_PRINTER_TYPE)].toUInt() & CUPS_PRINTER_DEFAULT;
+ return m_attributes[KCUPS_PRINTER_TYPE].toUInt() & CUPS_PRINTER_DEFAULT;
}
bool KCupsPrinter::isShared() const
{
- return m_arguments[QLatin1String(KCUPS_PRINTER_IS_SHARED)].toBool();
+ return m_attributes[KCUPS_PRINTER_IS_SHARED].toBool();
}
bool KCupsPrinter::isAcceptingJobs() const
{
- return m_arguments[QLatin1String(KCUPS_PRINTER_IS_ACCEPTING_JOBS)].toBool();
+ return m_attributes[KCUPS_PRINTER_IS_ACCEPTING_JOBS].toBool();
}
cups_ptype_e KCupsPrinter::type() const
{
- return static_cast<cups_ptype_e>(m_arguments[QLatin1String(KCUPS_PRINTER_TYPE)].toUInt());
+ return static_cast<cups_ptype_e>(m_attributes[KCUPS_PRINTER_TYPE].toUInt());
}
QString KCupsPrinter::location() const
{
- return m_arguments[QLatin1String(KCUPS_PRINTER_LOCATION)].toString();
+ return m_attributes[KCUPS_PRINTER_LOCATION].toString();
}
QString KCupsPrinter::info() const
{
- const QString printerInfo = m_arguments[QLatin1String(KCUPS_PRINTER_INFO)].toString();
+ const QString printerInfo = m_attributes[KCUPS_PRINTER_INFO].toString();
if (printerInfo.isEmpty()) {
return name();
}
@@ -70,92 +217,147 @@ QString KCupsPrinter::info() const
QString KCupsPrinter::makeAndModel() const
{
- return m_arguments[QLatin1String(KCUPS_PRINTER_MAKE_AND_MODEL)].toString();
+ return m_attributes[KCUPS_PRINTER_MAKE_AND_MODEL].toString();
}
QStringList KCupsPrinter::commands() const
{
- return m_arguments[QLatin1String(KCUPS_PRINTER_COMMANDS)].toStringList();
+ return m_attributes[KCUPS_PRINTER_COMMANDS].toStringList();
}
QStringList KCupsPrinter::memberNames() const
{
- return m_arguments[QLatin1String(KCUPS_MEMBER_NAMES)].toStringList();
+ return m_attributes[KCUPS_MEMBER_NAMES].toStringList();
}
QString KCupsPrinter::deviceUri() const
{
- return m_arguments[QLatin1String(KCUPS_DEVICE_URI)].toString();
+ return m_attributes[KCUPS_DEVICE_URI].toString();
}
QStringList KCupsPrinter::errorPolicy() const
{
- return m_arguments[QLatin1String(KCUPS_PRINTER_ERROR_POLICY)].toStringList();
+ return m_attributes[KCUPS_PRINTER_ERROR_POLICY].toStringList();
}
QStringList KCupsPrinter::errorPolicySupported() const
{
- return m_arguments[QLatin1String(KCUPS_PRINTER_ERROR_POLICY_SUPPORTED)].toStringList();
+ return m_attributes[KCUPS_PRINTER_ERROR_POLICY_SUPPORTED].toStringList();
}
QStringList KCupsPrinter::opPolicy() const
{
- return m_arguments[QLatin1String(KCUPS_PRINTER_OP_POLICY)].toStringList();
+ return m_attributes[KCUPS_PRINTER_OP_POLICY].toStringList();
}
QStringList KCupsPrinter::opPolicySupported() const
{
- return m_arguments[QLatin1String(KCUPS_PRINTER_OP_POLICY_SUPPORTED)].toStringList();
+ return m_attributes[KCUPS_PRINTER_OP_POLICY_SUPPORTED].toStringList();
}
QStringList KCupsPrinter::jobSheetsDefault() const
{
- return m_arguments[QLatin1String(KCUPS_JOB_SHEETS_DEFAULT)].toStringList();
+ return m_attributes[KCUPS_JOB_SHEETS_DEFAULT].toStringList();
}
QStringList KCupsPrinter::jobSheetsSupported() const
{
- return m_arguments[QLatin1String(KCUPS_JOB_SHEETS_SUPPORTED)].toStringList();
+ return m_attributes[KCUPS_JOB_SHEETS_SUPPORTED].toStringList();
}
QStringList KCupsPrinter::requestingUserNameAllowed() const
{
- return m_arguments[QLatin1String(KCUPS_REQUESTING_USER_NAME_ALLOWED)].toStringList();
+ return m_attributes[KCUPS_REQUESTING_USER_NAME_ALLOWED].toStringList();
}
QStringList KCupsPrinter::requestingUserNameDenied() const
{
- return m_arguments[QLatin1String(KCUPS_REQUESTING_USER_NAME_DENIED)].toStringList();
+ return m_attributes[KCUPS_REQUESTING_USER_NAME_DENIED].toStringList();
}
QStringList KCupsPrinter::authInfoRequired() const
{
- return m_arguments[QLatin1String(KCUPS_AUTH_INFO_REQUIRED)].toStringList();
+ return m_attributes[KCUPS_AUTH_INFO_REQUIRED].toStringList();
}
QString KCupsPrinter::uriSupported() const
{
- return m_arguments[QLatin1String(KCUPS_PRINTER_URI_SUPPORTED)].toString();
+ return m_attributes[KCUPS_PRINTER_URI_SUPPORTED].toString();
}
KCupsPrinter::Status KCupsPrinter::state() const
{
- return static_cast<Status>(m_arguments[QLatin1String(KCUPS_PRINTER_STATE)].toUInt());
+ return static_cast<Status>(m_attributes[KCUPS_PRINTER_STATE].toUInt());
}
QString KCupsPrinter::stateMsg() const
{
- return m_arguments[QLatin1String(KCUPS_PRINTER_STATE_MESSAGE)].toString();
+ return m_attributes[KCUPS_PRINTER_STATE_MESSAGE].toString();
}
int KCupsPrinter::markerChangeTime() const
{
- return m_arguments[QLatin1String(KCUPS_MARKER_CHANGE_TIME)].toInt();
+ return m_attributes[KCUPS_MARKER_CHANGE_TIME].toInt();
+}
+
+QStringList KCupsPrinter::markerColors() const
+{
+ return m_attributes[KCUPS_MARKER_COLORS].toStringList();
+}
+
+QStringList KCupsPrinter::markerNames() const
+{
+ return m_attributes[KCUPS_MARKER_NAMES].toStringList();
+}
+
+QStringList KCupsPrinter::markerTypes() const
+{
+ return m_attributes[KCUPS_MARKER_TYPES].toStringList();
+}
+
+static QList<int> toIntList(const QVariant &val)
+{
+ if (val.isValid()) {
+ if (val.canConvert<QList<int>>()) {
+ return val.value<QList<int>>();
+ } else {
+ return QList<int>{val.value<int>()};
+ }
+ }
+ return {};
+}
+
+QList<int> KCupsPrinter::markerLowLevels() const
+{
+ return toIntList(m_attributes.value(KCUPS_MARKER_LOW_LEVELS));
+}
+
+QList<int> KCupsPrinter::markerHighLevels() const
+{
+ return toIntList(m_attributes.value(KCUPS_MARKER_HIGH_LEVELS));
+}
+
+QList<int> KCupsPrinter::markerLevels() const
+{
+ return toIntList(m_attributes.value(KCUPS_MARKER_LEVELS));
+}
+
+QVariantMap KCupsPrinter::markers() const
+{
+ const auto levels = markerLevels();
+ if (levels.isEmpty()) {
+ return {};
+ } else {
+ return {{KCUPS_MARKER_LEVELS, QVariant::fromValue(levels)},
+ {KCUPS_MARKER_COLORS, markerColors()},
+ {KCUPS_MARKER_NAMES, markerNames()},
+ {KCUPS_MARKER_TYPES, markerTypes()}};
+ }
}
QVariant KCupsPrinter::argument(const QString &name) const
{
- return m_arguments.value(name);
+ return m_attributes.value(name);
}
QIcon KCupsPrinter::icon() const
diff --git a/src/libkcups/KCupsPrinter.h b/src/libkcups/KCupsPrinter.h
index e78859bc..e59c93f4 100644
--- a/src/libkcups/KCupsPrinter.h
+++ b/src/libkcups/KCupsPrinter.h
@@ -1,5 +1,6 @@
/*
SPDX-FileCopyrightText: 2010-2012 Daniel Nicoletti <[email protected]>
+ SPDX-FileCopyrightText: 2026 Mike Noe <[email protected]>
SPDX-License-Identifier: GPL-2.0-or-later
*/
@@ -50,27 +51,52 @@ public:
int markerChangeTime() const;
QVariant argument(const QString &name) const;
+ QVariantMap markers() const;
+ QList<int> markerLevels() const;
+ QList<int> markerLowLevels() const;
+ QList<int> markerHighLevels() const;
+ QStringList markerColors() const;
+ QStringList markerNames() const;
+ QStringList markerTypes() const;
+
/**
* Requires enum PrinterType to work properly
- *
*/
QIcon icon() const;
static QIcon icon(cups_ptype_e type);
QString iconName() const;
static QString iconName(cups_ptype_e type);
+ /**
+ * @brief checkMarkerLevels - Check all marker levels for the printer
+ * CUPS supports devices with both classes of markers, consumables and receptacles
+ *
+ * https://openprinting.github.io/cups/doc/spec-ipp.html#marker-types
+ *
+ * Consumables are generally toners/cartridges
+ * levels decrease from full to empty with warning level at near-empty
+ *
+ * Receptacles are generally waste containters
+ * levels increase from empty to full with warning level at near-full
+ *
+ * @return list of message strings, one for each marker found at warning threshold
+ */
+ QStringList checkMarkerLevels() const;
+
protected:
- KCupsPrinter(const QVariantMap &arguments);
+ explicit KCupsPrinter(const QVariantMap &attributes);
+ void setAttribute(const QString &key, const QVariant &value);
private:
friend class KCupsRequest;
+ friend class PrinterModel;
QString m_printer;
bool m_isClass;
- QVariantMap m_arguments;
+ QVariantMap m_attributes;
};
-typedef QList<KCupsPrinter> KCupsPrinters;
+using KCupsPrinters = QList<KCupsPrinter>;
Q_DECLARE_METATYPE(KCupsPrinters)
Q_DECLARE_METATYPE(KCupsPrinter)