File indexing completed on 2022-09-27 16:37:10

0001 /*
0002     SPDX-FileCopyrightText: 2010-2018 Daniel Nicoletti <dantti12@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "DevicesModel.h"
0008 
0009 #include <KCupsRequest.h>
0010 
0011 #include <KLocalizedString>
0012 #include <KMessageBox>
0013 
0014 #include <QHostInfo>
0015 #include <QDBusMetaType>
0016 #include <QDBusConnection>
0017 
0018 #include <QDebug>
0019 
0020 DevicesModel::DevicesModel(QObject *parent) : QStandardItemModel(parent)
0021   , m_request(nullptr)
0022   , m_rx(QLatin1String("[a-z]+://.*"))
0023   , m_blacklistedURIs({
0024                       QLatin1String("hp"),
0025                       QLatin1String("hpfax"),
0026                       QLatin1String("hal"),
0027                       QLatin1String("beh"),
0028                       QLatin1String("scsi"),
0029                       QLatin1String("http"),
0030                       QLatin1String("delete")
0031                       })
0032 {
0033     qDBusRegisterMetaType<MapSS>();
0034     qDBusRegisterMetaType<MapSMapSS>();
0035 
0036     // Adds the other device which is meant for manual URI input
0037     insertDevice(QLatin1String("other"),
0038                  QString(),
0039                  i18nc("@item", "Manual URI"),
0040                  QString(),
0041                  QLatin1String("other"),
0042                  QString());
0043 }
0044 
0045 void DevicesModel::update()
0046 {
0047     if (m_request) {
0048         return;
0049     }
0050 
0051     // clear the model to don't duplicate items
0052     if (rowCount()) {
0053         removeRows(1, rowCount() - 1);
0054     }
0055     m_request = new KCupsRequest;
0056     connect(m_request, &KCupsRequest::device, this, &DevicesModel::gotDevice);
0057     connect(m_request, &KCupsRequest::finished, this, &DevicesModel::finished);
0058 
0059     // Get devices with 5 seconds of timeout
0060     m_request->getDevices(10);
0061 }
0062 
0063 
0064 void DevicesModel::gotDevice(const QString &device_class,
0065                              const QString &device_id,
0066                              const QString &device_info,
0067                              const QString &device_make_and_model,
0068                              const QString &device_uri,
0069                              const QString &device_location)
0070 {
0071     // "direct"
0072     qDebug() << device_class;
0073     // "MFG:Samsung;CMD:GDI;MDL:SCX-4200 Series;CLS:PRINTER;MODE:PCL;STATUS:IDLE;"
0074     qDebug() << device_id;
0075     // "Samsung SCX-4200 Series"
0076     qDebug() << device_info;
0077     // "Samsung SCX-4200 Series"
0078     qDebug() << device_make_and_model;
0079     // "usb://Samsung/SCX-4200%20Series"
0080     qDebug() << device_uri;
0081     // ""
0082     qDebug() << device_location;
0083 
0084     if (m_blacklistedURIs.contains(device_uri)) {
0085         // ignore black listed uri's
0086         return;
0087     }
0088 
0089     // For the protocols, not real devices
0090     if (device_id.isEmpty() &&
0091             device_make_and_model == QLatin1String("Unknown")) {
0092         insertDevice(device_class,
0093                      device_id,
0094                      device_info,
0095                      device_make_and_model,
0096                      device_uri,
0097                      device_location);
0098     } else {
0099         // Map the devices so later we try to group them
0100         const MapSS mapSS({
0101                               {KCUPS_DEVICE_CLASS, device_class},
0102                               {KCUPS_DEVICE_ID, device_id},
0103                               {KCUPS_DEVICE_INFO, device_info},
0104                               {KCUPS_DEVICE_MAKE_AND_MODEL, device_make_and_model},
0105                               {KCUPS_DEVICE_LOCATION, device_location}
0106                           });
0107         m_mappedDevices[device_uri] = mapSS;
0108     }
0109 }
0110 
0111 void DevicesModel::finished()
0112 {
0113     bool hasError = m_request->hasError();
0114     if (hasError) {
0115         Q_EMIT errorMessage(i18n("Failed to get a list of devices: '%1'", m_request->errorMsg()));
0116     }
0117     m_request->deleteLater();
0118     m_request = nullptr;
0119 
0120     if (hasError || m_mappedDevices.isEmpty()) {
0121         Q_EMIT loaded();
0122         return;
0123     }
0124 
0125     QDBusMessage message;
0126     message = QDBusMessage::createMethodCall(QLatin1String("org.fedoraproject.Config.Printing"),
0127                                              QLatin1String("/org/fedoraproject/Config/Printing"),
0128                                              QLatin1String("org.fedoraproject.Config.Printing"),
0129                                              QLatin1String("GroupPhysicalDevices"));
0130     message << QVariant::fromValue(m_mappedDevices);
0131     QDBusConnection::sessionBus().callWithCallback(message,
0132                                                    this,
0133                                                    SLOT(getGroupedDevicesSuccess(QDBusMessage)),
0134                                                    SLOT(getGroupedDevicesFailed(QDBusError,QDBusMessage)));
0135 }
0136 
0137 void DevicesModel::insertDevice(const QString &device_class,
0138                                 const QString &device_id,
0139                                 const QString &device_info,
0140                                 const QString &device_make_and_model,
0141                                 const QString &device_uri,
0142                                 const QString &device_location,
0143                                 const QStringList &grouped_uris)
0144 {
0145     QStandardItem *stdItem;
0146     stdItem = createItem(device_class,
0147                          device_id,
0148                          device_info,
0149                          device_make_and_model,
0150                          device_uri,
0151                          device_location,
0152                          !grouped_uris.isEmpty());
0153     if (!grouped_uris.isEmpty()) {
0154         stdItem->setData(grouped_uris, DeviceUris);
0155     }
0156 }
0157 
0158 void DevicesModel::insertDevice(const QString &device_class,
0159                                 const QString &device_id,
0160                                 const QString &device_info,
0161                                 const QString &device_make_and_model,
0162                                 const QString &device_uri,
0163                                 const QString &device_location,
0164                                 const KCupsPrinters &grouped_printers)
0165 {
0166     QStandardItem *stdItem;
0167     stdItem = createItem(device_class,
0168                          device_id,
0169                          device_info,
0170                          device_make_and_model,
0171                          device_uri,
0172                          device_location,
0173                          !grouped_printers.isEmpty());
0174     if (!grouped_printers.isEmpty()) {
0175         stdItem->setData(QVariant::fromValue(grouped_printers), DeviceUris);
0176     }
0177 }
0178 
0179 QStandardItem *DevicesModel::createItem(const QString &device_class,
0180                                         const QString &device_id,
0181                                         const QString &device_info,
0182                                         const QString &device_make_and_model,
0183                                         const QString &device_uri,
0184                                         const QString &device_location,
0185                                         bool grouped)
0186 {
0187     // "direct"
0188     qDebug() << device_class;
0189     // "MFG:Samsung;CMD:GDI;MDL:SCX-4200 Series;CLS:PRINTER;MODE:PCL;STATUS:IDLE;"
0190     qDebug() << device_id;
0191     // "Samsung SCX-4200 Series"
0192     qDebug() << device_info;
0193     // "Samsung SCX-4200 Series"
0194     qDebug() << device_make_and_model;
0195     // "usb://Samsung/SCX-4200%20Series"
0196     qDebug() << device_uri;
0197     // ""
0198     qDebug() << device_location;
0199 
0200     Kind kind;
0201     // Store the kind of the device
0202     if (device_class == QLatin1String("network")) {
0203         if (m_rx.indexIn(device_uri) > -1) {
0204             kind = Networked;
0205         } else {
0206             // other network devices looks like
0207             // just "http"
0208             kind = OtherNetworked;
0209         }
0210     } else if (device_class == QLatin1String("other") &&
0211                device_uri == QLatin1String("other")) {
0212         kind = Other;
0213     } else {
0214         // If device class is not network assume local
0215         kind = Local;
0216     }
0217 
0218     QString location;
0219     if (device_location.isEmpty() && kind == Local) {
0220         location = QHostInfo::localHostName();
0221     } else {
0222         location = device_location;
0223     }
0224 
0225     QString text;
0226     if (!device_make_and_model.isEmpty() &&
0227             !grouped &&
0228             device_make_and_model.compare(QLatin1String("unknown"), Qt::CaseInsensitive)) {
0229         text = device_info + QLatin1String(" (") + device_make_and_model + QLatin1Char(')');
0230     } else {
0231         text = device_info;
0232     }
0233 
0234     QString toolTip;
0235     if (!grouped) {
0236         if (device_uri.startsWith(QLatin1String("parallel"))) {
0237             toolTip = i18nc("@info:tooltip",
0238                             "A printer connected to the parallel port");
0239         } else if (device_uri.startsWith(QLatin1String("usb"))) {
0240             toolTip = i18nc("@info:tooltip",
0241                             "A printer connected to a USB port");
0242         } else if (device_uri.startsWith(QLatin1String("bluetooth"))) {
0243             toolTip = i18nc("@info:tooltip",
0244                             "A printer connected via Bluetooth");
0245         } else if (device_uri.startsWith(QLatin1String("hal"))) {
0246             toolTip = i18nc("@info:tooltip",
0247                             "Local printer detected by the "
0248                             "Hardware Abstraction Layer (HAL)");
0249         } else if (device_uri.startsWith(QLatin1String("hp"))) {
0250             toolTip = i18nc("@info:tooltip",
0251                             "HPLIP software driving a printer, "
0252                             "or the printer function of a multi-function device");
0253         } else if (device_uri.startsWith(QLatin1String("hpfax"))) {
0254             toolTip = i18nc("@info:tooltip",
0255                             "HPLIP software driving a fax machine, "
0256                             "or the fax function of a multi-function device");
0257         } else if (device_uri.startsWith(QLatin1String("dnssd")) ||
0258                    device_uri.startsWith(QLatin1String("mdns"))) {
0259             toolTip = i18nc("@info:tooltip",
0260                             "Remote CUPS printer via DNS-SD");
0261         }
0262     }
0263 
0264     auto stdItem = new QStandardItem;
0265     stdItem->setText(text);
0266     stdItem->setToolTip(toolTip);
0267     stdItem->setData(device_class, DeviceClass);
0268     stdItem->setData(device_id, DeviceId);
0269     stdItem->setData(device_info, DeviceInfo);
0270     stdItem->setData(device_uri, DeviceUri);
0271     stdItem->setData(device_make_and_model, DeviceMakeAndModel);
0272     stdItem->setData(device_location, DeviceLocation);
0273 
0274     // Find the proper category to our item
0275     QStandardItem *catItem;
0276     switch (kind) {
0277     case Networked:
0278         catItem = findCreateCategory(i18nc("@item", "Discovered Network Printers"), kind);
0279         catItem->appendRow(stdItem);
0280         break;
0281     case OtherNetworked:
0282         catItem = findCreateCategory(i18nc("@item", "Other Network Printers"), kind);
0283         catItem->appendRow(stdItem);
0284         break;
0285     case Local:
0286         catItem = findCreateCategory(i18nc("@item", "Local Printers"), kind);
0287         catItem->appendRow(stdItem);
0288         break;
0289     default:
0290         stdItem->setData(kind, Qt::UserRole);
0291         appendRow(stdItem);
0292     }
0293 
0294     return stdItem;
0295 }
0296 
0297 void DevicesModel::getGroupedDevicesSuccess(const QDBusMessage &message)
0298 {
0299     if (message.type() == QDBusMessage::ReplyMessage && message.arguments().size() == 1) {
0300         const auto argument = message.arguments().first().value<QDBusArgument>();
0301         const auto groupeDevices = qdbus_cast<QList<QStringList> >(argument);
0302         for (const QStringList &list : groupeDevices) {
0303             if (list.isEmpty()) {
0304                 continue;
0305             }
0306 
0307             const QString uri = list.first();
0308             const MapSS device = m_mappedDevices[uri];
0309             insertDevice(device[KCUPS_DEVICE_CLASS],
0310                          device[KCUPS_DEVICE_ID],
0311                          device[KCUPS_DEVICE_INFO],
0312                          device[KCUPS_DEVICE_MAKE_AND_MODEL],
0313                          uri,
0314                          device[KCUPS_DEVICE_LOCATION],
0315                          list.size() > 1 ? list : QStringList());
0316         }
0317     } else {
0318         qWarning() << "Unexpected message" << message;
0319         groupedDevicesFallback();
0320     }
0321     Q_EMIT loaded();
0322 }
0323 
0324 void DevicesModel::getGroupedDevicesFailed(const QDBusError &error, const QDBusMessage &message)
0325 {
0326     qWarning() << error <<  message;
0327     groupedDevicesFallback();
0328     Q_EMIT errorMessage(i18n("Failed to group devices: '%1'", error.message()));
0329     Q_EMIT loaded();
0330 }
0331 
0332 void DevicesModel::groupedDevicesFallback()
0333 {
0334     MapSMapSS::const_iterator i = m_mappedDevices.constBegin();
0335     while (i != m_mappedDevices.constEnd()) {
0336         const MapSS device = i.value();
0337         insertDevice(device[KCUPS_DEVICE_CLASS],
0338                      device[KCUPS_DEVICE_ID],
0339                      device[KCUPS_DEVICE_INFO],
0340                      device[KCUPS_DEVICE_MAKE_AND_MODEL],
0341                      i.key(),
0342                      device[KCUPS_DEVICE_LOCATION]);
0343         ++i;
0344     }
0345 }
0346 
0347 QStandardItem* DevicesModel::findCreateCategory(const QString &category, Kind kind)
0348 {
0349     for (int i = 0; i < rowCount(); ++i) {
0350         QStandardItem *catItem = item(i);
0351         if (catItem->data(Qt::UserRole).toInt() == kind) {
0352             return catItem;
0353         }
0354     }
0355 
0356     int pos = 0;
0357     for (int i = 0; i < rowCount(); ++i, ++pos) {
0358         QStandardItem *catItem = item(i);
0359         if (catItem->data(Qt::UserRole).toInt() > kind) {
0360             pos = i;
0361             break;
0362         }
0363     }
0364 
0365     auto catItem = new QStandardItem(category);
0366     QFont font = catItem->font();
0367     font.setBold(true);
0368     catItem->setFont(font);
0369     catItem->setData(kind, Qt::UserRole);
0370     catItem->setFlags(Qt::ItemIsEnabled);
0371     insertRow(pos, catItem);
0372 
0373     // Emit the parent so the view expand the item
0374     Q_EMIT parentAdded(indexFromItem(catItem));
0375 
0376     return catItem;
0377 }
0378 
0379 #include "moc_DevicesModel.cpp"