File indexing completed on 2024-04-28 17:06:14
0001 /* 0002 SPDX-FileCopyrightText: 2000 Shie Erlich <krusader@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2000 Rafi Yanai <krusader@users.sourceforge.net> 0004 SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "kmountmangui.h" 0010 #include "../Dialogs/krspecialwidgets.h" 0011 #include "../FileSystem/filesystem.h" 0012 #include "../compat.h" 0013 #include "../defaults.h" 0014 #include "../icon.h" 0015 #include "../krglobal.h" 0016 0017 // QtCore 0018 #include <QCryptographicHash> 0019 #include <QFileInfo> 0020 #include <QList> 0021 // QtGui 0022 #include <QBitmap> 0023 #include <QCursor> 0024 #include <QPixmap> 0025 // QtWidgets 0026 #include <QCheckBox> 0027 #include <QDialogButtonBox> 0028 #include <QGridLayout> 0029 #include <QGroupBox> 0030 #include <QHeaderView> 0031 #include <QMenu> 0032 #include <QPushButton> 0033 0034 #include <KCodecs/KCodecs> 0035 #include <KConfigCore/KSharedConfig> 0036 #include <KI18n/KLocalizedString> 0037 #include <KIOCore/KDiskFreeSpaceInfo> 0038 #include <KIOCore/KMountPoint> 0039 #include <KWidgetsAddons/KGuiItem> 0040 #include <KWidgetsAddons/KMessageBox> 0041 0042 #include <Solid/StorageVolume> 0043 0044 #ifndef BSD 0045 #define MTAB "/etc/mtab" 0046 #endif 0047 0048 KMountManGUI::KMountManGUI(KMountMan *mntMan) 0049 : QDialog(mntMan->parentWindow) 0050 , mountMan(mntMan) 0051 , info(nullptr) 0052 , mountList(nullptr) 0053 , cbShowOnlyRemovable(nullptr) 0054 , watcher(nullptr) 0055 , sizeX(-1) 0056 , sizeY(-1) 0057 { 0058 setWindowTitle(i18n("MountMan - Your Mount-Manager")); 0059 setWindowModality(Qt::WindowModal); 0060 0061 auto *mainLayout = new QVBoxLayout; 0062 setLayout(mainLayout); 0063 0064 watcher = new QTimer(this); 0065 connect(watcher, &QTimer::timeout, this, &KMountManGUI::checkMountChange); 0066 0067 mainLayout->addLayout(createMainPage()); 0068 0069 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); 0070 mainLayout->addWidget(buttonBox); 0071 0072 ejectButton = new QPushButton(i18n("&Eject")); 0073 ejectButton->setIcon(Icon(QStringLiteral("media-eject"))); 0074 ejectButton->setEnabled(false); 0075 buttonBox->addButton(ejectButton, QDialogButtonBox::ActionRole); 0076 0077 mountButton = new QPushButton(i18n("&Unmount")); 0078 mountButton->setEnabled(false); 0079 buttonBox->addButton(mountButton, QDialogButtonBox::ActionRole); 0080 0081 // connections 0082 connect(buttonBox, &QDialogButtonBox::rejected, this, &KMountManGUI::reject); 0083 connect(ejectButton, &QPushButton::clicked, this, &KMountManGUI::slotEject); 0084 connect(mountButton, &QPushButton::clicked, this, &KMountManGUI::slotToggleMount); 0085 connect(mountList, &KrTreeWidget::itemDoubleClicked, this, &KMountManGUI::doubleClicked); 0086 connect(mountList, &KrTreeWidget::itemRightClicked, this, &KMountManGUI::clicked); 0087 connect(mountList, &KrTreeWidget::itemClicked, this, QOverload<QTreeWidgetItem *>::of(&KMountManGUI::changeActive)); 0088 connect(mountList, &KrTreeWidget::itemSelectionChanged, this, QOverload<>::of(&KMountManGUI::changeActive)); 0089 0090 KConfigGroup group(krConfig, "MountMan"); 0091 int sx = group.readEntry("Window Width", -1); 0092 int sy = group.readEntry("Window Height", -1); 0093 0094 if (sx != -1 && sy != -1) 0095 resize(sx, sy); 0096 else 0097 resize(600, 300); 0098 0099 if (group.readEntry("Window Maximized", false)) 0100 showMaximized(); 0101 else 0102 show(); 0103 0104 getSpaceData(); 0105 0106 exec(); 0107 } 0108 0109 KMountManGUI::~KMountManGUI() 0110 { 0111 watcher->stop(); 0112 delete watcher; 0113 0114 KConfigGroup group(krConfig, "MountMan"); 0115 0116 group.writeEntry("Window Width", sizeX); 0117 group.writeEntry("Window Height", sizeY); 0118 group.writeEntry("Window Maximized", isMaximized()); 0119 group.writeEntry("Last State", mountList->header()->saveState()); 0120 group.writeEntry("ShowOnlyRemovable", cbShowOnlyRemovable->isChecked()); 0121 } 0122 0123 void KMountManGUI::resizeEvent(QResizeEvent *e) 0124 { 0125 if (!isMaximized()) { 0126 sizeX = e->size().width(); 0127 sizeY = e->size().height(); 0128 } 0129 0130 QDialog::resizeEvent(e); 0131 } 0132 0133 QLayout *KMountManGUI::createMainPage() 0134 { 0135 auto *layout = new QGridLayout(); 0136 layout->setSpacing(10); 0137 mountList = new KrTreeWidget(this); // create the main container 0138 KConfigGroup grp(krConfig, "Look&Feel"); 0139 mountList->setFont(grp.readEntry("Filelist Font", _FilelistFont)); 0140 mountList->setSelectionMode(QAbstractItemView::SingleSelection); 0141 mountList->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); 0142 mountList->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); 0143 0144 QStringList labels; 0145 labels << i18n("Name"); 0146 labels << i18n("Type"); 0147 labels << i18n("Mnt.Point"); 0148 labels << i18n("Total Size"); 0149 labels << i18n("Free Size"); 0150 labels << i18n("Free %"); 0151 mountList->setHeaderLabels(labels); 0152 0153 mountList->header()->setSectionResizeMode(QHeaderView::Interactive); 0154 0155 grp = KConfigGroup(krConfig, "MountMan"); 0156 if (grp.hasKey("Last State")) 0157 mountList->header()->restoreState(grp.readEntry("Last State", QByteArray())); 0158 else { 0159 int i = QFontMetrics(mountList->font()).horizontalAdvance("W"); 0160 int j = QFontMetrics(mountList->font()).horizontalAdvance("0"); 0161 j = (i > j ? i : j); 0162 0163 mountList->setColumnWidth(0, j * 8); 0164 mountList->setColumnWidth(1, j * 4); 0165 mountList->setColumnWidth(2, j * 8); 0166 mountList->setColumnWidth(3, j * 6); 0167 mountList->setColumnWidth(4, j * 6); 0168 mountList->setColumnWidth(5, j * 5); 0169 } 0170 0171 mountList->setAllColumnsShowFocus(true); 0172 mountList->header()->setSortIndicatorShown(true); 0173 0174 mountList->sortItems(0, Qt::AscendingOrder); 0175 0176 // now the list is created, time to fill it with data. 0177 //=>mountMan->forceUpdate(); 0178 0179 QGroupBox *box = new QGroupBox(i18n("MountMan.Info"), this); 0180 box->setAlignment(Qt::AlignHCenter); 0181 auto *vboxl = new QVBoxLayout(box); 0182 info = new KrFSDisplay(box); 0183 vboxl->addWidget(info); 0184 info->resize(info->width(), height()); 0185 0186 cbShowOnlyRemovable = new QCheckBox(i18n("Show only removable devices"), this); 0187 cbShowOnlyRemovable->setChecked(grp.readEntry("ShowOnlyRemovable", false)); 0188 connect(cbShowOnlyRemovable, &QCheckBox::stateChanged, this, &KMountManGUI::updateList); 0189 0190 layout->addWidget(box, 0, 0); 0191 layout->addWidget(cbShowOnlyRemovable, 1, 0); 0192 layout->addWidget(mountList, 0, 1, 2, 1); 0193 0194 return layout; 0195 } 0196 0197 void KMountManGUI::getSpaceData() 0198 { 0199 fileSystems.clear(); 0200 KrMountDetector::getInstance()->hasMountsChanged(); 0201 0202 mounted = KMountPoint::currentMountPoints(); 0203 possible = KMountPoint::possibleMountPoints(); 0204 if (mounted.size() == 0) { // nothing is mounted 0205 addNonMounted(); 0206 updateList(); // let's continue 0207 return; 0208 } 0209 0210 for (auto &it : mounted) { 0211 // don't bother with invalid file systems 0212 if (mountMan->invalidFilesystem(it->mountType())) { 0213 continue; 0214 } 0215 KDiskFreeSpaceInfo info = KDiskFreeSpaceInfo::freeSpaceInfo(it->mountPoint()); 0216 if (!info.isValid()) { 0217 continue; 0218 } 0219 fsData data; 0220 data.setMntPoint(it->mountPoint()); 0221 data.setMounted(true); 0222 data.setTotalBlks(info.size() / 1024); 0223 data.setFreeBlks(info.available() / 1024); 0224 data.setName(it->mountedFrom()); 0225 data.setType(it->mountType()); 0226 fileSystems.append(data); 0227 } 0228 addNonMounted(); 0229 updateList(); 0230 } 0231 0232 void KMountManGUI::addNonMounted() 0233 { 0234 // handle the non-mounted ones 0235 for (auto &it : possible) { 0236 // make sure we don't add things we've already added 0237 if (KMountMan::findInListByMntPoint(mounted, it->mountPoint())) { 0238 continue; 0239 } else { 0240 fsData data; 0241 data.setMntPoint(it->mountPoint()); 0242 data.setMounted(false); 0243 data.setType(it->mountType()); 0244 data.setName(it->mountedFrom()); 0245 0246 if (mountMan->invalidFilesystem(data.type())) 0247 continue; 0248 0249 fileSystems.append(data); 0250 } 0251 } 0252 } 0253 0254 void KMountManGUI::addItemToMountList(KrTreeWidget *lst, fsData &fs) 0255 { 0256 Solid::Device device(mountMan->findUdiForPath(fs.mntPoint(), Solid::DeviceInterface::StorageAccess)); 0257 0258 if (cbShowOnlyRemovable->isChecked() && !mountMan->removable(device)) 0259 return; 0260 0261 bool mtd = fs.mounted(); 0262 0263 QString tSize = QString("%1").arg(KIO::convertSizeFromKiB(fs.totalBlks())); 0264 QString fSize = QString("%1").arg(KIO::convertSizeFromKiB(fs.freeBlks())); 0265 QString sPrct = QString("%1%").arg(100 - (fs.usedPerct())); 0266 auto *item = new QTreeWidgetItem(lst); 0267 item->setText(0, fs.name()); 0268 item->setText(1, fs.type()); 0269 item->setText(2, fs.mntPoint()); 0270 item->setText(3, (mtd ? tSize : QString("N/A"))); 0271 item->setText(4, (mtd ? fSize : QString("N/A"))); 0272 item->setText(5, (mtd ? sPrct : QString("N/A"))); 0273 0274 auto *vol = device.as<Solid::StorageVolume>(); 0275 QString iconName; 0276 0277 if (device.isValid()) 0278 iconName = device.icon(); 0279 else if (mountMan->networkFilesystem(fs.type())) 0280 iconName = "folder-remote"; 0281 QStringList overlays; 0282 if (mtd) { 0283 overlays << "emblem-mounted"; 0284 } else { 0285 overlays << QString(); // We have to guarantee the placement of the next emblem 0286 } 0287 if (vol && vol->usage() == Solid::StorageVolume::Encrypted) { 0288 overlays << "security-high"; 0289 } 0290 item->setIcon(0, Icon(iconName, overlays)); 0291 } 0292 0293 void KMountManGUI::updateList() 0294 { 0295 QString currentMP; 0296 int currentIdx = 0; 0297 QTreeWidgetItem *currentItem = mountList->currentItem(); 0298 if (currentItem) { 0299 currentMP = getMntPoint(currentItem); 0300 currentIdx = mountList->indexOfTopLevelItem(currentItem); 0301 } 0302 0303 mountList->clearSelection(); 0304 mountList->clear(); 0305 0306 for (auto &fileSystem : fileSystems) 0307 addItemToMountList(mountList, fileSystem); 0308 0309 currentItem = mountList->topLevelItem(currentIdx); 0310 for (int i = 0; i < mountList->topLevelItemCount(); i++) { 0311 QTreeWidgetItem *item = mountList->topLevelItem(i); 0312 if (getMntPoint(item) == currentMP) 0313 currentItem = item; 0314 } 0315 if (!currentItem) 0316 currentItem = mountList->topLevelItem(0); 0317 mountList->setCurrentItem(currentItem); 0318 changeActive(currentItem); 0319 0320 mountList->setFocus(); 0321 0322 watcher->setSingleShot(true); 0323 watcher->start(WATCHER_DELAY); // starting the watch timer ( single shot ) 0324 } 0325 0326 void KMountManGUI::checkMountChange() 0327 { 0328 if (KrMountDetector::getInstance()->hasMountsChanged()) 0329 getSpaceData(); 0330 watcher->setSingleShot(true); 0331 watcher->start(WATCHER_DELAY); // starting the watch timer ( single shot ) 0332 } 0333 0334 void KMountManGUI::doubleClicked(QTreeWidgetItem *i) 0335 { 0336 if (!i) 0337 return; // we don't want to refresh to swap, do we ? 0338 0339 // change the active panel to this mountpoint 0340 mountMan->emitRefreshPanel(QUrl::fromLocalFile(getMntPoint(i))); 0341 close(); 0342 } 0343 0344 void KMountManGUI::changeActive() 0345 { 0346 QList<QTreeWidgetItem *> seld = mountList->selectedItems(); 0347 if (seld.count() > 0) 0348 changeActive(seld[0]); 0349 } 0350 0351 // when user clicks on a filesystem, change information 0352 void KMountManGUI::changeActive(QTreeWidgetItem *i) 0353 { 0354 if (!i) { 0355 if (info) { 0356 info->setEmpty(true); 0357 info->update(); 0358 } 0359 mountButton->setEnabled(false); 0360 ejectButton->setEnabled(false); 0361 return; 0362 } 0363 0364 fsData *system = getFsData(i); 0365 0366 info->setAlias(system->mntPoint()); 0367 info->setRealName(system->name()); 0368 info->setMounted(system->mounted()); 0369 info->setEmpty(false); 0370 info->setTotalSpace(system->totalBlks()); 0371 info->setFreeSpace(system->freeBlks()); 0372 info->repaint(); 0373 0374 if (system->mounted()) 0375 mountButton->setText(i18n("&Unmount")); 0376 else 0377 mountButton->setText(i18n("&Mount")); 0378 0379 ejectButton->setEnabled(mountMan->ejectable(system->mntPoint())); 0380 mountButton->setEnabled(true); 0381 } 0382 0383 // called when right-clicked on a filesystem 0384 void KMountManGUI::clicked(QTreeWidgetItem *item, const QPoint &pos) 0385 { 0386 // these are the values that will exist in the menu 0387 #define MOUNT_ID 90 0388 #define UNMOUNT_ID 91 0389 #define FORMAT_ID 93 0390 #define EJECT_ID 94 0391 ////////////////////////////////////////////////////////// 0392 if (!item) 0393 return; 0394 0395 fsData *system = getFsData(item); 0396 // create the menu 0397 QMenu popup; 0398 popup.setTitle(i18n("MountMan")); 0399 if (!system->mounted()) { 0400 QAction *mountAct = popup.addAction(i18n("Mount")); 0401 mountAct->setData(QVariant(MOUNT_ID)); 0402 bool enable = !(mountMan->nonmountFilesystem(system->type(), system->mntPoint())); 0403 mountAct->setEnabled(enable); 0404 } else { 0405 QAction *umountAct = popup.addAction(i18n("Unmount")); 0406 umountAct->setData(QVariant(UNMOUNT_ID)); 0407 bool enable = !(mountMan->nonmountFilesystem(system->type(), system->mntPoint())); 0408 umountAct->setEnabled(enable); 0409 } 0410 if (mountMan->ejectable(system->mntPoint())) 0411 // if (system->type()=="iso9660" || mountMan->followLink(system->name()).left(2)=="cd") 0412 popup.addAction(i18n("Eject"))->setData(QVariant(EJECT_ID)); 0413 else { 0414 QAction *formatAct = popup.addAction(i18n("Format")); 0415 formatAct->setData(QVariant(FORMAT_ID)); 0416 formatAct->setEnabled(false); 0417 } 0418 0419 QString mountPoint = system->mntPoint(); 0420 0421 QAction *res = popup.exec(pos); 0422 int result = -1; 0423 0424 if (res && res->data().canConvert<int>()) 0425 result = res->data().toInt(); 0426 0427 // check out the user's option 0428 switch (result) { 0429 case -1: 0430 return; // the user clicked outside of the menu 0431 case MOUNT_ID: 0432 case UNMOUNT_ID: 0433 mountMan->toggleMount(mountPoint); 0434 break; 0435 case FORMAT_ID: 0436 break; 0437 case EJECT_ID: 0438 mountMan->eject(mountPoint); 0439 break; 0440 } 0441 } 0442 0443 void KMountManGUI::slotToggleMount() 0444 { 0445 QTreeWidgetItem *item = mountList->currentItem(); 0446 if (item) { 0447 mountMan->toggleMount(getFsData(item)->mntPoint()); 0448 } 0449 } 0450 0451 void KMountManGUI::slotEject() 0452 { 0453 QTreeWidgetItem *item = mountList->currentItem(); 0454 if (item) { 0455 mountMan->eject(getFsData(item)->mntPoint()); 0456 } 0457 } 0458 0459 fsData *KMountManGUI::getFsData(QTreeWidgetItem *item) 0460 { 0461 for (auto &fileSystem : fileSystems) { 0462 // the only thing which is unique is the mount point 0463 if (fileSystem.mntPoint() == getMntPoint(item)) { 0464 return &fileSystem; 0465 } 0466 } 0467 // this point shouldn't be reached 0468 abort(); 0469 return nullptr; 0470 } 0471 0472 QString KMountManGUI::getMntPoint(QTreeWidgetItem *item) 0473 { 0474 return item->text(2); // text(2) ? ugly ugly ugly 0475 } 0476 0477 KrMountDetector::KrMountDetector() 0478 { 0479 hasMountsChanged(); 0480 } 0481 0482 bool KrMountDetector::hasMountsChanged() 0483 { 0484 bool result = false; 0485 #ifndef BSD 0486 QFileInfo mtabInfo(MTAB); 0487 if (!mtabInfo.exists() || mtabInfo.isSymLink()) { // if mtab is a symlimk to /proc/mounts the mtime is unusable 0488 #endif 0489 KMountPoint::List mountPoints = KMountPoint::currentMountPoints(KMountPoint::NeedRealDeviceName); 0490 QCryptographicHash md5(QCryptographicHash::Md5); 0491 for (auto &mountPoint : mountPoints) { 0492 md5.addData(mountPoint->mountedFrom().toUtf8()); 0493 md5.addData(mountPoint->realDeviceName().toUtf8()); 0494 md5.addData(mountPoint->mountPoint().toUtf8()); 0495 md5.addData(mountPoint->mountType().toUtf8()); 0496 } 0497 QString s = md5.result(); 0498 result = s != checksum; 0499 checksum = s; 0500 #ifndef BSD 0501 } else { 0502 result = mtabInfo.lastModified() != lastMtab; 0503 lastMtab = mtabInfo.lastModified(); 0504 } 0505 #endif 0506 return result; 0507 } 0508 0509 KrMountDetector krMountDetector; 0510 KrMountDetector *KrMountDetector::getInstance() 0511 { 0512 return &krMountDetector; 0513 }