File indexing completed on 2025-01-19 03:50:42
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2002-16-10 0007 * Description : main digiKam interface implementation - Solid API based methods 0008 * 0009 * SPDX-FileCopyrightText: 2002-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0010 * 0011 * SPDX-License-Identifier: GPL-2.0-or-later 0012 * 0013 * ============================================================ */ 0014 0015 #include "digikamapp_p.h" 0016 0017 // Solid includes 0018 0019 #if defined(Q_CC_CLANG) 0020 # pragma clang diagnostic push 0021 # pragma clang diagnostic ignored "-Wnonportable-include-path" 0022 #endif 0023 0024 #include <solid/solidnamespace.h> 0025 #include <solid/camera.h> 0026 #include <solid/device.h> 0027 #include <solid/deviceinterface.h> 0028 #include <solid/devicenotifier.h> 0029 #include <solid/predicate.h> 0030 #include <solid/storageaccess.h> 0031 #include <solid/storagedrive.h> 0032 #include <solid/storagevolume.h> 0033 0034 #if defined(Q_CC_CLANG) 0035 # pragma clang diagnostic pop 0036 #endif 0037 0038 namespace Digikam 0039 { 0040 0041 // NOTE: static methods to not expose whole digiKam to Solid API. 0042 0043 bool s_checkSolidCamera(const Solid::Device& cameraDevice) 0044 { 0045 const Solid::Camera* const camera = cameraDevice.as<Solid::Camera>(); 0046 0047 if (!camera) 0048 { 0049 qCDebug(DIGIKAM_GENERAL_LOG) << "Solid device" << cameraDevice.description() << "is not a camera"; 0050 return false; 0051 } 0052 0053 QStringList drivers = camera->supportedDrivers(); 0054 0055 qCDebug(DIGIKAM_GENERAL_LOG) << "checkSolidCamera: Found Camera " 0056 << QString::fromUtf8("%1 %2").arg(cameraDevice.vendor()).arg(cameraDevice.product()) 0057 << " protocols " << camera->supportedProtocols() 0058 << " drivers " << camera->supportedDrivers(QLatin1String("ptp")); 0059 0060 // We handle gphoto2 cameras in this loop 0061 0062 if (!(camera->supportedDrivers().contains(QLatin1String("gphoto")) || 0063 camera->supportedProtocols().contains(QLatin1String("ptp")))) 0064 { 0065 return false; 0066 } 0067 0068 QVariant driverHandle = camera->driverHandle(QLatin1String("gphoto")); 0069 0070 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0071 0072 if (!driverHandle.canConvert(QMetaType(QMetaType::QVariantList))) 0073 0074 #else 0075 0076 if (!driverHandle.canConvert(QMetaType::QVariantList)) 0077 0078 #endif 0079 0080 { 0081 qCWarning(DIGIKAM_GENERAL_LOG) << "Solid returns unsupported driver handle for gphoto2"; 0082 return false; 0083 } 0084 0085 QList<QVariant> driverHandleList = driverHandle.toList(); 0086 0087 if ((driverHandleList.size() < 3) || 0088 (driverHandleList.at(0).toString() != QLatin1String("usb")) || 0089 0090 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0091 0092 !driverHandleList.at(1).canConvert(QMetaType(QMetaType::Int)) || 0093 !driverHandleList.at(2).canConvert(QMetaType(QMetaType::Int))) 0094 0095 #else 0096 0097 !driverHandleList.at(1).canConvert(QMetaType::Int) || 0098 !driverHandleList.at(2).canConvert(QMetaType::Int)) 0099 0100 #endif 0101 0102 { 0103 qCWarning(DIGIKAM_GENERAL_LOG) << "Solid returns unsupported driver handle for gphoto2"; 0104 return false; 0105 } 0106 0107 return true; 0108 } 0109 0110 QString s_labelForSolidCamera(const Solid::Device& cameraDevice) 0111 { 0112 QString vendor = cameraDevice.vendor(); 0113 QString product = cameraDevice.product(); 0114 0115 if (product == QLatin1String("USB Imaging Interface") || 0116 product == QLatin1String("USB Vendor Specific Interface")) 0117 { 0118 Solid::Device parentUsbDevice = cameraDevice.parent(); 0119 0120 if (parentUsbDevice.isValid()) 0121 { 0122 vendor = parentUsbDevice.vendor(); 0123 product = parentUsbDevice.product(); 0124 0125 if (!vendor.isEmpty() && !product.isEmpty()) 0126 { 0127 if (vendor == QLatin1String("Canon, Inc.")) 0128 { 0129 vendor = QLatin1String("Canon"); 0130 0131 if (product.startsWith(QLatin1String("Canon "))) 0132 { 0133 product = product.mid(6); // cut off another "Canon " from product 0134 } 0135 0136 if (product.endsWith(QLatin1String(" (ptp)"))) 0137 { 0138 product.chop(6); // cut off " (ptp)" 0139 } 0140 } 0141 else if (vendor == QLatin1String("Fuji Photo Film Co., Ltd")) 0142 { 0143 vendor = QLatin1String("Fuji"); 0144 } 0145 else if (vendor == QLatin1String("Nikon Corp.")) 0146 { 0147 vendor = QLatin1String("Nikon"); 0148 0149 if (product.startsWith(QLatin1String("NIKON "))) 0150 { 0151 product = product.mid(6); 0152 } 0153 } 0154 } 0155 } 0156 } 0157 0158 return vendor + QLatin1Char(' ') + product; 0159 } 0160 0161 // -------------------------------------------------------------------------------------------------- 0162 0163 void DigikamApp::fillSolidMenus() 0164 { 0165 QHash<QString, QDateTime> newAppearanceTimes; 0166 d->usbMediaMenu->clear(); 0167 d->cardReaderMenu->clear(); 0168 0169 // delete the actionGroups to avoid duplicate menu entries 0170 0171 delete d->solidUsmActionGroup; 0172 delete d->solidCameraActionGroup; 0173 0174 d->solidCameraActionGroup = new QActionGroup(this); 0175 0176 connect(d->solidCameraActionGroup, SIGNAL(triggered(QAction*)), 0177 this, SLOT(slotOpenSolidCamera(QAction*))); 0178 0179 d->solidUsmActionGroup = new QActionGroup(this); 0180 0181 connect(d->solidUsmActionGroup, SIGNAL(triggered(QAction*)), 0182 this, SLOT(slotOpenSolidUsmDevice(QAction*))); 0183 0184 // -------------------------------------------------------- 0185 0186 QList<Solid::Device> cameraDevices = Solid::Device::listFromType(Solid::DeviceInterface::Camera); 0187 0188 Q_FOREACH (const Solid::Device& cameraDevice, cameraDevices) 0189 { 0190 // USM camera: will be handled below 0191 0192 if (cameraDevice.is<Solid::StorageAccess>()) 0193 { 0194 continue; 0195 } 0196 0197 if (!s_checkSolidCamera(cameraDevice)) 0198 { 0199 continue; 0200 } 0201 0202 // -------------------------------------------------------- 0203 0204 QString l = s_labelForSolidCamera(cameraDevice); 0205 QString label = CameraNameHelper::cameraNameAutoDetected(l.trimmed()); 0206 0207 // -------------------------------------------------------- 0208 0209 QString iconName = cameraDevice.icon(); 0210 0211 if (iconName.isEmpty()) 0212 { 0213 iconName = QLatin1String("camera-photo"); 0214 } 0215 0216 QAction* const action = new QAction(label, d->solidCameraActionGroup); 0217 0218 action->setIcon(QIcon::fromTheme(iconName)); 0219 0220 // set data to identify device in action slot slotSolidSetupDevice 0221 0222 action->setData(cameraDevice.udi()); 0223 newAppearanceTimes[cameraDevice.udi()] = d->cameraAppearanceTimes.contains(cameraDevice.udi()) ? 0224 d->cameraAppearanceTimes.value(cameraDevice.udi()) : 0225 QDateTime::currentDateTime(); 0226 0227 d->cameraMenu->addAction(action); 0228 } 0229 0230 QList<Solid::Device> storageDevices = Solid::Device::listFromType(Solid::DeviceInterface::StorageAccess); 0231 0232 Q_FOREACH (const Solid::Device& accessDevice, storageDevices) 0233 { 0234 // check for StorageAccess 0235 0236 if (!accessDevice.is<Solid::StorageAccess>()) 0237 { 0238 continue; 0239 } 0240 0241 // check for StorageDrive 0242 0243 Solid::Device driveDevice; 0244 0245 for (Solid::Device currentDevice = accessDevice ; 0246 currentDevice.isValid() ; 0247 currentDevice = currentDevice.parent()) 0248 { 0249 if (currentDevice.is<Solid::StorageDrive>()) 0250 { 0251 driveDevice = currentDevice; 0252 break; 0253 } 0254 } 0255 0256 if (!driveDevice.isValid()) 0257 { 0258 continue; 0259 } 0260 0261 const Solid::StorageDrive* const drive = driveDevice.as<Solid::StorageDrive>(); 0262 0263 QString driveType; 0264 0265 bool isHarddisk = false; 0266 0267 switch (drive->driveType()) 0268 { 0269 // skip these 0270 0271 case Solid::StorageDrive::CdromDrive: 0272 case Solid::StorageDrive::Floppy: 0273 case Solid::StorageDrive::Tape: 0274 default: 0275 continue; 0276 0277 // accept card readers 0278 0279 case Solid::StorageDrive::CompactFlash: 0280 driveType = i18n("CompactFlash Card Reader"); 0281 break; 0282 0283 case Solid::StorageDrive::MemoryStick: 0284 driveType = i18n("Memory Stick Reader"); 0285 break; 0286 0287 case Solid::StorageDrive::SmartMedia: 0288 driveType = i18n("SmartMedia Card Reader"); 0289 break; 0290 0291 case Solid::StorageDrive::SdMmc: 0292 driveType = i18n("SD / MMC Card Reader"); 0293 break; 0294 0295 case Solid::StorageDrive::Xd: 0296 driveType = i18n("xD Card Reader"); 0297 break; 0298 0299 case Solid::StorageDrive::HardDisk: 0300 0301 // We don't want to list HardDisk partitions, but USB Mass Storage devices. 0302 // Don't know what is the exact difference between removable and hotpluggable. 0303 0304 if (drive->isRemovable() || drive->isHotpluggable()) 0305 { 0306 isHarddisk = true; 0307 0308 if (drive->bus() == Solid::StorageDrive::Usb) 0309 { 0310 driveType = i18n("USB Disk"); 0311 } 0312 else 0313 { 0314 driveType = i18nc("non-USB removable storage device", "Disk"); 0315 } 0316 0317 break; 0318 } 0319 else 0320 { 0321 continue; 0322 } 0323 } 0324 0325 // check for StorageVolume 0326 0327 Solid::Device volumeDevice; 0328 0329 for (Solid::Device currentDevice = accessDevice ; 0330 currentDevice.isValid() ; 0331 currentDevice = currentDevice.parent()) 0332 { 0333 if (currentDevice.is<Solid::StorageVolume>()) 0334 { 0335 volumeDevice = currentDevice; 0336 break; 0337 } 0338 } 0339 0340 if (!volumeDevice.isValid()) 0341 { 0342 continue; 0343 } 0344 0345 bool isCamera = accessDevice.is<Solid::Camera>(); 0346 const Solid::StorageAccess* const access = accessDevice.as<Solid::StorageAccess>(); 0347 const Solid::StorageVolume* const volume = volumeDevice.as<Solid::StorageVolume>(); 0348 0349 if (volume->isIgnored()) 0350 { 0351 continue; 0352 } 0353 0354 QString label; 0355 0356 if (isCamera) 0357 { 0358 label = accessDevice.vendor() + QLatin1Char(' ') + accessDevice.product(); 0359 } 0360 else 0361 { 0362 QString labelOrProduct; 0363 0364 if (!volume->label().isEmpty()) 0365 { 0366 labelOrProduct = volume->label(); 0367 } 0368 else if (!volumeDevice.product().isEmpty()) 0369 { 0370 labelOrProduct = volumeDevice.product(); 0371 } 0372 else if (!volumeDevice.vendor().isEmpty()) 0373 { 0374 labelOrProduct = volumeDevice.vendor(); 0375 } 0376 else if (!driveDevice.product().isEmpty()) 0377 { 0378 labelOrProduct = driveDevice.product(); 0379 } 0380 0381 if (!labelOrProduct.isNull()) 0382 { 0383 if (!access->filePath().isEmpty()) 0384 { 0385 label += i18nc("<drive type> \"<device name or label>\" at <mount path>", 0386 "%1 \"%2\" at %3", driveType, labelOrProduct, 0387 QDir::toNativeSeparators(access->filePath())); 0388 } 0389 else 0390 { 0391 label += i18nc("<drive type> \"<device name or label>\"", 0392 "%1 \"%2\"", driveType, labelOrProduct); 0393 } 0394 } 0395 else 0396 { 0397 if (!access->filePath().isEmpty()) 0398 { 0399 label += i18nc("<drive type> at <mount path>", 0400 "%1 at %2", driveType, 0401 QDir::toNativeSeparators(access->filePath())); 0402 } 0403 else 0404 { 0405 label += driveType; 0406 } 0407 } 0408 0409 if (volume->size()) 0410 { 0411 label += i18nc("device label etc... (<formatted byte size>)", 0412 " (%1)", ItemPropertiesTab::humanReadableBytesCount(volume->size())); 0413 } 0414 } 0415 0416 QString iconName; 0417 0418 if (!driveDevice.icon().isEmpty()) 0419 { 0420 iconName = driveDevice.icon(); 0421 } 0422 else if (!accessDevice.icon().isEmpty()) 0423 { 0424 iconName = accessDevice.icon(); 0425 } 0426 else if (!volumeDevice.icon().isEmpty()) 0427 { 0428 iconName = volumeDevice.icon(); 0429 } 0430 0431 QAction* const action = new QAction(label, d->solidUsmActionGroup); 0432 0433 if (!iconName.isEmpty()) 0434 { 0435 action->setIcon(QIcon::fromTheme(iconName)); 0436 } 0437 0438 // set data to identify device in action slot slotSolidSetupDevice 0439 0440 action->setData(accessDevice.udi()); 0441 newAppearanceTimes[accessDevice.udi()] = d->cameraAppearanceTimes.contains(accessDevice.udi()) ? 0442 d->cameraAppearanceTimes.value(accessDevice.udi()) : 0443 QDateTime::currentDateTime(); 0444 0445 if (isCamera) 0446 { 0447 d->cameraMenu->addAction(action); 0448 } 0449 0450 if (isHarddisk) 0451 { 0452 d->usbMediaMenu->addAction(action); 0453 } 0454 else 0455 { 0456 d->cardReaderMenu->addAction(action); 0457 } 0458 } 0459 0460 /* 0461 //TODO: Find best usable solution when no devices are connected: One entry, hide, or disable? 0462 0463 // Add one entry telling that no device is available 0464 0465 if (d->cameraSolidMenu->isEmpty()) 0466 { 0467 QAction* const action = d->cameraSolidMenu->addAction(i18n("No Camera Connected")); 0468 action->setEnabled(false); 0469 } 0470 0471 if (d->usbMediaMenu->isEmpty()) 0472 { 0473 QAction* const action = d->usbMediaMenu->addAction(i18n("No Storage Devices Found")); 0474 action->setEnabled(false); 0475 } 0476 0477 if (d->cardReaderMenu->isEmpty()) 0478 { 0479 QAction* const action = d->cardReaderMenu->addAction(i18n("No Card Readers Available")); 0480 action->setEnabled(false); 0481 } 0482 0483 // hide empty menus 0484 0485 d->cameraSolidMenu->menuAction()->setVisible(!d->cameraSolidMenu->isEmpty()); 0486 d->usbMediaMenu->menuAction()->setVisible(!d->usbMediaMenu->isEmpty()); 0487 d->cardReaderMenu->menuAction()->setVisible(!d->cardReaderMenu->isEmpty()); 0488 */ 0489 0490 d->cameraAppearanceTimes = newAppearanceTimes; 0491 0492 // disable empty menus 0493 0494 d->usbMediaMenu->setEnabled(!d->usbMediaMenu->isEmpty()); 0495 d->cardReaderMenu->setEnabled(!d->cardReaderMenu->isEmpty()); 0496 0497 updateCameraMenu(); 0498 updateQuickImportAction(); 0499 } 0500 0501 void DigikamApp::connectToSolidNotifiers() 0502 { 0503 connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceAdded(QString)), 0504 this, SLOT(slotSolidDeviceChanged(QString)), 0505 Qt::QueuedConnection); 0506 0507 connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceRemoved(QString)), 0508 this, SLOT(slotSolidDeviceChanged(QString)), 0509 Qt::QueuedConnection); 0510 0511 // -- queued connections ------------------------------------------- 0512 0513 connect(this, SIGNAL(queuedOpenCameraUiFromPath(QString)), 0514 this, SLOT(slotOpenCameraUiFromPath(QString)), 0515 Qt::QueuedConnection); 0516 0517 connect(this, SIGNAL(queuedOpenSolidDevice(QString)), 0518 this, SLOT(slotOpenSolidDevice(QString)), 0519 Qt::QueuedConnection); 0520 } 0521 0522 void DigikamApp::openSolidCamera(const QString& udi, const QString& cameraLabel) 0523 { 0524 // if there is already an open ImportUI for the device, show and raise it, and be done 0525 0526 if (d->cameraUIMap.contains(udi)) 0527 { 0528 ImportUI* const ui = d->cameraUIMap.value(udi); 0529 0530 if (ui && !ui->isClosed()) 0531 { 0532 ui->unminimizeAndActivateWindow(); 0533 0534 return; 0535 } 0536 } 0537 0538 // recreate device from unambiguous UDI 0539 0540 Solid::Device device(udi); 0541 0542 if (device.isValid()) 0543 { 0544 if (cameraLabel.isNull()) 0545 { 0546 QString label = s_labelForSolidCamera(device); 0547 } 0548 0549 Solid::Camera* const camera = device.as<Solid::Camera>(); 0550 QList<QVariant> list = camera->driverHandle(QLatin1String("gphoto")).toList(); 0551 0552 // all sanity checks have already been done when creating the action 0553 0554 if (list.size() < 3) 0555 { 0556 return; 0557 } 0558 0559 // NOTE: See bug #262296: With KDE 4.6, Solid API return device vendor id 0560 // and product id in hexadecimal strings. 0561 0562 bool ok; 0563 int vendorId = list.at(1).toString().toInt(&ok, 16); 0564 int productId = list.at(2).toString().toInt(&ok, 16); 0565 QString model, port; 0566 0567 if (CameraList::findConnectedCamera(vendorId, productId, model, port)) 0568 { 0569 qCDebug(DIGIKAM_GENERAL_LOG) << "Found camera from ids " << vendorId << " " << productId 0570 << " camera is: " << model << " at " << port; 0571 0572 // the ImportUI will delete itself when it has finished 0573 0574 ImportUI* const cgui = new ImportUI(cameraLabel, model, port, QLatin1String("/"), 1); 0575 d->cameraUIMap[udi] = cgui; 0576 0577 cgui->show(); 0578 0579 connect(cgui, SIGNAL(signalLastDestination(QUrl)), 0580 d->view, SLOT(slotSelectAlbum(QUrl))); 0581 } 0582 else 0583 { 0584 qCDebug(DIGIKAM_GENERAL_LOG) << "Failed to detect camera with GPhoto2 from Solid information"; 0585 } 0586 } 0587 } 0588 0589 void DigikamApp::openSolidUsmDevice(const QString& udi, const QString& givenLabel) 0590 { 0591 QString mediaLabel = givenLabel; 0592 0593 // if there is already an open ImportUI for the device, show and raise it 0594 0595 if (d->cameraUIMap.contains(udi)) 0596 { 0597 ImportUI* const ui = d->cameraUIMap.value(udi); 0598 0599 if (ui && !ui->isClosed()) 0600 { 0601 ui->unminimizeAndActivateWindow(); 0602 0603 return; 0604 } 0605 } 0606 0607 // recreate device from unambiguous UDI 0608 0609 Solid::Device device(udi); 0610 0611 if (device.isValid()) 0612 { 0613 Solid::StorageAccess* const access = device.as<Solid::StorageAccess>(); 0614 0615 if (!access) 0616 { 0617 return; 0618 } 0619 0620 if (!access->isAccessible()) 0621 { 0622 QApplication::setOverrideCursor(Qt::WaitCursor); 0623 0624 if (!access->setup()) 0625 { 0626 return; 0627 } 0628 0629 d->eventLoop = new QEventLoop(this); 0630 0631 // NOTE: Lambda function to not expose whole digiKam to Solid API. 0632 0633 connect(access, &Solid::StorageAccess::setupDone, 0634 this, [=](Solid::ErrorType errorType, QVariant errorData, const QString& /*udi*/) // clazy:exclude=function-args-by-ref 0635 { 0636 if (!d->eventLoop) 0637 { 0638 return; 0639 } 0640 0641 if (errorType == Solid::NoError) 0642 { 0643 d->eventLoop->exit(0); 0644 } 0645 else 0646 { 0647 d->solidErrorMessage = i18n("Cannot access the storage device.\n"); 0648 d->solidErrorMessage += errorData.toString(); 0649 d->eventLoop->exit(1); 0650 } 0651 } 0652 ); 0653 0654 int returnCode = d->eventLoop->exec(QEventLoop::ExcludeUserInputEvents); 0655 0656 delete d->eventLoop; 0657 d->eventLoop = nullptr; 0658 QApplication::restoreOverrideCursor(); 0659 0660 if (returnCode == 1) 0661 { 0662 QMessageBox::critical(this, qApp->applicationName(), d->solidErrorMessage); 0663 return; 0664 } 0665 } 0666 0667 // Create Camera UI 0668 0669 QString path = QDir::fromNativeSeparators(access->filePath()); 0670 0671 if (mediaLabel.isNull()) 0672 { 0673 mediaLabel = path; 0674 } 0675 0676 // the ImportUI will delete itself when it has finished 0677 0678 ImportUI* const cgui = new ImportUI(i18n("Images on %1", mediaLabel), 0679 QLatin1String("directory browse"), 0680 QLatin1String("Fixed"), path, 1); 0681 d->cameraUIMap[udi] = cgui; 0682 0683 cgui->show(); 0684 0685 connect(cgui, SIGNAL(signalLastDestination(QUrl)), 0686 d->view, SLOT(slotSelectAlbum(QUrl))); 0687 } 0688 } 0689 0690 void DigikamApp::slotOpenSolidCamera(QAction* action) 0691 { 0692 QString udi = action->data().toString(); 0693 openSolidCamera(udi, action->iconText()); 0694 } 0695 0696 void DigikamApp::slotOpenSolidUsmDevice(QAction* action) 0697 { 0698 QString udi = action->data().toString(); 0699 openSolidUsmDevice(udi, action->iconText()); 0700 } 0701 0702 void DigikamApp::slotOpenSolidDevice(const QString& udi) 0703 { 0704 // Identifies device as either Camera or StorageAccess and calls methods accordingly 0705 0706 Solid::Device device(udi); 0707 0708 if (!device.isValid()) 0709 { 0710 QMessageBox::critical(this, qApp->applicationName(), 0711 i18n("The specified device (\"%1\") is not valid.", udi)); 0712 return; 0713 } 0714 0715 if (device.is<Solid::StorageAccess>()) 0716 { 0717 openSolidUsmDevice(udi); 0718 } 0719 else if (device.is<Solid::Camera>()) 0720 { 0721 if (!s_checkSolidCamera(device)) 0722 { 0723 QMessageBox::critical(this, qApp->applicationName(), 0724 i18n("The specified camera (\"%1\") is not supported.", udi)); 0725 return; 0726 } 0727 0728 openSolidCamera(udi); 0729 } 0730 } 0731 0732 void DigikamApp::slotSolidDeviceChanged(const QString& udi) 0733 { 0734 qCDebug(DIGIKAM_GENERAL_LOG) << "slotSolidDeviceChanged:" << udi; 0735 fillSolidMenus(); 0736 } 0737 0738 } // namespace Digikam