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