1 /*
2     SPDX-FileCopyrightText: 2010-2018 Daniel Nicoletti <dantti12@gmail.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "PrinterModel.h"
8 
9 #include "Debug.h"
10 
11 #include <QDateTime>
12 #include <QMimeData>
13 #include <QDBusConnection>
14 #include <QDBusInterface>
15 #include <QPointer>
16 
17 #include <KUser>
18 #include <KLocalizedString>
19 #include <KMessageBox>
20 
21 #include <KCupsRequest.h>
22 
23 #include <cups/cups.h>
24 
25 const static QStringList attrs(QStringList{
26                                    KCUPS_PRINTER_NAME,
27                                    KCUPS_PRINTER_STATE,
28                                    KCUPS_PRINTER_STATE_MESSAGE,
29                                    KCUPS_PRINTER_IS_SHARED,
30                                    KCUPS_PRINTER_IS_ACCEPTING_JOBS,
31                                    KCUPS_PRINTER_TYPE,
32                                    KCUPS_PRINTER_LOCATION,
33                                    KCUPS_PRINTER_INFO,
34                                    KCUPS_PRINTER_MAKE_AND_MODEL,
35                                    KCUPS_PRINTER_COMMANDS,
36                                    KCUPS_MARKER_CHANGE_TIME,
37                                    KCUPS_MARKER_COLORS,
38                                    KCUPS_MARKER_LEVELS,
39                                    KCUPS_MARKER_NAMES,
40                                    KCUPS_MARKER_TYPES
41                                });
42 
PrinterModel(QObject * parent)43 PrinterModel::PrinterModel(QObject *parent) :
44     QStandardItemModel(parent)
45 {
46     m_roles = QStandardItemModel::roleNames();
47     m_roles[DestStatus] = "stateMessage";
48     m_roles[DestName] = "printerName";
49     m_roles[DestState] = "printerState";
50     m_roles[DestIsDefault] = "isDefault";
51     m_roles[DestIsShared] = "isShared";
52     m_roles[DestIsAcceptingJobs] = "isAcceptingJobs";
53     m_roles[DestIsPaused] = "isPaused";
54     m_roles[DestIsClass] = "isClass";
55     m_roles[DestLocation] = "location";
56     m_roles[DestDescription] = "info";
57     m_roles[DestKind] = "kind";
58     m_roles[DestType] = "type";
59     m_roles[DestCommands] = "commands";
60     m_roles[DestMarkerChangeTime] = "markerChangeTime";
61     m_roles[DestMarkers] = "markers";
62     m_roles[DestIconName] = "iconName";
63     m_roles[DestRemote] = "remote";
64 
65     // This is emitted when a printer is added
66     connect(KCupsConnection::global(), &KCupsConnection::printerAdded, this, &PrinterModel::insertUpdatePrinter);
67 
68     // This is emitted when a printer is modified
69     connect(KCupsConnection::global(), &KCupsConnection::printerModified, this, &PrinterModel::insertUpdatePrinter);
70 
71     // This is emitted when a printer has it's state changed
72     connect(KCupsConnection::global(), &KCupsConnection::printerStateChanged, this, &PrinterModel::insertUpdatePrinter);
73 
74     // This is emitted when a printer is stopped
75     connect(KCupsConnection::global(), &KCupsConnection::printerStopped, this, &PrinterModel::insertUpdatePrinter);
76 
77     // This is emitted when a printer is restarted
78     connect(KCupsConnection::global(), &KCupsConnection::printerRestarted, this, &PrinterModel::insertUpdatePrinter);
79 
80     // This is emitted when a printer is shutdown
81     connect(KCupsConnection::global(), &KCupsConnection::printerShutdown, this, &PrinterModel::insertUpdatePrinter);
82 
83     // This is emitted when a printer is removed
84     connect(KCupsConnection::global(), &KCupsConnection::printerDeleted, this, &PrinterModel::printerRemoved);
85 
86     connect(KCupsConnection::global(), &KCupsConnection::serverAudit, this, &PrinterModel::serverChanged);
87     connect(KCupsConnection::global(), &KCupsConnection::serverStarted, this, &PrinterModel::serverChanged);
88     connect(KCupsConnection::global(), &KCupsConnection::serverStopped, this, &PrinterModel::serverChanged);
89     connect(KCupsConnection::global(), &KCupsConnection::serverRestarted, this, &PrinterModel::serverChanged);
90 
91     // Deprecated stuff that works better than the above
92     connect(KCupsConnection::global(), &KCupsConnection::rhPrinterAdded, this, &PrinterModel::insertUpdatePrinterName);
93     connect(KCupsConnection::global(), &KCupsConnection::rhPrinterRemoved, this, &PrinterModel::printerRemovedName);
94     connect(KCupsConnection::global(), &KCupsConnection::rhQueueChanged, this, &PrinterModel::insertUpdatePrinterName);
95 
96     connect(this, &PrinterModel::rowsInserted, this, &PrinterModel::slotCountChanged);
97     connect(this, &PrinterModel::rowsRemoved, this, &PrinterModel::slotCountChanged);
98     connect(this, &PrinterModel::modelReset, this, &PrinterModel::slotCountChanged);
99 
100     update();
101 }
102 
getDestsFinished(KCupsRequest * request)103 void PrinterModel::getDestsFinished(KCupsRequest *request)
104 {
105     // When there is no printer IPP_NOT_FOUND is returned
106     if (request->hasError() && request->error() != IPP_NOT_FOUND) {
107         // clear the model after so that the proper widget can be shown
108         clear();
109 
110         emit error(request->error(), request->serverError(), request->errorMsg());
111         if (request->error() == IPP_SERVICE_UNAVAILABLE && !m_unavailable) {
112             m_unavailable = true;
113             emit serverUnavailableChanged(m_unavailable);
114         }
115     } else {
116         if (m_unavailable) {
117             m_unavailable = false;
118             emit serverUnavailableChanged(m_unavailable);
119         }
120 
121         const KCupsPrinters printers = request->printers();
122         for (int i = 0; i < printers.size(); ++i) {
123             // If there is a printer and it's not the current one add it
124             // as a new destination
125             int dest_row = destRow(printers.at(i).name());
126             if (dest_row == -1) {
127                 // not found, insert new one
128                 insertDest(i, printers.at(i));
129             } else if (dest_row == i) {
130                 // update the printer
131                 updateDest(item(i), printers.at(i));
132             } else {
133                 // found at wrong position
134                 // take it and insert on the right position
135                 QList<QStandardItem *> row = takeRow(dest_row);
136                 insertRow(i, row);
137                 updateDest(item(i), printers.at(i));
138             }
139         }
140 
141         // remove old printers
142         // The above code starts from 0 and make sure
143         // dest == modelIndex(x) and if it's not the
144         // case it either inserts or moves it.
145         // so any item > num_jobs can be safely deleted
146         while (rowCount() > printers.size()) {
147             removeRow(rowCount() - 1);
148         }
149 
150         emit error(IPP_OK, QString(), QString());
151     }
152     request->deleteLater();
153 }
154 
slotCountChanged()155 void PrinterModel::slotCountChanged()
156 {
157     emit countChanged(rowCount());
158 }
159 
headerData(int section,Qt::Orientation orientation,int role) const160 QVariant PrinterModel::headerData(int section, Qt::Orientation orientation, int role) const
161 {
162     if (section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) {
163         return i18n("Printers");
164     }
165     return QVariant();
166 }
167 
count() const168 int PrinterModel::count() const
169 {
170     return rowCount();
171 }
172 
serverUnavailable() const173 bool PrinterModel::serverUnavailable() const
174 {
175     return m_unavailable;
176 }
177 
roleNames() const178 QHash<int, QByteArray> PrinterModel::roleNames() const
179 {
180     return m_roles;
181 }
182 
pausePrinter(const QString & printerName)183 void PrinterModel::pausePrinter(const QString &printerName)
184 {
185     QPointer<KCupsRequest> request = new KCupsRequest;
186     request->pausePrinter(printerName);
187     request->waitTillFinished();
188     if (request) {
189         request->deleteLater();
190     }
191 }
192 
resumePrinter(const QString & printerName)193 void PrinterModel::resumePrinter(const QString &printerName)
194 {
195     QPointer<KCupsRequest> request = new KCupsRequest;
196     request->resumePrinter(printerName);
197     request->waitTillFinished();
198     if (request) {
199         request->deleteLater();
200     }
201 }
202 
rejectJobs(const QString & printerName)203 void PrinterModel::rejectJobs(const QString &printerName)
204 {
205     QPointer<KCupsRequest> request = new KCupsRequest;
206     request->rejectJobs(printerName);
207     request->waitTillFinished();
208     if (request) {
209         request->deleteLater();
210     }
211 }
212 
acceptJobs(const QString & printerName)213 void PrinterModel::acceptJobs(const QString &printerName)
214 {
215     QPointer<KCupsRequest> request = new KCupsRequest;
216     request->acceptJobs(printerName);
217     request->waitTillFinished();
218     if (request) {
219         request->deleteLater();
220     }
221 }
222 
update()223 void PrinterModel::update()
224 {
225 //                 kcmshell(6331) PrinterModel::update: (QHash(("printer-type", QVariant(int, 75534348) ) ( "marker-names" ,  QVariant(QStringList, ("Cyan", "Yellow", "Magenta", "Black") ) ) ( "printer-name" ,  QVariant(QString, "EPSON_Stylus_TX105") ) ( "marker-colors" ,  QVariant(QStringList, ("#00ffff", "#ffff00", "#ff00ff", "#000000") ) ) ( "printer-location" ,  QVariant(QString, "Luiz Vitor’s MacBook Pro") ) ( "marker-levels" ,  QVariant(QList<int>, ) ) ( "marker-types" ,  QVariant(QStringList, ("inkCartridge", "inkCartridge", "inkCartridge", "inkCartridge") ) ) ( "printer-is-shared" ,  QVariant(bool, true) ) ( "printer-state-message" ,  QVariant(QString, "") ) ( "printer-commands" ,  QVariant(QStringList, ("Clean", "PrintSelfTestPage", "ReportLevels") ) ) ( "marker-change-time" ,  QVariant(int, 1267903160) ) ( "printer-state" ,  QVariant(int, 3) ) ( "printer-info" ,  QVariant(QString, "EPSON Stylus TX105") ) ( "printer-make-and-model" ,  QVariant(QString, "EPSON TX105 Series") ) )  )
226     // Get destinations with these attributes
227     auto request = new KCupsRequest;
228     connect(request, &KCupsRequest::finished, this, &PrinterModel::getDestsFinished);
229     request->getPrinters(attrs);
230 }
231 
insertDest(int pos,const KCupsPrinter & printer)232 void PrinterModel::insertDest(int pos, const KCupsPrinter &printer)
233 {
234     // Create the printer item
235     auto stdItem = new QStandardItem(printer.name());
236     stdItem->setData(printer.name(), DestName);
237     stdItem->setIcon(printer.icon());
238     // update the item
239     updateDest(stdItem, printer);
240 
241     // insert the printer Item
242     insertRow(pos, stdItem);
243 }
244 
updateDest(QStandardItem * destItem,const KCupsPrinter & printer)245 void PrinterModel::updateDest(QStandardItem *destItem, const KCupsPrinter &printer)
246 {
247     // store if the printer is the network default
248     bool isDefault = printer.isDefault();
249     if (isDefault != destItem->data(DestIsDefault).toBool()) {
250         destItem->setData(isDefault, DestIsDefault);
251     }
252 
253     // store the printer state
254     KCupsPrinter::Status state = printer.state();
255     if (state != destItem->data(DestState)) {
256         destItem->setData(state, DestState);
257     }
258     qCDebug(LIBKCUPS) << state << printer.name();
259 
260     // store if the printer is accepting jobs
261     bool accepting = printer.isAcceptingJobs();
262     if (accepting != destItem->data(DestIsAcceptingJobs)) {
263         destItem->setData(accepting, DestIsAcceptingJobs);
264     }
265 
266     // store the printer status message
267     QString status = destStatus(state, printer.stateMsg(), accepting);
268     if (status != destItem->data(DestStatus)) {
269         destItem->setData(status, DestStatus);
270     }
271 
272     bool paused = (state == KCupsPrinter::Stopped || !accepting);
273     if (paused != destItem->data(DestIsPaused)) {
274         destItem->setData(paused, DestIsPaused);
275     }
276 
277     // store if the printer is shared
278     bool shared = printer.isShared();
279     if (shared != destItem->data(DestIsShared)) {
280         destItem->setData(shared, DestIsShared);
281     }
282 
283     // store if the printer is a class
284     // the printer-type param is a flag
285     bool isClass = printer.isClass();
286     if (isClass != destItem->data(DestIsClass)) {
287         destItem->setData(isClass, DestIsClass);
288     }
289 
290     // store if the printer type
291     // the printer-type param is a flag
292     uint printerType = printer.type();
293     if (printerType != destItem->data(DestType)) {
294         destItem->setData(printerType, DestType);
295         destItem->setData(printerType & CUPS_PRINTER_REMOTE, DestRemote);
296     }
297 
298     // store the printer location
299     QString location = printer.location();
300     if (location != destItem->data(DestLocation).toString()) {
301         destItem->setData(location, DestLocation);
302     }
303 
304     // store the printer icon name
305     QString iconName = printer.iconName();
306     if (iconName != destItem->data(DestIconName).toString()) {
307         destItem->setData(iconName, DestIconName);
308     }
309 
310     if (destItem->data(DestName).toString() != destItem->text()){
311         if (destItem->text() != destItem->data(DestName).toString()){
312             destItem->setText(destItem->data(DestName).toString());
313         }
314     }
315 
316     // store the printer description
317     QString description = printer.info();
318     if (description != destItem->data(DestDescription).toString()){
319         destItem->setData(description, DestDescription);
320     }
321 
322     // store the printer kind
323     QString kind = printer.makeAndModel();
324     if (kind != destItem->data(DestKind)) {
325         destItem->setData(kind, DestKind);
326     }
327 
328     // store the printer commands
329     QStringList commands = printer.commands();
330     if (commands != destItem->data(DestCommands)) {
331         destItem->setData(commands, DestCommands);
332     }
333 
334     int markerChangeTime = printer.markerChangeTime();
335     if (markerChangeTime != destItem->data(DestMarkerChangeTime)) {
336         destItem->setData(printer.markerChangeTime(), DestMarkerChangeTime);
337         const QVariantHash markers{
338             {KCUPS_MARKER_CHANGE_TIME, printer.markerChangeTime()},
339             {KCUPS_MARKER_COLORS, printer.argument(KCUPS_MARKER_COLORS)},
340             {KCUPS_MARKER_LEVELS, printer.argument(KCUPS_MARKER_LEVELS)},
341             {KCUPS_MARKER_NAMES, printer.argument(KCUPS_MARKER_NAMES)},
342             {KCUPS_MARKER_TYPES, printer.argument(KCUPS_MARKER_TYPES)}
343         };
344         destItem->setData(markers, DestMarkers);
345     }
346 }
347 
destRow(const QString & destName)348 int PrinterModel::destRow(const QString &destName)
349 {
350     // find the position of the jobId inside the model
351     for (int i = 0; i < rowCount(); i++) {
352         if (destName == item(i)->data(DestName).toString())
353         {
354             return i;
355         }
356     }
357     // -1 if not found
358     return -1;
359 }
360 
destStatus(KCupsPrinter::Status state,const QString & message,bool isAcceptingJobs) const361 QString PrinterModel::destStatus(KCupsPrinter::Status state, const QString &message, bool isAcceptingJobs) const
362 {
363     switch (state) {
364     case KCupsPrinter::Idle:
365         if (message.isEmpty()){
366             return isAcceptingJobs ? i18n("Idle") : i18n("Idle, rejecting jobs");
367         } else {
368             return isAcceptingJobs ? i18n("Idle - '%1'", message) : i18n("Idle, rejecting jobs - '%1'", message);
369         }
370     case KCupsPrinter::Printing:
371         if (message.isEmpty()){
372             return i18n("In use");
373         } else {
374             return i18n("In use - '%1'", message);
375         }
376     case KCupsPrinter::Stopped:
377         if (message.isEmpty()){
378             return isAcceptingJobs ? i18n("Paused") : i18n("Paused, rejecting jobs");
379         } else {
380             return isAcceptingJobs ? i18n("Paused - '%1'", message) : i18n("Paused, rejecting jobs - '%1'", message);
381         }
382     default :
383         if (message.isEmpty()){
384             return i18n("Unknown");
385         } else {
386             return i18n("Unknown - '%1'", message);
387         }
388     }
389 }
390 
clear()391 void PrinterModel::clear()
392 {
393     removeRows(0, rowCount());
394 }
395 
flags(const QModelIndex & index) const396 Qt::ItemFlags PrinterModel::flags(const QModelIndex &index) const
397 {
398     Q_UNUSED(index)
399     return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
400 }
401 
402 
insertUpdatePrinterName(const QString & printerName)403 void PrinterModel::insertUpdatePrinterName(const QString &printerName)
404 {
405     auto request = new KCupsRequest;
406     connect(request, &KCupsRequest::finished, this, &PrinterModel::insertUpdatePrinterFinished);
407     // TODO how do we know if it's a class if this DBus signal
408     // does not tell us
409     request->getPrinterAttributes(printerName, false, attrs);
410 }
411 
insertUpdatePrinter(const QString & text,const QString & printerUri,const QString & printerName,uint printerState,const QString & printerStateReasons,bool printerIsAcceptingJobs)412 void PrinterModel::insertUpdatePrinter(const QString &text,
413                                        const QString &printerUri,
414                                        const QString &printerName,
415                                        uint printerState,
416                                        const QString &printerStateReasons,
417                                        bool printerIsAcceptingJobs)
418 {
419     Q_UNUSED(text)
420     Q_UNUSED(printerUri)
421     Q_UNUSED(printerState)
422     Q_UNUSED(printerStateReasons)
423     Q_UNUSED(printerIsAcceptingJobs)
424 
425     qCDebug(LIBKCUPS) << text << printerUri << printerName << printerState << printerStateReasons << printerIsAcceptingJobs;
426     insertUpdatePrinterName(printerName);
427 }
428 
insertUpdatePrinterFinished(KCupsRequest * request)429 void PrinterModel::insertUpdatePrinterFinished(KCupsRequest *request)
430 {
431     if (!request->hasError()) {
432         const KCupsPrinters printers = request->printers();
433         for (const KCupsPrinter &printer : printers) {
434             // If there is a printer and it's not the current one add it
435             // as a new destination
436             int dest_row = destRow(printer.name());
437             if (dest_row == -1) {
438                 // not found, insert new one
439                 insertDest(0, printer);
440             } else {
441                 // update the printer
442                 updateDest(item(dest_row), printer);
443             }
444         }
445     }
446     request->deleteLater();
447 }
448 
printerRemovedName(const QString & printerName)449 void PrinterModel::printerRemovedName(const QString &printerName)
450 {
451     qCDebug(LIBKCUPS) << printerName;
452 
453     // Look for the removed printer
454     int dest_row = destRow(printerName);
455     if (dest_row != -1) {
456         removeRows(dest_row, 1);
457     }
458 }
459 
printerRemoved(const QString & text,const QString & printerUri,const QString & printerName,uint printerState,const QString & printerStateReasons,bool printerIsAcceptingJobs)460 void PrinterModel::printerRemoved(const QString &text,
461                                   const QString &printerUri,
462                                   const QString &printerName,
463                                   uint printerState,
464                                   const QString &printerStateReasons,
465                                   bool printerIsAcceptingJobs)
466 {
467     // REALLY? all these parameters just to say foo was deleted??
468     Q_UNUSED(text)
469     Q_UNUSED(printerUri)
470     Q_UNUSED(printerState)
471     Q_UNUSED(printerStateReasons)
472     Q_UNUSED(printerIsAcceptingJobs)
473     qCDebug(LIBKCUPS) << text << printerUri << printerName << printerState << printerStateReasons << printerIsAcceptingJobs;
474 
475     // Look for the removed printer
476     int dest_row = destRow(printerName);
477     if (dest_row != -1) {
478         removeRows(dest_row, 1);
479     }
480 }
481 
printerStateChanged(const QString & text,const QString & printerUri,const QString & printerName,uint printerState,const QString & printerStateReasons,bool printerIsAcceptingJobs)482 void PrinterModel::printerStateChanged(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs)
483 {
484     qCDebug(LIBKCUPS) << text << printerUri << printerName << printerState << printerStateReasons << printerIsAcceptingJobs;
485 }
printerStopped(const QString & text,const QString & printerUri,const QString & printerName,uint printerState,const QString & printerStateReasons,bool printerIsAcceptingJobs)486 void PrinterModel::printerStopped(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs)
487 {
488     qCDebug(LIBKCUPS) << text << printerUri << printerName << printerState << printerStateReasons << printerIsAcceptingJobs;
489 }
490 
printerRestarted(const QString & text,const QString & printerUri,const QString & printerName,uint printerState,const QString & printerStateReasons,bool printerIsAcceptingJobs)491 void PrinterModel::printerRestarted(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs)
492 {
493     qCDebug(LIBKCUPS) << text << printerUri << printerName << printerState << printerStateReasons << printerIsAcceptingJobs;
494 }
495 
printerShutdown(const QString & text,const QString & printerUri,const QString & printerName,uint printerState,const QString & printerStateReasons,bool printerIsAcceptingJobs)496 void PrinterModel::printerShutdown(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs)
497 {
498     qCDebug(LIBKCUPS) << text << printerUri << printerName << printerState << printerStateReasons << printerIsAcceptingJobs;
499 }
500 
printerModified(const QString & text,const QString & printerUri,const QString & printerName,uint printerState,const QString & printerStateReasons,bool printerIsAcceptingJobs)501 void PrinterModel::printerModified(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs)
502 {
503     qCDebug(LIBKCUPS) << text << printerUri << printerName << printerState << printerStateReasons << printerIsAcceptingJobs;
504 }
505 
serverChanged(const QString & text)506 void PrinterModel::serverChanged(const QString &text)
507 {
508     qCDebug(LIBKCUPS) << text;
509     update();
510 }
511 
512 #include "moc_PrinterModel.cpp"
513