File indexing completed on 2024-04-28 17:06:05
0001 /* 0002 SPDX-FileCopyrightText: 2006 Csaba Karai <cskarai@freemail.hu> 0003 SPDX-FileCopyrightText: 2006-2022 Krusader Krew <https://krusader.org> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "mediabutton.h" 0009 0010 #include "../MountMan/kmountman.h" 0011 #include "../icon.h" 0012 #include "../krglobal.h" 0013 0014 // QtCore 0015 #include <QEvent> 0016 // QtGui 0017 #include <QCursor> 0018 #include <QMouseEvent> 0019 0020 #include <KConfigCore/KConfigGroup> 0021 #include <KConfigCore/KSharedConfig> 0022 #include <KI18n/KLocalizedString> 0023 #include <KIO/Global> 0024 #include <KIOCore/KMountPoint> 0025 #include <KWidgetsAddons/KMessageBox> 0026 0027 #include <Solid/DeviceInterface> 0028 #include <Solid/DeviceNotifier> 0029 #include <Solid/OpticalDisc> 0030 #include <Solid/OpticalDrive> 0031 #include <Solid/StorageAccess> 0032 #include <Solid/StorageVolume> 0033 #include <utility> 0034 0035 QString MediaButton::remotePrefix = QLatin1String("remote:"); 0036 0037 MediaButton::MediaButton(QWidget *parent) 0038 : QToolButton(parent) 0039 , popupMenu(nullptr) 0040 , rightMenu(nullptr) 0041 , openInNewTab(false) 0042 { 0043 setAutoRaise(true); 0044 setIcon(Icon("system-file-manager")); 0045 setText(i18n("Open the available media list")); 0046 setToolTip(i18n("Open the available media list")); 0047 setPopupMode(QToolButton::InstantPopup); 0048 setAcceptDrops(false); 0049 0050 popupMenu = new QMenu(this); 0051 popupMenu->installEventFilter(this); 0052 Q_CHECK_PTR(popupMenu); 0053 0054 setMenu(popupMenu); 0055 0056 connect(popupMenu, &QMenu::aboutToShow, this, &MediaButton::slotAboutToShow); 0057 connect(popupMenu, &QMenu::aboutToHide, this, &MediaButton::slotAboutToHide); 0058 connect(popupMenu, &QMenu::triggered, this, &MediaButton::slotPopupActivated); 0059 0060 Solid::DeviceNotifier *notifier = Solid::DeviceNotifier::instance(); 0061 connect(notifier, &Solid::DeviceNotifier::deviceAdded, this, &MediaButton::slotDeviceAdded); 0062 connect(notifier, &Solid::DeviceNotifier::deviceRemoved, this, &MediaButton::slotDeviceRemoved); 0063 0064 connect(&mountCheckerTimer, &QTimer::timeout, this, &MediaButton::slotCheckMounts); 0065 } 0066 0067 MediaButton::~MediaButton() = default; 0068 0069 void MediaButton::updateIcon(const QString &mountPoint) 0070 { 0071 if (!mountPoint.isEmpty() && mountPoint == currentMountPoint) 0072 return; 0073 0074 currentMountPoint = mountPoint; 0075 0076 QString iconName("system-file-manager"); 0077 QStringList overlays; 0078 0079 if (!mountPoint.isEmpty()) { 0080 Solid::Device device(krMtMan.findUdiForPath(mountPoint, Solid::DeviceInterface::StorageAccess)); 0081 auto *vol = device.as<Solid::StorageVolume>(); 0082 0083 if (device.isValid()) 0084 iconName = device.icon(); 0085 if (vol && vol->usage() == Solid::StorageVolume::Encrypted) 0086 overlays << "security-high"; 0087 } 0088 setIcon(Icon(iconName, overlays)); 0089 } 0090 0091 void MediaButton::slotAboutToShow() 0092 { 0093 emit aboutToShow(); 0094 0095 popupMenu->clear(); 0096 udiNameMap.clear(); 0097 0098 createMediaList(); 0099 } 0100 0101 void MediaButton::slotAboutToHide() 0102 { 0103 if (rightMenu) 0104 rightMenu->close(); 0105 mountCheckerTimer.stop(); 0106 } 0107 0108 void MediaButton::createMediaList() 0109 { 0110 // devices detected by solid 0111 storageDevices = Solid::Device::listFromType(Solid::DeviceInterface::StorageAccess); 0112 0113 for (int p = storageDevices.count() - 1; p >= 0; p--) { 0114 Solid::Device device = storageDevices[p]; 0115 QString udi = device.udi(); 0116 0117 QString name; 0118 QIcon kdeIcon; 0119 if (!getNameAndIcon(device, name, kdeIcon)) 0120 continue; 0121 0122 QAction *act = popupMenu->addAction(kdeIcon, name); 0123 act->setData(QVariant(udi)); 0124 udiNameMap[udi] = name; 0125 0126 connect(device.as<Solid::StorageAccess>(), &Solid::StorageAccess::accessibilityChanged, this, &MediaButton::slotAccessibilityChanged); 0127 } 0128 0129 KMountPoint::List possibleMountList = KMountPoint::possibleMountPoints(); 0130 KMountPoint::List currentMountList = KMountPoint::currentMountPoints(); 0131 0132 for (auto &it : possibleMountList) { 0133 if (krMtMan.networkFilesystem(it->mountType())) { 0134 bool mounted = false; 0135 0136 for (auto &it2 : currentMountList) { 0137 if (krMtMan.networkFilesystem(it2->mountType()) && it->mountPoint() == it2->mountPoint()) { 0138 mounted = true; 0139 break; 0140 } 0141 } 0142 0143 QString name = i18nc("%1 is the mount point of the remote share", "Remote Share [%1]", it->mountPoint()); 0144 QStringList overlays; 0145 if (mounted) 0146 overlays << "emblem-mounted"; 0147 QAction *act = popupMenu->addAction(Icon("network-wired", overlays), name); 0148 QString udi = remotePrefix + it->mountPoint(); 0149 act->setData(QVariant(udi)); 0150 } 0151 } 0152 0153 mountCheckerTimer.setSingleShot(true); 0154 mountCheckerTimer.start(1000); 0155 } 0156 0157 bool MediaButton::getNameAndIcon(Solid::Device &device, QString &name, QIcon &iconOut) 0158 { 0159 auto *access = device.as<Solid::StorageAccess>(); 0160 if (access == nullptr) 0161 return false; 0162 0163 QString label = i18nc("Unknown label", "Unknown"); 0164 bool mounted = access->isAccessible(); 0165 QString path = access->filePath(); 0166 QString type = i18nc("Unknown media type", "Unknown"); 0167 QString iconName = device.icon(); 0168 QString fstype; 0169 QString size; 0170 0171 auto *vol = device.as<Solid::StorageVolume>(); 0172 if (vol) { 0173 label = vol->label(); 0174 fstype = vol->fsType(); 0175 size = KIO::convertSize(vol->size()); 0176 } 0177 0178 bool printSize = false; 0179 0180 if (iconName == "media-floppy") 0181 type = i18n("Floppy"); 0182 else if (iconName == "drive-optical") 0183 type = i18n("CD/DVD-ROM"); 0184 else if (iconName == "drive-removable-media-usb-pendrive") 0185 type = i18n("USB pen drive"), printSize = true; 0186 else if (iconName == "drive-removable-media-usb") 0187 type = i18n("USB device"), printSize = true; 0188 else if (iconName == "drive-removable-media") 0189 type = i18n("Removable media"), printSize = true; 0190 else if (iconName == "drive-harddisk") 0191 type = i18n("Hard Disk"), printSize = true; 0192 else if (iconName == "camera-photo") 0193 type = i18n("Camera"); 0194 else if (iconName == "media-optical-video") 0195 type = i18n("Video CD/DVD-ROM"); 0196 else if (iconName == "media-optical-audio") 0197 type = i18n("Audio CD/DVD-ROM"); 0198 else if (iconName == "media-optical") 0199 type = i18n("Recordable CD/DVD-ROM"); 0200 0201 KConfigGroup cfg(KSharedConfig::openConfig(), QStringLiteral("MediaMenu")); 0202 0203 if (fstype == "squashfs" && cfg.readEntry("HideSquashFS", false)) { 0204 return false; 0205 } 0206 0207 if (cfg.readEntry("Hidden Mountpoints", QStringList()).contains(path)) { 0208 return false; 0209 } 0210 0211 if (printSize) { 0212 QString showSizeSetting = cfg.readEntry("ShowSize", "Always"); 0213 if (showSizeSetting == "WhenNoLabel") 0214 printSize = label.isEmpty(); 0215 else if (showSizeSetting == "Never") 0216 printSize = false; 0217 } 0218 0219 if (printSize && !size.isEmpty()) 0220 name += size + ' '; 0221 if (!label.isEmpty()) 0222 name += label + ' '; 0223 else 0224 name += type + ' '; 0225 if (!fstype.isEmpty() && cfg.readEntry("ShowFSType", true)) 0226 name += '(' + fstype + ") "; 0227 if (!path.isEmpty() && cfg.readEntry("ShowPath", true)) 0228 name += '[' + path + "] "; 0229 0230 name = name.trimmed(); 0231 0232 QStringList overlays; 0233 if (mounted) { 0234 overlays << "emblem-mounted"; 0235 } else { 0236 overlays << QString(); // We have to guarantee the placement of the next emblem 0237 } 0238 if (vol && vol->usage() == Solid::StorageVolume::Encrypted) { 0239 overlays << "security-high"; 0240 } 0241 iconOut = Icon(iconName, overlays); 0242 return true; 0243 } 0244 0245 void MediaButton::slotPopupActivated(QAction *action) 0246 { 0247 if (action && action->data().canConvert<QString>()) { 0248 QString udi = action->data().toString(); 0249 if (!udi.isEmpty()) { 0250 bool mounted = false; 0251 QString mountPoint; 0252 getStatus(udi, mounted, &mountPoint); 0253 0254 if (mounted) 0255 emit openUrl(QUrl::fromLocalFile(mountPoint)); 0256 else 0257 mount(udi, true); 0258 } 0259 } 0260 } 0261 0262 void MediaButton::showMenu() 0263 { 0264 QMenu *pP = menu(); 0265 if (pP) { 0266 menu()->exec(mapToGlobal(QPoint(0, height()))); 0267 } 0268 } 0269 0270 bool MediaButton::eventFilter(QObject *o, QEvent *e) 0271 { 0272 if (o == popupMenu) { 0273 if (e->type() == QEvent::ContextMenu) { 0274 QAction *act = popupMenu->activeAction(); 0275 if (act && act->data().canConvert<QString>()) { 0276 QString id = act->data().toString(); 0277 if (!id.isEmpty()) { 0278 QPoint globalPos = popupMenu->mapToGlobal(popupMenu->actionGeometry(act).topRight()); 0279 rightClickMenu(id, globalPos); 0280 return true; 0281 } 0282 } 0283 } else if (e->type() == QEvent::KeyPress) { 0284 auto *ke = dynamic_cast<QKeyEvent *>(e); 0285 if (ke->key() == Qt::Key_Return && ke->modifiers() == Qt::ControlModifier) { 0286 if (QAction *act = popupMenu->activeAction()) { 0287 QString id = act->data().toString(); 0288 if (!id.isEmpty()) { 0289 toggleMount(id); 0290 return true; 0291 } 0292 } 0293 } 0294 } else if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonRelease) { 0295 auto *m = dynamic_cast<QMouseEvent *>(e); 0296 if (m->button() == Qt::RightButton) { 0297 if (e->type() == QEvent::MouseButtonPress) { 0298 QAction *act = popupMenu->actionAt(m->pos()); 0299 if (act && act->data().canConvert<QString>()) { 0300 QString id = act->data().toString(); 0301 if (!id.isEmpty()) 0302 rightClickMenu(id, m->globalPos()); 0303 } 0304 } 0305 m->accept(); 0306 return true; 0307 } 0308 } 0309 } 0310 return false; 0311 } 0312 0313 void MediaButton::rightClickMenu(const QString &udi, QPoint pos) 0314 { 0315 if (rightMenu) 0316 rightMenu->close(); 0317 0318 bool ejectable = false; 0319 bool mounted = false; 0320 QString mountPoint; 0321 0322 getStatus(udi, mounted, &mountPoint, &ejectable); 0323 0324 QUrl openURL = QUrl::fromLocalFile(mountPoint); 0325 0326 QMenu *myMenu = rightMenu = new QMenu(popupMenu); 0327 QAction *actOpen = myMenu->addAction(i18n("Open")); 0328 actOpen->setData(QVariant(1)); 0329 QAction *actOpenNewTab = myMenu->addAction(i18n("Open in a new tab")); 0330 actOpenNewTab->setData(QVariant(2)); 0331 0332 myMenu->addSeparator(); 0333 if (!mounted) { 0334 QAction *actMount = myMenu->addAction(i18n("Mount")); 0335 actMount->setData(QVariant(3)); 0336 } else { 0337 QAction *actUnmount = myMenu->addAction(i18n("Unmount")); 0338 actUnmount->setData(QVariant(4)); 0339 } 0340 if (ejectable) { 0341 QAction *actEject = myMenu->addAction(i18n("Eject")); 0342 actEject->setData(QVariant(5)); 0343 } 0344 0345 QAction *act = myMenu->exec(pos); 0346 int result = -1; 0347 if (act != nullptr && act->data().canConvert<int>()) 0348 result = act->data().toInt(); 0349 0350 delete myMenu; 0351 if (rightMenu == myMenu) 0352 rightMenu = nullptr; 0353 else 0354 return; 0355 0356 switch (result) { 0357 case 1: 0358 case 2: 0359 popupMenu->close(); 0360 if (mounted) { 0361 if (result == 1) 0362 emit openUrl(openURL); 0363 else 0364 emit newTab(openURL); 0365 } else { 0366 mount(udi, true, result == 2); // mount first, when mounted open the tab 0367 } 0368 break; 0369 case 3: 0370 mount(udi); 0371 break; 0372 case 4: 0373 umount(udi); 0374 break; 0375 case 5: 0376 eject(udi); 0377 break; 0378 default: 0379 break; 0380 } 0381 } 0382 0383 void MediaButton::toggleMount(const QString &udi) 0384 { 0385 bool mounted = false; 0386 getStatus(udi, mounted); 0387 0388 if (mounted) 0389 umount(udi); 0390 else 0391 mount(udi); 0392 } 0393 0394 void MediaButton::getStatus(const QString &udi, bool &mounted, QString *mountPointOut, bool *ejectableOut) 0395 { 0396 mounted = false; 0397 bool network = udi.startsWith(remotePrefix); 0398 bool ejectable = false; 0399 QString mountPoint; 0400 0401 if (network) { 0402 mountPoint = udi.mid(remotePrefix.length()); 0403 0404 KMountPoint::List currentMountList = KMountPoint::currentMountPoints(); 0405 for (auto &it : currentMountList) { 0406 if (krMtMan.networkFilesystem(it->mountType()) && it->mountPoint() == mountPoint) { 0407 mounted = true; 0408 break; 0409 } 0410 } 0411 } else { 0412 Solid::Device device(udi); 0413 0414 auto *access = device.as<Solid::StorageAccess>(); 0415 auto *optdisc = device.as<Solid::OpticalDisc>(); 0416 if (access) 0417 mountPoint = access->filePath(); 0418 if (access && access->isAccessible()) 0419 mounted = true; 0420 if (optdisc) 0421 ejectable = true; 0422 } 0423 0424 if (mountPointOut) 0425 *mountPointOut = mountPoint; 0426 if (ejectableOut) 0427 *ejectableOut = ejectable; 0428 } 0429 0430 void MediaButton::mount(const QString &udi, bool open, bool newtab) 0431 { 0432 if (udi.startsWith(remotePrefix)) { 0433 QString mp = udi.mid(remotePrefix.length()); 0434 krMtMan.mount(mp, true); 0435 if (newtab) 0436 emit newTab(QUrl::fromLocalFile(mp)); 0437 else 0438 emit openUrl(QUrl::fromLocalFile(mp)); 0439 return; 0440 } 0441 Solid::Device device(udi); 0442 auto *access = device.as<Solid::StorageAccess>(); 0443 if (access && !access->isAccessible()) { 0444 if (open) 0445 udiToOpen = device.udi(), openInNewTab = newtab; 0446 connect(access, &Solid::StorageAccess::setupDone, this, &MediaButton::slotSetupDone); 0447 connect(access, &Solid::StorageAccess::teardownRequested, &krMtMan, &KMountMan::slotTeardownRequested, Qt::UniqueConnection); 0448 access->setup(); 0449 } 0450 } 0451 0452 void MediaButton::slotSetupDone(Solid::ErrorType error, const QVariant &errorData, const QString &udi) 0453 { 0454 if (error == Solid::NoError) { 0455 if (udi == udiToOpen) { 0456 auto *access = Solid::Device(udi).as<Solid::StorageAccess>(); 0457 if (access && access->isAccessible()) { 0458 if (openInNewTab) 0459 emit newTab(QUrl::fromLocalFile(access->filePath())); 0460 else 0461 emit openUrl(QUrl::fromLocalFile(access->filePath())); 0462 } 0463 udiToOpen = QString(), openInNewTab = false; 0464 } 0465 } else { 0466 if (udi == udiToOpen) 0467 udiToOpen = QString(), openInNewTab = false; 0468 QString name; 0469 if (udiNameMap.contains(udi)) 0470 name = udiNameMap[udi]; 0471 0472 if (errorData.isValid()) { 0473 KMessageBox::error(this, i18n("An error occurred while accessing '%1', the system responded: %2", name, errorData.toString())); 0474 } else { 0475 KMessageBox::error(this, i18n("An error occurred while accessing '%1'", name)); 0476 } 0477 } 0478 } 0479 0480 void MediaButton::umount(const QString &udi) 0481 { 0482 if (udi.startsWith(remotePrefix)) { 0483 krMtMan.unmount(udi.mid(remotePrefix.length()), false); 0484 return; 0485 } 0486 krMtMan.unmount(krMtMan.pathForUdi(udi), false); 0487 } 0488 0489 void MediaButton::eject(QString udi) 0490 { 0491 krMtMan.eject(krMtMan.pathForUdi(std::move(udi))); 0492 } 0493 0494 void MediaButton::slotAccessibilityChanged(bool /*accessible*/, const QString &udi) 0495 { 0496 QList<QAction *> actionList = popupMenu->actions(); 0497 foreach (QAction *act, actionList) { 0498 if (act && act->data().canConvert<QString>() && act->data().toString() == udi) { 0499 Solid::Device device(udi); 0500 0501 QString name; 0502 QIcon kdeIcon; 0503 if (getNameAndIcon(device, name, kdeIcon)) { 0504 act->setText(name); 0505 act->setIcon(kdeIcon); 0506 } 0507 break; 0508 } 0509 } 0510 } 0511 0512 void MediaButton::slotDeviceAdded(const QString &udi) 0513 { 0514 if (popupMenu->isHidden()) 0515 return; 0516 0517 Solid::Device device(udi); 0518 auto *access = device.as<Solid::StorageAccess>(); 0519 if (access == nullptr) 0520 return; 0521 0522 QString name; 0523 QIcon kdeIcon; 0524 if (!getNameAndIcon(device, name, kdeIcon)) 0525 return; 0526 0527 QAction *act = popupMenu->addAction(kdeIcon, name); 0528 act->setData(QVariant(udi)); 0529 udiNameMap[udi] = name; 0530 0531 connect(device.as<Solid::StorageAccess>(), &Solid::StorageAccess::accessibilityChanged, this, &MediaButton::slotAccessibilityChanged); 0532 } 0533 0534 void MediaButton::slotDeviceRemoved(const QString &udi) 0535 { 0536 if (popupMenu->isHidden()) 0537 return; 0538 0539 QList<QAction *> actionList = popupMenu->actions(); 0540 foreach (QAction *act, actionList) { 0541 if (act && act->data().canConvert<QString>() && act->data().toString() == udi) { 0542 popupMenu->removeAction(act); 0543 delete act; 0544 break; 0545 } 0546 } 0547 } 0548 0549 void MediaButton::slotCheckMounts() 0550 { 0551 if (isHidden()) 0552 return; 0553 0554 KMountPoint::List possibleMountList = KMountPoint::possibleMountPoints(); 0555 KMountPoint::List currentMountList = KMountPoint::currentMountPoints(); 0556 QList<QAction *> actionList = popupMenu->actions(); 0557 0558 foreach (QAction *act, actionList) { 0559 if (act && act->data().canConvert<QString>() && act->data().toString().startsWith(remotePrefix)) { 0560 QString mountPoint = act->data().toString().mid(remotePrefix.length()); 0561 bool available = false; 0562 0563 for (auto &it : possibleMountList) { 0564 if (krMtMan.networkFilesystem(it->mountType()) && it->mountPoint() == mountPoint) { 0565 available = true; 0566 break; 0567 } 0568 } 0569 0570 if (!available) { 0571 popupMenu->removeAction(act); 0572 delete act; 0573 } 0574 } 0575 } 0576 0577 for (auto &it : possibleMountList) { 0578 if (krMtMan.networkFilesystem(it->mountType())) { 0579 QString path = it->mountPoint(); 0580 bool mounted = false; 0581 QString udi = remotePrefix + path; 0582 0583 QAction *correspondingAct = nullptr; 0584 foreach (QAction *act, actionList) { 0585 if (act && act->data().canConvert<QString>() && act->data().toString() == udi) { 0586 correspondingAct = act; 0587 break; 0588 } 0589 } 0590 for (auto &it2 : currentMountList) { 0591 if (krMtMan.networkFilesystem(it2->mountType()) && path == it2->mountPoint()) { 0592 mounted = true; 0593 break; 0594 } 0595 } 0596 0597 QString name = i18nc("%1 is the mount point of the remote share", "Remote Share [%1]", it->mountPoint()); 0598 QStringList overlays; 0599 if (mounted) 0600 overlays << "emblem-mounted"; 0601 QIcon kdeIcon = Icon("network-wired", overlays); 0602 0603 if (!correspondingAct) { 0604 QAction *act = popupMenu->addAction(kdeIcon, name); 0605 act->setData(QVariant(udi)); 0606 } else { 0607 correspondingAct->setText(name); 0608 correspondingAct->setIcon(kdeIcon); 0609 } 0610 } 0611 } 0612 0613 mountCheckerTimer.setSingleShot(true); 0614 mountCheckerTimer.start(1000); 0615 }