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