File indexing completed on 2023-09-24 04:09:24
0001 /* 0002 This file is part of the KDE project 0003 SPDX-FileCopyrightText: 1998, 1999 Torben Weis <weis@kde.org> 0004 SPDX-FileCopyrightText: 1999, 2000 Preston Brown <pbrown@kde.org> 0005 SPDX-FileCopyrightText: 2000 Simon Hausmann <hausmann@kde.org> 0006 SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org> 0007 SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org> 0008 SPDX-FileCopyrightText: 2021 Ahmad Samir <a.samirh78@gmail.com> 0009 SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org> 0010 0011 SPDX-License-Identifier: LGPL-2.0-or-later 0012 */ 0013 0014 /* 0015 * kpropertiesdialog.cpp 0016 * View/Edit Properties of files, locally or remotely 0017 * 0018 * some FilePermissionsPropsPlugin-changes by 0019 * Henner Zeller <zeller@think.de> 0020 * some layout management by 0021 * Bertrand Leconte <B.Leconte@mail.dotcom.fr> 0022 * the rest of the layout management, bug fixes, adaptation to libkio, 0023 * template feature by 0024 * David Faure <faure@kde.org> 0025 * More layout, cleanups, and fixes by 0026 * Preston Brown <pbrown@kde.org> 0027 * Plugin capability, cleanups and port to KDialog by 0028 * Simon Hausmann <hausmann@kde.org> 0029 * KDesktopPropsPlugin by 0030 * Waldo Bastian <bastian@kde.org> 0031 */ 0032 0033 #include "kpropertiesdialog.h" 0034 #include "../utils_p.h" 0035 #include "kio_widgets_debug.h" 0036 #include "kpropertiesdialog_p.h" 0037 0038 #include <config-kiowidgets.h> 0039 0040 #include <gpudetection_p.h> 0041 #include <kacl.h> 0042 #include <kbuildsycocaprogressdialog.h> 0043 #include <kdirnotify.h> 0044 #include <kfileitemlistproperties.h> 0045 #include <kio/chmodjob.h> 0046 #include <kio/copyjob.h> 0047 #include <kio/desktopexecparser.h> 0048 #include <kio/directorysizejob.h> 0049 #include <kio/global.h> 0050 #include <kio/jobuidelegate.h> 0051 #include <kio/renamedialog.h> 0052 #include <kio/statjob.h> 0053 #include <kioglobal_p.h> 0054 #include <kmountpoint.h> 0055 #include <kprotocolinfo.h> 0056 #include <kprotocolmanager.h> 0057 #include <kurlrequester.h> 0058 0059 #include <KApplicationTrader> 0060 #include <KAuthorized> 0061 #include <KCapacityBar> 0062 #include <KColorScheme> 0063 #include <KCompletion> 0064 #include <KConfigGroup> 0065 #include <KDesktopFile> 0066 #include <KDialogJobUiDelegate> 0067 #include <KIO/ApplicationLauncherJob> 0068 #include <KIO/FileSystemFreeSpaceJob> 0069 #include <KIO/OpenFileManagerWindowJob> 0070 #include <KIconButton> 0071 #include <KJobUiDelegate> 0072 #include <KJobWidgets> 0073 #include <KLazyLocalizedString> 0074 #include <KLineEdit> 0075 #include <KLocalizedString> 0076 #include <KMessageBox> 0077 #include <KMessageWidget> 0078 #include <KMimeTypeChooser> 0079 #include <KMimeTypeEditor> 0080 #include <KMimeTypeTrader> 0081 #include <KPluginFactory> 0082 #include <KPluginMetaData> 0083 #include <KSeparator> 0084 #include <KService> 0085 #include <KSharedConfig> 0086 #include <KShell> 0087 #include <KSqueezedTextLabel> 0088 #include <KSycoca> 0089 #include <KWindowConfig> 0090 0091 #include <qplatformdefs.h> 0092 0093 #include <QCheckBox> 0094 #include <QClipboard> 0095 #include <QComboBox> 0096 #include <QDBusConnection> 0097 #include <QDBusInterface> 0098 #include <QDBusReply> 0099 #include <QDebug> 0100 #include <QDialogButtonBox> 0101 #include <QDir> 0102 #include <QFile> 0103 #include <QFileDialog> 0104 #include <QFileInfo> 0105 #include <QFileSystemWatcher> 0106 #include <QFutureWatcher> 0107 #include <QLabel> 0108 #include <QLayout> 0109 #include <QLocale> 0110 #include <QMimeDatabase> 0111 #include <QProgressBar> 0112 #include <QPushButton> 0113 #include <QRegularExpression> 0114 #include <QStandardPaths> 0115 #include <QStyle> 0116 #include <QUrl> 0117 #include <QVector> 0118 #include <QtConcurrentRun> 0119 0120 #include <cerrno> 0121 extern "C" { 0122 #if HAVE_SYS_XATTR_H 0123 #include <sys/xattr.h> 0124 #endif 0125 #if HAVE_SYS_EXTATTR_H 0126 #include <sys/extattr.h> 0127 #endif 0128 #if HAVE_SYS_MOUNT_H 0129 #include <sys/mount.h> 0130 #endif 0131 } 0132 0133 #include "ui_checksumswidget.h" 0134 #include "ui_kfilepropspluginwidget.h" 0135 #include "ui_kpropertiesdesktopadvbase.h" 0136 #include "ui_kpropertiesdesktopbase.h" 0137 0138 #include <algorithm> 0139 #include <cerrno> 0140 #include <functional> 0141 #include <vector> 0142 0143 #if HAVE_POSIX_ACL 0144 #include "kacleditwidget.h" 0145 #endif 0146 0147 #ifdef Q_OS_WIN 0148 #include <process.h> 0149 #include <qt_windows.h> 0150 #include <shellapi.h> 0151 #ifdef __GNUC__ 0152 #warning TODO: port completely to win32 0153 #endif 0154 #endif 0155 0156 using namespace KDEPrivate; 0157 0158 static QString nameFromFileName(QString nameStr) 0159 { 0160 if (nameStr.endsWith(QLatin1String(".desktop"))) { 0161 nameStr.chop(8); 0162 } 0163 if (nameStr.endsWith(QLatin1String(".kdelnk"))) { 0164 nameStr.chop(7); 0165 } 0166 // Make it human-readable (%2F => '/', ...) 0167 nameStr = KIO::decodeFileName(nameStr); 0168 return nameStr; 0169 } 0170 0171 constexpr mode_t KFilePermissionsPropsPlugin::fperm[3][4] = {{S_IRUSR, S_IWUSR, S_IXUSR, S_ISUID}, 0172 {S_IRGRP, S_IWGRP, S_IXGRP, S_ISGID}, 0173 {S_IROTH, S_IWOTH, S_IXOTH, S_ISVTX}}; 0174 0175 static QString couldNotSaveMsg(const QString &path) 0176 { 0177 return xi18nc("@info", "Could not save properties due to insufficient write access to:<nl/><filename>%1</filename>.", path); 0178 } 0179 0180 class KPropertiesDialogPrivate 0181 { 0182 public: 0183 explicit KPropertiesDialogPrivate(KPropertiesDialog *qq) 0184 : q(qq) 0185 { 0186 } 0187 ~KPropertiesDialogPrivate() 0188 { 0189 // qDeleteAll deletes the pages in order, this prevents crashes when closing the dialog 0190 qDeleteAll(m_pages); 0191 } 0192 0193 /** 0194 * Common initialization for all constructors 0195 */ 0196 void init(); 0197 /** 0198 * Inserts all pages in the dialog. 0199 */ 0200 void insertPages(); 0201 0202 KPropertiesDialog *const q; 0203 bool m_aborted = false; 0204 KPageWidgetItem *fileSharePageItem = nullptr; 0205 KFilePropsPlugin *m_filePropsPlugin = nullptr; 0206 KFilePermissionsPropsPlugin *m_permissionsPropsPlugin = nullptr; 0207 KDesktopPropsPlugin *m_desktopPropsPlugin = nullptr; 0208 KUrlPropsPlugin *m_urlPropsPlugin = nullptr; 0209 0210 /** 0211 * The URL of the props dialog (when shown for only one file) 0212 */ 0213 QUrl m_singleUrl; 0214 /** 0215 * List of items this props dialog is shown for 0216 */ 0217 KFileItemList m_items; 0218 /** 0219 * For templates 0220 */ 0221 QString m_defaultName; 0222 QUrl m_currentDir; 0223 0224 /** 0225 * List of all plugins inserted ( first one first ) 0226 */ 0227 std::vector<KPropertiesDialogPlugin *> m_pages; 0228 }; 0229 0230 KPropertiesDialog::KPropertiesDialog(const KFileItem &item, QWidget *parent) 0231 : KPageDialog(parent) 0232 , d(new KPropertiesDialogPrivate(this)) 0233 { 0234 setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(item.name()))); 0235 0236 Q_ASSERT(!item.isNull()); 0237 d->m_items.append(item); 0238 0239 d->m_singleUrl = item.url(); 0240 Q_ASSERT(!d->m_singleUrl.isEmpty()); 0241 0242 d->init(); 0243 } 0244 0245 KPropertiesDialog::KPropertiesDialog(const QString &title, QWidget *parent) 0246 : KPageDialog(parent) 0247 , d(new KPropertiesDialogPrivate(this)) 0248 { 0249 setWindowTitle(i18n("Properties for %1", title)); 0250 0251 d->init(); 0252 } 0253 0254 KPropertiesDialog::KPropertiesDialog(const KFileItemList &_items, QWidget *parent) 0255 : KPageDialog(parent) 0256 , d(new KPropertiesDialogPrivate(this)) 0257 { 0258 if (_items.count() > 1) { 0259 setWindowTitle(i18np("Properties for 1 item", "Properties for %1 Selected Items", _items.count())); 0260 } else { 0261 setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(_items.first().name()))); 0262 } 0263 0264 Q_ASSERT(!_items.isEmpty()); 0265 d->m_singleUrl = _items.first().url(); 0266 Q_ASSERT(!d->m_singleUrl.isEmpty()); 0267 0268 d->m_items = _items; 0269 0270 d->init(); 0271 } 0272 0273 KPropertiesDialog::KPropertiesDialog(const QUrl &_url, QWidget *parent) 0274 : KPageDialog(parent) 0275 , d(new KPropertiesDialogPrivate(this)) 0276 { 0277 d->m_singleUrl = _url.adjusted(QUrl::StripTrailingSlash); 0278 0279 setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(d->m_singleUrl.fileName()))); 0280 0281 KIO::StatJob *job = KIO::stat(d->m_singleUrl); 0282 KJobWidgets::setWindow(job, parent); 0283 job->exec(); 0284 KIO::UDSEntry entry = job->statResult(); 0285 0286 d->m_items.append(KFileItem(entry, d->m_singleUrl)); 0287 d->init(); 0288 } 0289 0290 KPropertiesDialog::KPropertiesDialog(const QList<QUrl> &urls, QWidget *parent) 0291 : KPageDialog(parent) 0292 , d(new KPropertiesDialogPrivate(this)) 0293 { 0294 if (urls.count() > 1) { 0295 setWindowTitle(i18np("Properties for 1 item", "Properties for %1 Selected Items", urls.count())); 0296 } else { 0297 setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(urls.first().fileName()))); 0298 } 0299 0300 Q_ASSERT(!urls.isEmpty()); 0301 d->m_singleUrl = urls.first(); 0302 Q_ASSERT(!d->m_singleUrl.isEmpty()); 0303 0304 d->m_items.reserve(urls.size()); 0305 for (const QUrl &url : urls) { 0306 KIO::StatJob *job = KIO::stat(url); 0307 KJobWidgets::setWindow(job, parent); 0308 job->exec(); 0309 KIO::UDSEntry entry = job->statResult(); 0310 0311 d->m_items.append(KFileItem(entry, url)); 0312 } 0313 0314 d->init(); 0315 } 0316 0317 KPropertiesDialog::KPropertiesDialog(const QUrl &_tempUrl, const QUrl &_currentDir, const QString &_defaultName, QWidget *parent) 0318 : KPageDialog(parent) 0319 , d(new KPropertiesDialogPrivate(this)) 0320 { 0321 setWindowTitle(i18n("Properties for %1", KIO::decodeFileName(_tempUrl.fileName()))); 0322 0323 d->m_singleUrl = _tempUrl; 0324 d->m_defaultName = _defaultName; 0325 d->m_currentDir = _currentDir; 0326 Q_ASSERT(!d->m_singleUrl.isEmpty()); 0327 0328 // Create the KFileItem for the _template_ file, in order to read from it. 0329 d->m_items.append(KFileItem(d->m_singleUrl)); 0330 d->init(); 0331 } 0332 0333 #ifdef Q_OS_WIN 0334 bool showWin32FilePropertyDialog(const QString &fileName) 0335 { 0336 QString path_ = QDir::toNativeSeparators(QFileInfo(fileName).absoluteFilePath()); 0337 0338 #ifndef _WIN32_WCE 0339 SHELLEXECUTEINFOW execInfo; 0340 #else 0341 SHELLEXECUTEINFO execInfo; 0342 #endif 0343 memset(&execInfo, 0, sizeof(execInfo)); 0344 execInfo.cbSize = sizeof(execInfo); 0345 #ifndef _WIN32_WCE 0346 execInfo.fMask = SEE_MASK_INVOKEIDLIST | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI; 0347 #else 0348 execInfo.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI; 0349 #endif 0350 const QString verb(QLatin1String("properties")); 0351 execInfo.lpVerb = (LPCWSTR)verb.utf16(); 0352 execInfo.lpFile = (LPCWSTR)path_.utf16(); 0353 #ifndef _WIN32_WCE 0354 return ShellExecuteExW(&execInfo); 0355 #else 0356 return ShellExecuteEx(&execInfo); 0357 // There is no native file property dialog in wince 0358 // return false; 0359 #endif 0360 } 0361 #endif 0362 0363 bool KPropertiesDialog::showDialog(const KFileItem &item, QWidget *parent, bool modal) 0364 { 0365 // TODO: do we really want to show the win32 property dialog? 0366 // This means we lose metainfo, support for .desktop files, etc. (DF) 0367 #ifdef Q_OS_WIN 0368 QString localPath = item.localPath(); 0369 if (!localPath.isEmpty()) { 0370 return showWin32FilePropertyDialog(localPath); 0371 } 0372 #endif 0373 KPropertiesDialog *dlg = new KPropertiesDialog(item, parent); 0374 if (modal) { 0375 dlg->exec(); 0376 } else { 0377 dlg->show(); 0378 } 0379 0380 return true; 0381 } 0382 0383 bool KPropertiesDialog::showDialog(const QUrl &_url, QWidget *parent, bool modal) 0384 { 0385 #ifdef Q_OS_WIN 0386 if (_url.isLocalFile()) { 0387 return showWin32FilePropertyDialog(_url.toLocalFile()); 0388 } 0389 #endif 0390 KPropertiesDialog *dlg = new KPropertiesDialog(_url, parent); 0391 if (modal) { 0392 dlg->exec(); 0393 } else { 0394 dlg->show(); 0395 } 0396 0397 return true; 0398 } 0399 0400 bool KPropertiesDialog::showDialog(const KFileItemList &_items, QWidget *parent, bool modal) 0401 { 0402 if (_items.count() == 1) { 0403 const KFileItem &item = _items.first(); 0404 if (item.entry().count() == 0 && item.localPath().isEmpty()) // this remote item wasn't listed by a worker 0405 // Let's stat to get more info on the file 0406 { 0407 return KPropertiesDialog::showDialog(item.url(), parent, modal); 0408 } else { 0409 return KPropertiesDialog::showDialog(_items.first(), parent, modal); 0410 } 0411 } 0412 KPropertiesDialog *dlg = new KPropertiesDialog(_items, parent); 0413 if (modal) { 0414 dlg->exec(); 0415 } else { 0416 dlg->show(); 0417 } 0418 return true; 0419 } 0420 0421 bool KPropertiesDialog::showDialog(const QList<QUrl> &urls, QWidget *parent, bool modal) 0422 { 0423 KPropertiesDialog *dlg = new KPropertiesDialog(urls, parent); 0424 if (modal) { 0425 dlg->exec(); 0426 } else { 0427 dlg->show(); 0428 } 0429 return true; 0430 } 0431 0432 void KPropertiesDialogPrivate::init() 0433 { 0434 q->setFaceType(KPageDialog::Tabbed); 0435 0436 insertPages(); 0437 0438 KConfigGroup group(KSharedConfig::openConfig(), "KPropertiesDialog"); 0439 KWindowConfig::restoreWindowSize(q->windowHandle(), group); 0440 } 0441 0442 void KPropertiesDialog::showFileSharingPage() 0443 { 0444 if (d->fileSharePageItem) { 0445 setCurrentPage(d->fileSharePageItem); 0446 } 0447 } 0448 0449 void KPropertiesDialog::setFileSharingPage(QWidget *page) 0450 { 0451 d->fileSharePageItem = addPage(page, i18nc("@title:tab", "Share")); 0452 } 0453 0454 void KPropertiesDialog::setFileNameReadOnly(bool ro) 0455 { 0456 if (d->m_filePropsPlugin) { 0457 d->m_filePropsPlugin->setFileNameReadOnly(ro); 0458 } 0459 0460 if (d->m_urlPropsPlugin) { 0461 d->m_urlPropsPlugin->setFileNameReadOnly(ro); 0462 } 0463 } 0464 0465 KPropertiesDialog::~KPropertiesDialog() 0466 { 0467 KConfigGroup group(KSharedConfig::openConfig(), "KPropertiesDialog"); 0468 KWindowConfig::saveWindowSize(windowHandle(), group, KConfigBase::Persistent); 0469 } 0470 0471 void KPropertiesDialog::insertPlugin(KPropertiesDialogPlugin *plugin) 0472 { 0473 connect(plugin, &KPropertiesDialogPlugin::changed, plugin, qOverload<>(&KPropertiesDialogPlugin::setDirty)); 0474 0475 d->m_pages.push_back(plugin); 0476 } 0477 0478 QUrl KPropertiesDialog::url() const 0479 { 0480 return d->m_singleUrl; 0481 } 0482 0483 KFileItem &KPropertiesDialog::item() 0484 { 0485 return d->m_items.first(); 0486 } 0487 0488 KFileItemList KPropertiesDialog::items() const 0489 { 0490 return d->m_items; 0491 } 0492 0493 QUrl KPropertiesDialog::currentDir() const 0494 { 0495 return d->m_currentDir; 0496 } 0497 0498 QString KPropertiesDialog::defaultName() const 0499 { 0500 return d->m_defaultName; 0501 } 0502 0503 bool KPropertiesDialog::canDisplay(const KFileItemList &_items) 0504 { 0505 // TODO: cache the result of those calls. Currently we parse .desktop files far too many times 0506 /* clang-format off */ 0507 return KFilePropsPlugin::supports(_items) 0508 || KFilePermissionsPropsPlugin::supports(_items) 0509 || KDesktopPropsPlugin::supports(_items) 0510 || KUrlPropsPlugin::supports(_items); 0511 /* clang-format on */ 0512 } 0513 0514 void KPropertiesDialog::slotOk() 0515 { 0516 accept(); 0517 } 0518 0519 void KPropertiesDialog::accept() 0520 { 0521 d->m_aborted = false; 0522 0523 auto acceptAndClose = [this]() { 0524 Q_EMIT applied(); 0525 Q_EMIT propertiesClosed(); 0526 deleteLater(); // Somewhat like Qt::WA_DeleteOnClose would do. 0527 KPageDialog::accept(); 0528 }; 0529 0530 const bool isAnyDirty = std::any_of(d->m_pages.cbegin(), d->m_pages.cend(), [](const KPropertiesDialogPlugin *page) { 0531 return page->isDirty(); 0532 }); 0533 0534 if (!isAnyDirty) { // No point going further 0535 acceptAndClose(); 0536 return; 0537 } 0538 0539 // If any page is dirty, then set the main one (KFilePropsPlugin) as 0540 // dirty too. This is what makes it possible to save changes to a global 0541 // desktop file into a local one. In other cases, it doesn't hurt. 0542 if (d->m_filePropsPlugin) { 0543 d->m_filePropsPlugin->setDirty(true); 0544 } 0545 0546 // Changes are applied in the following order: 0547 // - KFilePropsPlugin changes, this is because in case of renaming an item or saving changes 0548 // of a template or a .desktop file, the renaming or copying respectively, must be finished 0549 // first, before applying the rest of the changes 0550 // - KFilePermissionsPropsPlugin changes, e.g. if the item was read-only and was changed to 0551 // read/write, this must be applied first for other changes to work 0552 // - The rest of the changes from the other plugins/tabs 0553 // - KFilePropsPlugin::postApplyChanges() 0554 0555 auto applyOtherChanges = [this, acceptAndClose]() { 0556 Q_ASSERT(!d->m_filePropsPlugin->isDirty()); 0557 Q_ASSERT(!d->m_permissionsPropsPlugin->isDirty()); 0558 0559 // Apply the changes for the rest of the plugins 0560 for (auto *page : d->m_pages) { 0561 if (d->m_aborted) { 0562 break; 0563 } 0564 0565 if (page->isDirty()) { 0566 // qDebug() << "applying changes for " << page->metaObject()->className(); 0567 page->applyChanges(); 0568 } 0569 /* else { 0570 qDebug() << "skipping page " << page->metaObject()->className(); 0571 } */ 0572 } 0573 0574 if (!d->m_aborted && d->m_filePropsPlugin) { 0575 d->m_filePropsPlugin->postApplyChanges(); 0576 } 0577 0578 if (!d->m_aborted) { 0579 acceptAndClose(); 0580 } // Else, keep dialog open for user to fix the problem. 0581 }; 0582 0583 auto applyPermissionsChanges = [this, applyOtherChanges]() { 0584 connect(d->m_permissionsPropsPlugin, &KFilePermissionsPropsPlugin::changesApplied, this, [applyOtherChanges]() { 0585 applyOtherChanges(); 0586 }); 0587 0588 d->m_permissionsPropsPlugin->applyChanges(); 0589 }; 0590 0591 if (d->m_filePropsPlugin && d->m_filePropsPlugin->isDirty()) { 0592 // changesApplied() is _not_ emitted if applying the changes was aborted 0593 connect(d->m_filePropsPlugin, &KFilePropsPlugin::changesApplied, this, [this, applyPermissionsChanges, applyOtherChanges]() { 0594 if (d->m_permissionsPropsPlugin && d->m_permissionsPropsPlugin->isDirty()) { 0595 applyPermissionsChanges(); 0596 } else { 0597 applyOtherChanges(); 0598 } 0599 }); 0600 0601 d->m_filePropsPlugin->applyChanges(); 0602 } 0603 } 0604 0605 void KPropertiesDialog::slotCancel() 0606 { 0607 reject(); 0608 } 0609 0610 void KPropertiesDialog::reject() 0611 { 0612 Q_EMIT canceled(); 0613 Q_EMIT propertiesClosed(); 0614 0615 deleteLater(); 0616 KPageDialog::reject(); 0617 } 0618 0619 void KPropertiesDialogPrivate::insertPages() 0620 { 0621 if (m_items.isEmpty()) { 0622 return; 0623 } 0624 0625 if (KFilePropsPlugin::supports(m_items)) { 0626 m_filePropsPlugin = new KFilePropsPlugin(q); 0627 q->insertPlugin(m_filePropsPlugin); 0628 } 0629 0630 if (KFilePermissionsPropsPlugin::supports(m_items)) { 0631 m_permissionsPropsPlugin = new KFilePermissionsPropsPlugin(q); 0632 q->insertPlugin(m_permissionsPropsPlugin); 0633 } 0634 0635 if (KChecksumsPlugin::supports(m_items)) { 0636 KPropertiesDialogPlugin *p = new KChecksumsPlugin(q); 0637 q->insertPlugin(p); 0638 } 0639 0640 if (KDesktopPropsPlugin::supports(m_items)) { 0641 m_desktopPropsPlugin = new KDesktopPropsPlugin(q); 0642 q->insertPlugin(m_desktopPropsPlugin); 0643 } 0644 0645 if (KUrlPropsPlugin::supports(m_items)) { 0646 m_urlPropsPlugin = new KUrlPropsPlugin(q); 0647 q->insertPlugin(m_urlPropsPlugin); 0648 } 0649 0650 if (m_items.count() != 1) { 0651 return; 0652 } 0653 0654 const KFileItem item = m_items.first(); 0655 const QString mimetype = item.mimetype(); 0656 0657 if (mimetype.isEmpty()) { 0658 return; 0659 } 0660 0661 const auto scheme = item.url().scheme(); 0662 QString query = 0663 QStringLiteral("(((not exist [X-KDE-Protocol]) and (not exist [X-KDE-Protocols])) or ([X-KDE-Protocol] == '%1') or ('%1' in [X-KDE-Protocols]))") 0664 .arg(scheme); 0665 0666 // qDebug() << "trader query: " << query; 0667 0668 QStringList addedPlugins; 0669 const auto filter = [mimetype, scheme](const KPluginMetaData &metaData) { 0670 const auto supportedProtocol = metaData.value(QStringLiteral("X-KDE-Protocol"), QString()); 0671 if (!supportedProtocol.isEmpty() && supportedProtocol != scheme) { 0672 return false; 0673 } 0674 0675 const auto supportedProtocols = metaData.value(QStringLiteral("X-KDE-Protocols"), QStringList()); 0676 if (!supportedProtocols.isEmpty()) { 0677 const auto none = std::none_of(supportedProtocols.cbegin(), supportedProtocols.cend(), [scheme](const auto &protocol) { 0678 return !protocol.isEmpty() && protocol == scheme; 0679 }); 0680 if (none) { 0681 return false; 0682 } 0683 } 0684 0685 return metaData.mimeTypes().isEmpty() || metaData.supportsMimeType(mimetype); 0686 }; 0687 const auto jsonPlugins = KPluginMetaData::findPlugins(QStringLiteral("kf" QT_STRINGIFY(QT_VERSION_MAJOR) "/propertiesdialog"), filter); 0688 for (const auto &jsonMetadata : jsonPlugins) { 0689 if (auto plugin = KPluginFactory::instantiatePlugin<KPropertiesDialogPlugin>(jsonMetadata, q).plugin) { 0690 q->insertPlugin(plugin); 0691 addedPlugins.append(jsonMetadata.pluginId()); 0692 } 0693 } 0694 0695 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 83) 0696 QT_WARNING_PUSH 0697 QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations") 0698 QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") 0699 const KService::List offers = KMimeTypeTrader::self()->query(mimetype, QStringLiteral("KPropertiesDialog/Plugin"), query); 0700 for (const KService::Ptr &ptr : offers) { 0701 if (addedPlugins.contains(ptr->desktopEntryName())) { 0702 continue; 0703 } 0704 qCWarning(KIO_WIDGETS) << "Plugin" << ptr->desktopEntryName() << "is using the deprecated loading style. Please port it to JSON loading."; 0705 KPropertiesDialogPlugin *plugin = ptr->createInstance<KPropertiesDialogPlugin>(q); 0706 if (!plugin) { 0707 continue; 0708 } 0709 plugin->setObjectName(ptr->name()); 0710 0711 q->insertPlugin(plugin); 0712 addedPlugins.append(ptr->desktopEntryName()); 0713 } 0714 QT_WARNING_POP 0715 #endif 0716 } 0717 0718 void KPropertiesDialog::updateUrl(const QUrl &_newUrl) 0719 { 0720 Q_ASSERT(d->m_items.count() == 1); 0721 // qDebug() << "KPropertiesDialog::updateUrl (pre)" << _newUrl; 0722 QUrl newUrl = _newUrl; 0723 Q_EMIT saveAs(d->m_singleUrl, newUrl); 0724 // qDebug() << "KPropertiesDialog::updateUrl (post)" << newUrl; 0725 0726 d->m_singleUrl = newUrl; 0727 d->m_items.first().setUrl(newUrl); 0728 Q_ASSERT(!d->m_singleUrl.isEmpty()); 0729 // If we have an Desktop page, set it dirty, so that a full file is saved locally 0730 // Same for a URL page (because of the Name= hack) 0731 if (d->m_urlPropsPlugin) { 0732 d->m_urlPropsPlugin->setDirty(); 0733 } else if (d->m_desktopPropsPlugin) { 0734 d->m_desktopPropsPlugin->setDirty(); 0735 } 0736 } 0737 0738 void KPropertiesDialog::rename(const QString &_name) 0739 { 0740 Q_ASSERT(d->m_items.count() == 1); 0741 // qDebug() << "KPropertiesDialog::rename " << _name; 0742 QUrl newUrl; 0743 // if we're creating from a template : use currentdir 0744 if (!d->m_currentDir.isEmpty()) { 0745 newUrl = d->m_currentDir; 0746 newUrl.setPath(Utils::concatPaths(newUrl.path(), _name)); 0747 } else { 0748 // It's a directory, so strip the trailing slash first 0749 newUrl = d->m_singleUrl.adjusted(QUrl::StripTrailingSlash); 0750 // Now change the filename 0751 newUrl = newUrl.adjusted(QUrl::RemoveFilename); // keep trailing slash 0752 newUrl.setPath(Utils::concatPaths(newUrl.path(), _name)); 0753 } 0754 updateUrl(newUrl); 0755 } 0756 0757 void KPropertiesDialog::abortApplying() 0758 { 0759 d->m_aborted = true; 0760 } 0761 0762 class KPropertiesDialogPluginPrivate 0763 { 0764 public: 0765 KPropertiesDialogPluginPrivate() 0766 { 0767 } 0768 ~KPropertiesDialogPluginPrivate() 0769 { 0770 } 0771 0772 bool m_bDirty; 0773 int fontHeight; 0774 }; 0775 0776 KPropertiesDialogPlugin::KPropertiesDialogPlugin(KPropertiesDialog *_props) 0777 : QObject(_props) 0778 , d(new KPropertiesDialogPluginPrivate) 0779 { 0780 properties = _props; 0781 d->fontHeight = 2 * properties->fontMetrics().height(); 0782 d->m_bDirty = false; 0783 } 0784 0785 KPropertiesDialogPlugin::~KPropertiesDialogPlugin() = default; 0786 0787 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(4, 1) 0788 bool KPropertiesDialogPlugin::isDesktopFile(const KFileItem &_item) 0789 { 0790 return _item.isDesktopFile(); 0791 } 0792 #endif 0793 0794 void KPropertiesDialogPlugin::setDirty(bool b) 0795 { 0796 d->m_bDirty = b; 0797 } 0798 0799 void KPropertiesDialogPlugin::setDirty() 0800 { 0801 d->m_bDirty = true; 0802 } 0803 0804 bool KPropertiesDialogPlugin::isDirty() const 0805 { 0806 return d->m_bDirty; 0807 } 0808 0809 void KPropertiesDialogPlugin::applyChanges() 0810 { 0811 qCWarning(KIO_WIDGETS) << "applyChanges() not implemented in page !"; 0812 } 0813 0814 int KPropertiesDialogPlugin::fontHeight() const 0815 { 0816 return d->fontHeight; 0817 } 0818 0819 /////////////////////////////////////////////////////////////////////////////// 0820 0821 class KFilePropsPlugin::KFilePropsPluginPrivate 0822 { 0823 public: 0824 KFilePropsPluginPrivate() 0825 : m_ui(new Ui_KFilePropsPluginWidget()) 0826 { 0827 m_ui->setupUi(&m_mainWidget); 0828 } 0829 0830 ~KFilePropsPluginPrivate() 0831 { 0832 if (dirSizeJob) { 0833 dirSizeJob->kill(); 0834 } 0835 } 0836 0837 void hideMountPointLabels() 0838 { 0839 m_ui->fsLabel_Left->hide(); 0840 m_ui->fsLabel->hide(); 0841 0842 m_ui->mountPointLabel_Left->hide(); 0843 m_ui->mountPointLabel->hide(); 0844 0845 m_ui->mountSrcLabel_Left->hide(); 0846 m_ui->mountSrcLabel->hide(); 0847 } 0848 0849 QWidget m_mainWidget; 0850 Ui_KFilePropsPluginWidget *m_ui = nullptr; 0851 KIO::DirectorySizeJob *dirSizeJob = nullptr; 0852 QTimer *dirSizeUpdateTimer = nullptr; 0853 bool bMultiple; 0854 bool bIconChanged; 0855 bool bKDesktopMode; 0856 bool bDesktopFile; 0857 QString mimeType; 0858 QString oldFileName; 0859 0860 QString m_sRelativePath; 0861 bool m_bFromTemplate; 0862 0863 /** 0864 * The initial filename 0865 */ 0866 QString oldName; 0867 }; 0868 0869 KFilePropsPlugin::KFilePropsPlugin(KPropertiesDialog *_props) 0870 : KPropertiesDialogPlugin(_props) 0871 , d(new KFilePropsPluginPrivate) 0872 { 0873 const auto itemsList = properties->items(); 0874 d->bMultiple = (itemsList.count() > 1); 0875 d->bIconChanged = false; 0876 d->bDesktopFile = KDesktopPropsPlugin::supports(itemsList); 0877 // qDebug() << "KFilePropsPlugin::KFilePropsPlugin bMultiple=" << d->bMultiple; 0878 0879 // We set this data from the first item, and we'll 0880 // check that the other items match against it, resetting when not. 0881 const KFileItem firstItem = properties->item(); 0882 auto [url, isLocal] = firstItem.isMostLocalUrl(); 0883 bool isReallyLocal = firstItem.url().isLocalFile(); 0884 bool bDesktopFile = firstItem.isDesktopFile(); 0885 mode_t mode = firstItem.mode(); 0886 bool hasDirs = firstItem.isDir() && !firstItem.isLink(); 0887 bool hasRoot = url.path() == QLatin1String("/"); 0888 QString iconStr = firstItem.iconName(); 0889 QString directory = properties->url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path(); 0890 QString protocol = properties->url().scheme(); 0891 d->bKDesktopMode = protocol == QLatin1String("desktop") || properties->currentDir().scheme() == QLatin1String("desktop"); 0892 QString mimeComment = firstItem.mimeComment(); 0893 d->mimeType = firstItem.mimetype(); 0894 KIO::filesize_t totalSize = firstItem.size(); 0895 QString magicMimeName; 0896 QString magicMimeComment; 0897 QMimeDatabase db; 0898 if (isLocal) { 0899 QMimeType magicMimeType = db.mimeTypeForFile(url.toLocalFile(), QMimeDatabase::MatchContent); 0900 if (magicMimeType.isValid() && !magicMimeType.isDefault()) { 0901 magicMimeName = magicMimeType.name(); 0902 magicMimeComment = magicMimeType.comment(); 0903 } 0904 } 0905 #ifdef Q_OS_WIN 0906 if (isReallyLocal) { 0907 directory = QDir::toNativeSeparators(directory.mid(1)); 0908 } 0909 #endif 0910 0911 // Those things only apply to 'single file' mode 0912 QString filename; 0913 bool isTrash = false; 0914 d->m_bFromTemplate = false; 0915 0916 // And those only to 'multiple' mode 0917 uint iDirCount = hasDirs ? 1 : 0; 0918 uint iFileCount = 1 - iDirCount; 0919 0920 properties->addPage(&d->m_mainWidget, i18nc("@title:tab File properties", "&General")); 0921 0922 d->m_ui->symlinkTargetMessageWidget->hide(); 0923 0924 if (!d->bMultiple) { 0925 QString path; 0926 if (!d->m_bFromTemplate) { 0927 isTrash = (properties->url().scheme() == QLatin1String("trash")); 0928 // Extract the full name, but without file: for local files 0929 path = properties->url().toDisplayString(QUrl::PreferLocalFile); 0930 } else { 0931 path = Utils::concatPaths(properties->currentDir().path(), properties->defaultName()); 0932 directory = properties->currentDir().toDisplayString(QUrl::PreferLocalFile); 0933 } 0934 0935 if (d->bDesktopFile) { 0936 determineRelativePath(path); 0937 } 0938 0939 // Extract the file name only 0940 filename = properties->defaultName(); 0941 if (filename.isEmpty()) { // no template 0942 const QFileInfo finfo(firstItem.name()); // this gives support for UDS_NAME, e.g. for kio_trash or kio_system 0943 filename = finfo.fileName(); // Make sure only the file's name is displayed (#160964). 0944 } else { 0945 d->m_bFromTemplate = true; 0946 setDirty(); // to enforce that the copy happens 0947 } 0948 d->oldFileName = filename; 0949 0950 // Make it human-readable 0951 filename = nameFromFileName(filename); 0952 d->oldName = filename; 0953 } else { 0954 // Multiple items: see what they have in common 0955 for (const auto &item : itemsList) { 0956 if (item == firstItem) { 0957 continue; 0958 } 0959 0960 const QUrl url = item.url(); 0961 // qDebug() << "KFilePropsPlugin::KFilePropsPlugin " << url.toDisplayString(); 0962 // The list of things we check here should match the variables defined 0963 // at the beginning of this method. 0964 if (url.isLocalFile() != isLocal) { 0965 isLocal = false; // not all local 0966 } 0967 if (bDesktopFile && item.isDesktopFile() != bDesktopFile) { 0968 bDesktopFile = false; // not all desktop files 0969 } 0970 if (item.mode() != mode) { 0971 mode = static_cast<mode_t>(0); 0972 } 0973 if (KIO::iconNameForUrl(url) != iconStr) { 0974 iconStr = QStringLiteral("document-multiple"); 0975 } 0976 if (url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path() != directory) { 0977 directory.clear(); 0978 } 0979 if (url.scheme() != protocol) { 0980 protocol.clear(); 0981 } 0982 if (!mimeComment.isNull() && item.mimeComment() != mimeComment) { 0983 mimeComment.clear(); 0984 } 0985 if (isLocal && !magicMimeComment.isNull()) { 0986 QMimeType magicMimeType = db.mimeTypeForFile(url.toLocalFile(), QMimeDatabase::MatchContent); 0987 if (magicMimeType.isValid() && magicMimeType.comment() != magicMimeComment) { 0988 magicMimeName.clear(); 0989 magicMimeComment.clear(); 0990 } 0991 } 0992 0993 if (isLocal && url.path() == QLatin1String("/")) { 0994 hasRoot = true; 0995 } 0996 if (item.isDir() && !item.isLink()) { 0997 iDirCount++; 0998 hasDirs = true; 0999 } else { 1000 iFileCount++; 1001 totalSize += item.size(); 1002 } 1003 } 1004 } 1005 1006 if (!isReallyLocal && !protocol.isEmpty()) { 1007 directory += QLatin1String(" (") + protocol + QLatin1Char(')'); 1008 } 1009 1010 if (!isTrash // 1011 && (bDesktopFile || Utils::isDirMask(mode)) // 1012 && !d->bMultiple // not implemented for multiple 1013 && enableIconButton()) { 1014 d->m_ui->iconLabel->hide(); 1015 1016 const int bsize = 66 + (2 * d->m_ui->iconButton->style()->pixelMetric(QStyle::PM_ButtonMargin)); 1017 d->m_ui->iconButton->setFixedSize(bsize, bsize); 1018 d->m_ui->iconButton->setIconSize(48); 1019 d->m_ui->iconButton->setStrictIconSize(false); 1020 if (bDesktopFile && isLocal) { 1021 const KDesktopFile config(url.toLocalFile()); 1022 if (config.hasDeviceType()) { 1023 d->m_ui->iconButton->setIconType(KIconLoader::Desktop, KIconLoader::Device); 1024 } else { 1025 d->m_ui->iconButton->setIconType(KIconLoader::Desktop, KIconLoader::Application); 1026 } 1027 } else { 1028 d->m_ui->iconButton->setIconType(KIconLoader::Desktop, KIconLoader::Place); 1029 } 1030 1031 d->m_ui->iconButton->setIcon(iconStr); 1032 connect(d->m_ui->iconButton, &KIconButton::iconChanged, this, &KFilePropsPlugin::slotIconChanged); 1033 } else { 1034 d->m_ui->iconButton->hide(); 1035 1036 const int bsize = 66 + (2 * d->m_ui->iconLabel->style()->pixelMetric(QStyle::PM_ButtonMargin)); 1037 d->m_ui->iconLabel->setFixedSize(bsize, bsize); 1038 d->m_ui->iconLabel->setPixmap(QIcon::fromTheme(iconStr).pixmap(48)); 1039 } 1040 1041 KFileItemListProperties itemList(KFileItemList{firstItem}); 1042 if (d->bMultiple || isTrash || hasRoot || !(d->m_bFromTemplate || itemList.supportsMoving())) { 1043 d->m_ui->fileNameLineEdit->hide(); 1044 setFileNameReadOnly(true); 1045 if (d->bMultiple) { 1046 d->m_ui->fileNameLabel->setText(KIO::itemsSummaryString(iFileCount + iDirCount, iFileCount, iDirCount, 0, false)); 1047 } 1048 } else { 1049 d->m_ui->fileNameLabel->hide(); 1050 1051 d->m_ui->fileNameLineEdit->setText(filename); 1052 connect(d->m_ui->fileNameLineEdit, &QLineEdit::textChanged, this, &KFilePropsPlugin::nameFileChanged); 1053 } 1054 1055 // Mimetype widgets 1056 if (!mimeComment.isEmpty() && !isTrash) { 1057 d->m_ui->mimeCommentLabel->setText(mimeComment); 1058 d->m_ui->mimeCommentLabel->setToolTip(d->mimeType); 1059 1060 const int hSpacing = properties->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing); 1061 d->m_ui->defaultHandlerLayout->setSpacing(hSpacing); 1062 1063 #ifndef Q_OS_WIN 1064 updateDefaultHandler(d->mimeType); 1065 connect(KSycoca::self(), &KSycoca::databaseChanged, this, [this] { 1066 updateDefaultHandler(d->mimeType); 1067 }); 1068 1069 connect(d->m_ui->configureMimeBtn, &QAbstractButton::clicked, this, &KFilePropsPlugin::slotEditFileType); 1070 #endif 1071 1072 } else { 1073 d->m_ui->typeLabel->hide(); 1074 d->m_ui->mimeCommentLabel->hide(); 1075 d->m_ui->configureMimeBtn->hide(); 1076 1077 d->m_ui->defaultHandlerLabel_Left->hide(); 1078 d->m_ui->defaultHandlerIcon->hide(); 1079 d->m_ui->defaultHandlerLabel->hide(); 1080 } 1081 1082 #ifdef Q_OS_WIN 1083 d->m_ui->defaultHandlerLabel_Left->hide(); 1084 d->m_ui->defaultHandlerIcon->hide(); 1085 d->m_ui->defaultHandlerLabel->hide(); 1086 #endif 1087 1088 if (!magicMimeComment.isEmpty() && magicMimeComment != mimeComment) { 1089 d->m_ui->magicMimeCommentLabel->setText(magicMimeComment); 1090 d->m_ui->magicMimeCommentLabel->setToolTip(magicMimeName); 1091 } else { 1092 d->m_ui->contentLabel->hide(); 1093 d->m_ui->magicMimeCommentLabel->hide(); 1094 } 1095 1096 d->m_ui->configureMimeBtn->setVisible(KAuthorized::authorizeAction(QStringLiteral("editfiletype")) && !d->m_ui->defaultHandlerLabel->isHidden()); 1097 1098 // Location: 1099 if (!directory.isEmpty()) { 1100 d->m_ui->locationLabel->setText(directory); 1101 1102 // Layout direction for this label is always LTR; but if we are in RTL mode, 1103 // align the text to the right, otherwise the text is on the wrong side of the dialog 1104 if (properties->layoutDirection() == Qt::RightToLeft) { 1105 d->m_ui->locationLabel->setAlignment(Qt::AlignRight); 1106 } 1107 } 1108 1109 // Size widgets 1110 if (!hasDirs) { // Only files [and symlinks] 1111 d->m_ui->sizeLabel->setText(QStringLiteral("%1 (%2)").arg(KIO::convertSize(totalSize), QLocale().toString(totalSize))); 1112 d->m_ui->calculateSizeBtn->hide(); 1113 d->m_ui->stopCalculateSizeBtn->hide(); 1114 d->m_ui->sizeDetailsBtn->hide(); 1115 } else { // Directory 1116 connect(d->m_ui->calculateSizeBtn, &QAbstractButton::clicked, this, &KFilePropsPlugin::slotSizeDetermine); 1117 connect(d->m_ui->stopCalculateSizeBtn, &QAbstractButton::clicked, this, &KFilePropsPlugin::slotSizeStop); 1118 1119 if (auto filelight = KService::serviceByDesktopName(QStringLiteral("org.kde.filelight"))) { 1120 d->m_ui->sizeDetailsBtn->setText(i18nc("@action:button", "Explore in %1", filelight->name())); 1121 d->m_ui->sizeDetailsBtn->setIcon(QIcon::fromTheme(filelight->icon())); 1122 connect(d->m_ui->sizeDetailsBtn, &QPushButton::clicked, this, &KFilePropsPlugin::slotSizeDetails); 1123 } else { 1124 d->m_ui->sizeDetailsBtn->hide(); 1125 } 1126 1127 // sizelay->addStretch(10); // so that the buttons don't grow horizontally 1128 1129 // auto-launch for local dirs only, and not for '/' 1130 if (isLocal && !hasRoot) { 1131 d->m_ui->calculateSizeBtn->setText(i18n("Refresh")); 1132 slotSizeDetermine(); 1133 } else { 1134 d->m_ui->stopCalculateSizeBtn->setEnabled(false); 1135 } 1136 } 1137 1138 // Symlink widgets 1139 if (!d->bMultiple && firstItem.isLink()) { 1140 d->m_ui->symlinkTargetEdit->setText(firstItem.linkDest()); 1141 connect(d->m_ui->symlinkTargetEdit, &QLineEdit::textChanged, this, qOverload<>(&KFilePropsPlugin::setDirty)); 1142 1143 connect(d->m_ui->symlinkTargetOpenDir, &QPushButton::clicked, this, [this] { 1144 const QUrl resolvedTargetLocation = properties->item().url().resolved(QUrl(d->m_ui->symlinkTargetEdit->text())); 1145 1146 KIO::StatJob *statJob = KIO::statDetails(resolvedTargetLocation, KIO::StatJob::SourceSide, KIO::StatNoDetails, KIO::HideProgressInfo); 1147 connect(statJob, &KJob::finished, this, [this, statJob] { 1148 if (statJob->error()) { 1149 d->m_ui->symlinkTargetMessageWidget->setText(statJob->errorString()); 1150 d->m_ui->symlinkTargetMessageWidget->animatedShow(); 1151 return; 1152 } 1153 1154 KIO::highlightInFileManager({statJob->url()}); 1155 properties->close(); 1156 }); 1157 }); 1158 } else { 1159 d->m_ui->symlinkTargetLabel->hide(); 1160 d->m_ui->symlinkTargetEdit->hide(); 1161 d->m_ui->symlinkTargetOpenDir->hide(); 1162 } 1163 1164 // Time widgets 1165 if (!d->bMultiple) { 1166 QLocale locale; 1167 if (const QDateTime dt = firstItem.time(KFileItem::CreationTime); !dt.isNull()) { 1168 d->m_ui->createdTimeLabel->setText(locale.toString(dt, QLocale::LongFormat)); 1169 } else { 1170 d->m_ui->createdTimeLabel->hide(); 1171 d->m_ui->createdTimeLabel_Left->hide(); 1172 } 1173 1174 if (const QDateTime dt = firstItem.time(KFileItem::ModificationTime); !dt.isNull()) { 1175 d->m_ui->modifiedTimeLabel->setText(locale.toString(dt, QLocale::LongFormat)); 1176 } else { 1177 d->m_ui->modifiedTimeLabel->hide(); 1178 d->m_ui->modifiedTimeLabel_Left->hide(); 1179 } 1180 1181 if (const QDateTime dt = firstItem.time(KFileItem::AccessTime); !dt.isNull()) { 1182 d->m_ui->accessTimeLabel->setText(locale.toString(dt, QLocale::LongFormat)); 1183 } else { 1184 d->m_ui->accessTimeLabel->hide(); 1185 d->m_ui->accessTimeLabel_Left->hide(); 1186 } 1187 } else { 1188 d->m_ui->createdTimeLabel->hide(); 1189 d->m_ui->createdTimeLabel_Left->hide(); 1190 d->m_ui->modifiedTimeLabel->hide(); 1191 d->m_ui->modifiedTimeLabel_Left->hide(); 1192 d->m_ui->accessTimeLabel->hide(); 1193 d->m_ui->accessTimeLabel_Left->hide(); 1194 } 1195 1196 // File system and mount point widgets 1197 if (hasDirs) { // only for directories 1198 if (isLocal) { 1199 KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByPath(url.toLocalFile()); 1200 1201 if (mp) { 1202 d->m_ui->fsLabel->setText(mp->mountType()); 1203 d->m_ui->mountPointLabel->setText(mp->mountPoint()); 1204 d->m_ui->mountSrcLabel->setText(mp->mountedFrom()); 1205 } else { 1206 qCWarning(KIO_WIDGETS) << "Could not find mount point for" << url; 1207 d->hideMountPointLabels(); 1208 } 1209 } else { 1210 d->hideMountPointLabels(); 1211 } 1212 1213 KIO::FileSystemFreeSpaceJob *job = KIO::fileSystemFreeSpace(url); 1214 connect(job, &KIO::FileSystemFreeSpaceJob::result, this, &KFilePropsPlugin::slotFreeSpaceResult); 1215 } else { 1216 d->m_ui->fsSeparator->hide(); 1217 d->m_ui->freespaceLabel->hide(); 1218 d->m_ui->capacityBar->hide(); 1219 d->hideMountPointLabels(); 1220 } 1221 1222 // UDSEntry extra fields 1223 if (const auto extraFields = KProtocolInfo::extraFields(url); !d->bMultiple && !extraFields.isEmpty()) { 1224 int curRow = d->m_ui->gridLayout->rowCount(); 1225 KSeparator *sep = new KSeparator(Qt::Horizontal, &d->m_mainWidget); 1226 d->m_ui->gridLayout->addWidget(sep, curRow++, 0, 1, 3); 1227 1228 QLocale locale; 1229 for (int i = 0; i < extraFields.count(); ++i) { 1230 const auto &field = extraFields.at(i); 1231 1232 QString text = firstItem.entry().stringValue(KIO::UDSEntry::UDS_EXTRA + i); 1233 if (field.type == KProtocolInfo::ExtraField::Invalid || text.isEmpty()) { 1234 continue; 1235 } 1236 1237 if (field.type == KProtocolInfo::ExtraField::DateTime) { 1238 const QDateTime date = QDateTime::fromString(text, Qt::ISODate); 1239 if (!date.isValid()) { 1240 continue; 1241 } 1242 1243 text = locale.toString(date, QLocale::LongFormat); 1244 } 1245 1246 auto *label = new QLabel(i18n("%1:", field.name), &d->m_mainWidget); 1247 d->m_ui->gridLayout->addWidget(label, curRow, 0, Qt::AlignRight); 1248 1249 auto *squeezedLabel = new KSqueezedTextLabel(text, &d->m_mainWidget); 1250 if (properties->layoutDirection() == Qt::RightToLeft) { 1251 squeezedLabel->setAlignment(Qt::AlignRight); 1252 } else { 1253 squeezedLabel->setLayoutDirection(Qt::LeftToRight); 1254 } 1255 1256 d->m_ui->gridLayout->addWidget(squeezedLabel, curRow++, 1); 1257 squeezedLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); 1258 } 1259 } 1260 } 1261 1262 bool KFilePropsPlugin::enableIconButton() const 1263 { 1264 const KFileItem item = properties->item(); 1265 1266 // desktop files are special, files in /usr/share/applications can be 1267 // edited by overlaying them in .local/share/applications 1268 // https://bugs.kde.org/show_bug.cgi?id=429613 1269 if (item.isDesktopFile()) { 1270 return true; 1271 } 1272 1273 // If the current item is a directory, check if it's writable, 1274 // so we can create/update a .directory 1275 // Current item is a file, same thing: check if it is writable 1276 if (item.isWritable()) { 1277 // exclude remote dirs as changing the icon has no effect (bug 205954) 1278 if (item.isLocalFile() || item.url().scheme() == QLatin1String("desktop")) { 1279 return true; 1280 } 1281 } 1282 1283 return false; 1284 } 1285 1286 void KFilePropsPlugin::setFileNameReadOnly(bool readOnly) 1287 { 1288 Q_ASSERT(readOnly); // false isn't supported 1289 1290 if (readOnly) { 1291 Q_ASSERT(!d->m_bFromTemplate); 1292 1293 d->m_ui->fileNameLineEdit->hide(); 1294 1295 d->m_ui->fileNameLabel->show(); 1296 d->m_ui->fileNameLabel->setText(d->oldName); // will get overwritten if d->bMultiple 1297 } 1298 } 1299 1300 void KFilePropsPlugin::slotEditFileType() 1301 { 1302 QString mime; 1303 if (d->mimeType == QLatin1String("application/octet-stream")) { 1304 const int pos = d->oldFileName.lastIndexOf(QLatin1Char('.')); 1305 if (pos != -1) { 1306 mime = QLatin1Char('*') + QStringView(d->oldFileName).mid(pos); 1307 } else { 1308 mime = QStringLiteral("*"); 1309 } 1310 } else { 1311 mime = d->mimeType; 1312 } 1313 KMimeTypeEditor::editMimeType(mime, properties->window()); 1314 } 1315 1316 void KFilePropsPlugin::slotIconChanged() 1317 { 1318 d->bIconChanged = true; 1319 Q_EMIT changed(); 1320 } 1321 1322 void KFilePropsPlugin::nameFileChanged(const QString &text) 1323 { 1324 properties->buttonBox()->button(QDialogButtonBox::Ok)->setEnabled(!text.isEmpty()); 1325 Q_EMIT changed(); 1326 } 1327 1328 static QString relativeAppsLocation(const QString &file) 1329 { 1330 // Don't resolve symlinks, so that editing /usr/share/applications/foo.desktop that is 1331 // a symlink works 1332 const QString absolute = QFileInfo(file).absoluteFilePath(); 1333 const QStringList dirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation); 1334 for (const QString &base : dirs) { 1335 QDir base_dir = QDir(base); 1336 if (base_dir.exists() && absolute.startsWith(base_dir.canonicalPath())) { 1337 return absolute.mid(base.length() + 1); 1338 } 1339 } 1340 return QString(); // return empty if the file is not in apps 1341 } 1342 1343 void KFilePropsPlugin::determineRelativePath(const QString &path) 1344 { 1345 // now let's make it relative 1346 d->m_sRelativePath = relativeAppsLocation(path); 1347 } 1348 1349 void KFilePropsPlugin::slotFreeSpaceResult(KIO::Job *job, KIO::filesize_t size, KIO::filesize_t available) 1350 { 1351 if (!job->error()) { 1352 const quint64 used = size - available; 1353 const int percentUsed = qRound(100.0 * qreal(used) / qreal(size)); 1354 1355 d->m_ui->capacityBar->setText(i18nc("Available space out of total partition size (percent used)", 1356 "%1 free of %2 (%3% used)", 1357 KIO::convertSize(available), 1358 KIO::convertSize(size), 1359 percentUsed)); 1360 1361 d->m_ui->capacityBar->setValue(percentUsed); 1362 } else { 1363 d->m_ui->capacityBar->setText(i18nc("@info:status", "Unknown size")); 1364 d->m_ui->capacityBar->setValue(0); 1365 } 1366 } 1367 1368 void KFilePropsPlugin::slotDirSizeUpdate() 1369 { 1370 KIO::filesize_t totalSize = d->dirSizeJob->totalSize(); 1371 KIO::filesize_t totalFiles = d->dirSizeJob->totalFiles(); 1372 KIO::filesize_t totalSubdirs = d->dirSizeJob->totalSubdirs(); 1373 d->m_ui->sizeLabel->setText(i18n("Calculating... %1 (%2)\n%3, %4", 1374 KIO::convertSize(totalSize), 1375 QLocale().toString(totalSize), 1376 i18np("1 file", "%1 files", totalFiles), 1377 i18np("1 sub-folder", "%1 sub-folders", totalSubdirs))); 1378 } 1379 1380 void KFilePropsPlugin::slotDirSizeFinished(KJob *job) 1381 { 1382 if (job->error()) { 1383 d->m_ui->sizeLabel->setText(job->errorString()); 1384 } else { 1385 KIO::filesize_t totalSize = d->dirSizeJob->totalSize(); 1386 KIO::filesize_t totalFiles = d->dirSizeJob->totalFiles(); 1387 KIO::filesize_t totalSubdirs = d->dirSizeJob->totalSubdirs(); 1388 d->m_ui->sizeLabel->setText(QStringLiteral("%1 (%2)\n%3, %4") 1389 .arg(KIO::convertSize(totalSize), 1390 QLocale().toString(totalSize), 1391 i18np("1 file", "%1 files", totalFiles), 1392 i18np("1 sub-folder", "%1 sub-folders", totalSubdirs))); 1393 } 1394 d->m_ui->stopCalculateSizeBtn->setEnabled(false); 1395 // just in case you change something and try again :) 1396 d->m_ui->calculateSizeBtn->setText(i18n("Refresh")); 1397 d->m_ui->calculateSizeBtn->setEnabled(true); 1398 d->dirSizeJob = nullptr; 1399 delete d->dirSizeUpdateTimer; 1400 d->dirSizeUpdateTimer = nullptr; 1401 } 1402 1403 void KFilePropsPlugin::slotSizeDetermine() 1404 { 1405 d->m_ui->sizeLabel->setText(i18n("Calculating...\n")); 1406 // qDebug() << "properties->item()=" << properties->item() << "URL=" << properties->item().url(); 1407 1408 d->dirSizeJob = KIO::directorySize(properties->items()); 1409 d->dirSizeUpdateTimer = new QTimer(this); 1410 connect(d->dirSizeUpdateTimer, &QTimer::timeout, this, &KFilePropsPlugin::slotDirSizeUpdate); 1411 d->dirSizeUpdateTimer->start(500); 1412 connect(d->dirSizeJob, &KJob::result, this, &KFilePropsPlugin::slotDirSizeFinished); 1413 d->m_ui->stopCalculateSizeBtn->setEnabled(true); 1414 d->m_ui->calculateSizeBtn->setEnabled(false); 1415 1416 // also update the "Free disk space" display 1417 if (!d->m_ui->capacityBar->isHidden()) { 1418 const KFileItem item = properties->item(); 1419 KIO::FileSystemFreeSpaceJob *job = KIO::fileSystemFreeSpace(item.url()); 1420 connect(job, &KIO::FileSystemFreeSpaceJob::result, this, &KFilePropsPlugin::slotFreeSpaceResult); 1421 } 1422 } 1423 1424 void KFilePropsPlugin::slotSizeStop() 1425 { 1426 if (d->dirSizeJob) { 1427 KIO::filesize_t totalSize = d->dirSizeJob->totalSize(); 1428 d->m_ui->sizeLabel->setText(i18n("At least %1\n", KIO::convertSize(totalSize))); 1429 d->dirSizeJob->kill(); 1430 d->dirSizeJob = nullptr; 1431 } 1432 if (d->dirSizeUpdateTimer) { 1433 d->dirSizeUpdateTimer->stop(); 1434 } 1435 1436 d->m_ui->stopCalculateSizeBtn->setEnabled(false); 1437 d->m_ui->calculateSizeBtn->setEnabled(true); 1438 } 1439 1440 void KFilePropsPlugin::slotSizeDetails() 1441 { 1442 // Open the current folder in filelight 1443 KService::Ptr service = KService::serviceByDesktopName(QStringLiteral("org.kde.filelight")); 1444 if (service) { 1445 auto *job = new KIO::ApplicationLauncherJob(service); 1446 job->setUrls({properties->url()}); 1447 job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, properties)); 1448 job->start(); 1449 } 1450 } 1451 1452 KFilePropsPlugin::~KFilePropsPlugin() = default; 1453 1454 bool KFilePropsPlugin::supports(const KFileItemList & /*_items*/) 1455 { 1456 return true; 1457 } 1458 1459 void KFilePropsPlugin::applyChanges() 1460 { 1461 if (d->dirSizeJob) { 1462 slotSizeStop(); 1463 } 1464 1465 // qDebug() << "KFilePropsPlugin::applyChanges"; 1466 1467 if (!d->m_ui->fileNameLineEdit->isHidden()) { 1468 QString n = d->m_ui->fileNameLineEdit->text(); 1469 // Remove trailing spaces (#4345) 1470 while (!n.isEmpty() && n[n.length() - 1].isSpace()) { 1471 n.chop(1); 1472 } 1473 if (n.isEmpty()) { 1474 KMessageBox::error(properties, i18n("The new file name is empty.")); 1475 properties->abortApplying(); 1476 return; 1477 } 1478 1479 // Do we need to rename the file ? 1480 // qDebug() << "oldname = " << d->oldName; 1481 // qDebug() << "newname = " << n; 1482 if (d->oldName != n || d->m_bFromTemplate) { // true for any from-template file 1483 KIO::CopyJob *job = nullptr; 1484 QUrl oldurl = properties->url(); 1485 1486 QString newFileName = KIO::encodeFileName(n); 1487 if (d->bDesktopFile && !newFileName.endsWith(QLatin1String(".desktop")) && !newFileName.endsWith(QLatin1String(".kdelnk"))) { 1488 newFileName += QLatin1String(".desktop"); 1489 } 1490 1491 // Tell properties. Warning, this changes the result of properties->url() ! 1492 properties->rename(newFileName); 1493 1494 // Update also relative path (for apps) 1495 if (!d->m_sRelativePath.isEmpty()) { 1496 determineRelativePath(properties->url().toLocalFile()); 1497 } 1498 1499 // qDebug() << "New URL = " << properties->url(); 1500 // qDebug() << "old = " << oldurl.url(); 1501 1502 // Don't remove the template !! 1503 if (!d->m_bFromTemplate) { // (normal renaming) 1504 job = KIO::moveAs(oldurl, properties->url()); 1505 } else { // Copying a template 1506 job = KIO::copyAs(oldurl, properties->url()); 1507 } 1508 KJobWidgets::setWindow(job, properties); 1509 connect(job, &KJob::result, this, &KFilePropsPlugin::slotCopyFinished); 1510 connect(job, &KIO::CopyJob::renamed, this, &KFilePropsPlugin::slotFileRenamed); 1511 return; 1512 } 1513 1514 properties->updateUrl(properties->url()); 1515 // Update also relative path (for apps) 1516 if (!d->m_sRelativePath.isEmpty()) { 1517 determineRelativePath(properties->url().toLocalFile()); 1518 } 1519 } 1520 1521 // No job, keep going 1522 slotCopyFinished(nullptr); 1523 } 1524 1525 void KFilePropsPlugin::slotCopyFinished(KJob *job) 1526 { 1527 // qDebug() << "KFilePropsPlugin::slotCopyFinished"; 1528 if (job) { 1529 if (job->error()) { 1530 job->uiDelegate()->showErrorMessage(); 1531 // Didn't work. Revert the URL to the old one 1532 properties->updateUrl(static_cast<KIO::CopyJob *>(job)->srcUrls().constFirst()); 1533 properties->abortApplying(); // Don't apply the changes to the wrong file ! 1534 return; 1535 } 1536 } 1537 1538 Q_ASSERT(!properties->item().isNull()); 1539 Q_ASSERT(!properties->item().url().isEmpty()); 1540 1541 // Save the file locally 1542 if (d->bDesktopFile && !d->m_sRelativePath.isEmpty()) { 1543 // qDebug() << "KFilePropsPlugin::slotCopyFinished " << d->m_sRelativePath; 1544 const QString newPath = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + QLatin1Char('/') + d->m_sRelativePath; 1545 const QUrl newURL = QUrl::fromLocalFile(newPath); 1546 // qDebug() << "KFilePropsPlugin::slotCopyFinished path=" << newURL; 1547 properties->updateUrl(newURL); 1548 } 1549 1550 if (d->bKDesktopMode && d->bDesktopFile) { 1551 // Renamed? Update Name field 1552 // Note: The desktop KIO worker does this as well, but not when 1553 // the file is copied from a template. 1554 if (d->m_bFromTemplate) { 1555 KIO::StatJob *job = KIO::stat(properties->url()); 1556 job->exec(); 1557 KIO::UDSEntry entry = job->statResult(); 1558 1559 KFileItem item(entry, properties->url()); 1560 KDesktopFile config(item.localPath()); 1561 KConfigGroup cg = config.desktopGroup(); 1562 QString nameStr = nameFromFileName(properties->url().fileName()); 1563 cg.writeEntry("Name", nameStr); 1564 cg.writeEntry("Name", nameStr, KConfigGroup::Persistent | KConfigGroup::Localized); 1565 } 1566 } 1567 1568 if (!d->m_ui->symlinkTargetEdit->isHidden() && !d->bMultiple) { 1569 const KFileItem item = properties->item(); 1570 const QString newTarget = d->m_ui->symlinkTargetEdit->text(); 1571 if (newTarget != item.linkDest()) { 1572 // qDebug() << "Updating target of symlink to" << newTarget; 1573 KIO::Job *job = KIO::symlink(newTarget, item.url(), KIO::Overwrite); 1574 job->uiDelegate()->setAutoErrorHandlingEnabled(true); 1575 job->exec(); 1576 } 1577 } 1578 1579 // "Link to Application" templates need to be made executable 1580 // Instead of matching against a filename we check if the destination 1581 // is an Application now. 1582 if (d->m_bFromTemplate) { 1583 // destination is not necessarily local, use the src template 1584 KDesktopFile templateResult(static_cast<KIO::CopyJob *>(job)->srcUrls().constFirst().toLocalFile()); 1585 if (templateResult.hasApplicationType()) { 1586 // We can either stat the file and add the +x bit or use the larger chmod() job 1587 // with a umask designed to only touch u+x. This is only one KIO job, so let's 1588 // do that. 1589 1590 KFileItem appLink(properties->item()); 1591 KFileItemList fileItemList; 1592 fileItemList << appLink; 1593 1594 // first 0100 adds u+x, second 0100 only allows chmod to change u+x 1595 KIO::Job *chmodJob = KIO::chmod(fileItemList, 0100, 0100, QString(), QString(), KIO::HideProgressInfo); 1596 chmodJob->exec(); 1597 } 1598 } 1599 1600 setDirty(false); 1601 Q_EMIT changesApplied(); 1602 } 1603 1604 void KFilePropsPlugin::applyIconChanges() 1605 { 1606 if (d->m_ui->iconButton->isHidden() || !d->bIconChanged) { 1607 return; 1608 } 1609 // handle icon changes - only local files (or pseudo-local) for now 1610 // TODO: Use KTempFile and KIO::file_copy with overwrite = true 1611 QUrl url = properties->url(); 1612 KIO::StatJob *job = KIO::mostLocalUrl(url); 1613 KJobWidgets::setWindow(job, properties); 1614 job->exec(); 1615 url = job->mostLocalUrl(); 1616 1617 if (url.isLocalFile()) { 1618 QString path; 1619 1620 if (Utils::isDirMask(properties->item().mode())) { 1621 path = url.toLocalFile() + QLatin1String("/.directory"); 1622 // don't call updateUrl because the other tabs (i.e. permissions) 1623 // apply to the directory, not the .directory file. 1624 } else { 1625 path = url.toLocalFile(); 1626 } 1627 1628 // Get the default image 1629 QMimeDatabase db; 1630 const QString str = db.mimeTypeForFile(url.toLocalFile(), QMimeDatabase::MatchExtension).iconName(); 1631 // Is it another one than the default ? 1632 QString sIcon; 1633 if (const QString currIcon = d->m_ui->iconButton->icon(); str != currIcon) { 1634 sIcon = currIcon; 1635 } 1636 // (otherwise write empty value) 1637 1638 // qDebug() << "**" << path << "**"; 1639 1640 // If default icon and no .directory file -> don't create one 1641 if (!sIcon.isEmpty() || QFile::exists(path)) { 1642 KDesktopFile cfg(path); 1643 // qDebug() << "sIcon = " << (sIcon); 1644 // qDebug() << "str = " << (str); 1645 cfg.desktopGroup().writeEntry("Icon", sIcon); 1646 cfg.sync(); 1647 1648 cfg.reparseConfiguration(); 1649 if (cfg.desktopGroup().readEntry("Icon") != sIcon) { 1650 properties->abortApplying(); 1651 1652 KMessageBox::error(nullptr, couldNotSaveMsg(path)); 1653 } 1654 } 1655 } 1656 } 1657 1658 void KFilePropsPlugin::updateDefaultHandler(const QString &mimeType) 1659 { 1660 const bool isGeneric = d->mimeType == QLatin1String("application/octet-stream"); 1661 1662 const auto service = KApplicationTrader::preferredService(mimeType); 1663 if (!isGeneric && service) { 1664 const int iconSize = properties->style()->pixelMetric(QStyle::PM_SmallIconSize); 1665 d->m_ui->defaultHandlerIcon->setPixmap(QIcon::fromTheme(service->icon()).pixmap(iconSize)); 1666 d->m_ui->defaultHandlerIcon->show(); 1667 d->m_ui->defaultHandlerLabel->setText(service->name()); 1668 d->m_ui->defaultHandlerLabel->setDisabled(false); 1669 } else { 1670 d->m_ui->defaultHandlerIcon->hide(); 1671 if (isGeneric) { 1672 d->m_ui->defaultHandlerLabel->setText(i18n("No registered file type")); 1673 } else { 1674 d->m_ui->defaultHandlerLabel->setText(i18n("No associated application")); 1675 } 1676 d->m_ui->defaultHandlerLabel->setDisabled(true); 1677 } 1678 1679 if (isGeneric) { 1680 d->m_ui->configureMimeBtn->setText(i18nc("@action:button Create new file type", "Create…")); 1681 d->m_ui->configureMimeBtn->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); 1682 } else { 1683 d->m_ui->configureMimeBtn->setText(i18nc("@action:button", "Change…")); 1684 d->m_ui->configureMimeBtn->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); 1685 } 1686 } 1687 1688 void KFilePropsPlugin::slotFileRenamed(KIO::Job *, const QUrl &, const QUrl &newUrl) 1689 { 1690 // This is called in case of an existing local file during the copy/move operation, 1691 // if the user chooses Rename. 1692 properties->updateUrl(newUrl); 1693 } 1694 1695 void KFilePropsPlugin::postApplyChanges() 1696 { 1697 // Save the icon only after applying the permissions changes (#46192) 1698 applyIconChanges(); 1699 1700 const KFileItemList items = properties->items(); 1701 const QList<QUrl> lst = items.urlList(); 1702 org::kde::KDirNotify::emitFilesChanged(QList<QUrl>(lst)); 1703 } 1704 1705 class KFilePermissionsPropsPlugin::KFilePermissionsPropsPluginPrivate 1706 { 1707 public: 1708 KFilePermissionsPropsPluginPrivate() 1709 { 1710 } 1711 ~KFilePermissionsPropsPluginPrivate() 1712 { 1713 } 1714 1715 QFrame *m_frame = nullptr; 1716 QCheckBox *cbRecursive = nullptr; 1717 QLabel *explanationLabel = nullptr; 1718 QComboBox *ownerPermCombo = nullptr; 1719 QComboBox *groupPermCombo = nullptr; 1720 QComboBox *othersPermCombo = nullptr; 1721 QCheckBox *extraCheckbox = nullptr; 1722 mode_t partialPermissions; 1723 KFilePermissionsPropsPlugin::PermissionsMode pmode; 1724 bool canChangePermissions; 1725 bool isIrregular; 1726 bool hasExtendedACL; 1727 KACL extendedACL; 1728 KACL defaultACL; 1729 bool fileSystemSupportsACLs; 1730 1731 QComboBox *grpCombo = nullptr; 1732 1733 KLineEdit *usrEdit = nullptr; 1734 KLineEdit *grpEdit = nullptr; 1735 1736 // Old permissions 1737 mode_t permissions; 1738 // Old group 1739 QString strGroup; 1740 // Old owner 1741 QString strOwner; 1742 }; 1743 1744 static constexpr mode_t UniOwner{S_IRUSR | S_IWUSR | S_IXUSR}; 1745 static constexpr mode_t UniGroup{S_IRGRP | S_IWGRP | S_IXGRP}; 1746 static constexpr mode_t UniOthers{S_IROTH | S_IWOTH | S_IXOTH}; 1747 static constexpr mode_t UniRead{S_IRUSR | S_IRGRP | S_IROTH}; 1748 static constexpr mode_t UniWrite{S_IWUSR | S_IWGRP | S_IWOTH}; 1749 static constexpr mode_t UniExec{S_IXUSR | S_IXGRP | S_IXOTH}; 1750 static constexpr mode_t UniSpecial{S_ISUID | S_ISGID | S_ISVTX}; 1751 static constexpr mode_t s_invalid_mode_t{static_cast<mode_t>(-1)}; 1752 1753 // synced with PermissionsTarget 1754 constexpr mode_t KFilePermissionsPropsPlugin::permissionsMasks[3] = {UniOwner, UniGroup, UniOthers}; 1755 constexpr mode_t KFilePermissionsPropsPlugin::standardPermissions[4] = {0, UniRead, UniRead | UniWrite, s_invalid_mode_t}; 1756 1757 // synced with PermissionsMode and standardPermissions 1758 static constexpr KLazyLocalizedString permissionsTexts[4][4] = { 1759 {kli18n("No Access"), kli18n("Can Only View"), kli18n("Can View & Modify"), {}}, 1760 {kli18n("No Access"), kli18n("Can Only View Content"), kli18n("Can View & Modify Content"), {}}, 1761 {{}, {}, {}, {}}, // no texts for links 1762 {kli18n("No Access"), kli18n("Can Only View/Read Content"), kli18n("Can View/Read & Modify/Write"), {}}}; 1763 1764 KFilePermissionsPropsPlugin::KFilePermissionsPropsPlugin(KPropertiesDialog *_props) 1765 : KPropertiesDialogPlugin(_props) 1766 , d(new KFilePermissionsPropsPluginPrivate) 1767 { 1768 const auto &[localUrl, isLocal] = properties->item().isMostLocalUrl(); 1769 bool isTrash = (properties->url().scheme() == QLatin1String("trash")); 1770 KUser myself(KUser::UseEffectiveUID); 1771 const bool IamRoot = myself.isSuperUser(); 1772 1773 const KFileItem firstItem = properties->item(); 1774 bool isLink = firstItem.isLink(); 1775 bool isDir = firstItem.isDir(); // all dirs 1776 bool hasDir = firstItem.isDir(); // at least one dir 1777 d->permissions = firstItem.permissions(); // common permissions to all files 1778 d->partialPermissions = d->permissions; // permissions that only some files have (at first we take everything) 1779 d->isIrregular = isIrregular(d->permissions, isDir, isLink); 1780 d->strOwner = firstItem.user(); 1781 d->strGroup = firstItem.group(); 1782 d->hasExtendedACL = firstItem.ACL().isExtended() || firstItem.defaultACL().isValid(); 1783 d->extendedACL = firstItem.ACL(); 1784 d->defaultACL = firstItem.defaultACL(); 1785 d->fileSystemSupportsACLs = false; 1786 1787 if (properties->items().count() > 1) { 1788 // Multiple items: see what they have in common 1789 const KFileItemList items = properties->items(); 1790 for (const auto &item : items) { 1791 if (item == firstItem) { // No need to check the first one again 1792 continue; 1793 } 1794 1795 const bool isItemDir = item.isDir(); 1796 const bool isItemLink = item.isLink(); 1797 1798 if (!d->isIrregular) { 1799 d->isIrregular |= isIrregular(item.permissions(), isItemDir == isDir, isItemLink == isLink); 1800 } 1801 1802 d->hasExtendedACL = d->hasExtendedACL || item.hasExtendedACL(); 1803 1804 if (isItemLink != isLink) { 1805 isLink = false; 1806 } 1807 1808 if (isItemDir != isDir) { 1809 isDir = false; 1810 } 1811 hasDir |= isItemDir; 1812 1813 if (item.permissions() != d->permissions) { 1814 d->permissions &= item.permissions(); 1815 d->partialPermissions |= item.permissions(); 1816 } 1817 1818 if (item.user() != d->strOwner) { 1819 d->strOwner.clear(); 1820 } 1821 1822 if (item.group() != d->strGroup) { 1823 d->strGroup.clear(); 1824 } 1825 } 1826 } 1827 1828 if (isLink) { 1829 d->pmode = PermissionsOnlyLinks; 1830 } else if (isDir) { 1831 d->pmode = PermissionsOnlyDirs; 1832 } else if (hasDir) { 1833 d->pmode = PermissionsMixed; 1834 } else { 1835 d->pmode = PermissionsOnlyFiles; 1836 } 1837 1838 // keep only what's not in the common permissions 1839 d->partialPermissions = d->partialPermissions & ~d->permissions; 1840 1841 bool isMyFile = false; 1842 1843 if (isLocal && !d->strOwner.isEmpty()) { // local files, and all owned by the same person 1844 if (myself.isValid()) { 1845 isMyFile = (d->strOwner == myself.loginName()); 1846 } else { 1847 qCWarning(KIO_WIDGETS) << "I don't exist ?! geteuid=" << KUserId::currentEffectiveUserId().toString(); 1848 } 1849 } else { 1850 // We don't know, for remote files, if they are ours or not. 1851 // So we let the user change permissions, and 1852 // KIO::chmod will tell, if he had no right to do it. 1853 isMyFile = true; 1854 } 1855 1856 d->canChangePermissions = (isMyFile || IamRoot) && (!isLink); 1857 1858 // create GUI 1859 1860 d->m_frame = new QFrame(); 1861 properties->addPage(d->m_frame, i18n("&Permissions")); 1862 1863 QBoxLayout *box = new QVBoxLayout(d->m_frame); 1864 box->setContentsMargins(0, 0, 0, 0); 1865 1866 QWidget *l; 1867 QLabel *lbl; 1868 QGroupBox *gb; 1869 QGridLayout *gl; 1870 QPushButton *pbAdvancedPerm = nullptr; 1871 1872 /* Group: Access Permissions */ 1873 gb = new QGroupBox(i18n("Access Permissions"), d->m_frame); 1874 box->addWidget(gb); 1875 1876 gl = new QGridLayout(gb); 1877 gl->setColumnStretch(1, 1); 1878 1879 l = d->explanationLabel = new QLabel(gb); 1880 if (isLink) { 1881 d->explanationLabel->setText( 1882 i18np("This file is a link and does not have permissions.", "All files are links and do not have permissions.", properties->items().count())); 1883 } else if (!d->canChangePermissions) { 1884 d->explanationLabel->setText(i18n("Only the owner can change permissions.")); 1885 } 1886 gl->addWidget(l, 0, 0, 1, 2); 1887 1888 lbl = new QLabel(i18n("O&wner:"), gb); 1889 gl->addWidget(lbl, 1, 0, Qt::AlignRight); 1890 l = d->ownerPermCombo = new QComboBox(gb); 1891 lbl->setBuddy(l); 1892 gl->addWidget(l, 1, 1); 1893 connect(d->ownerPermCombo, qOverload<int>(&QComboBox::activated), this, &KPropertiesDialogPlugin::changed); 1894 l->setWhatsThis(i18n("Specifies the actions that the owner is allowed to do.")); 1895 1896 lbl = new QLabel(i18n("Gro&up:"), gb); 1897 gl->addWidget(lbl, 2, 0, Qt::AlignRight); 1898 l = d->groupPermCombo = new QComboBox(gb); 1899 lbl->setBuddy(l); 1900 gl->addWidget(l, 2, 1); 1901 connect(d->groupPermCombo, qOverload<int>(&QComboBox::activated), this, &KPropertiesDialogPlugin::changed); 1902 l->setWhatsThis(i18n("Specifies the actions that the members of the group are allowed to do.")); 1903 1904 lbl = new QLabel(i18n("O&thers:"), gb); 1905 gl->addWidget(lbl, 3, 0, Qt::AlignRight); 1906 l = d->othersPermCombo = new QComboBox(gb); 1907 lbl->setBuddy(l); 1908 gl->addWidget(l, 3, 1); 1909 connect(d->othersPermCombo, qOverload<int>(&QComboBox::activated), this, &KPropertiesDialogPlugin::changed); 1910 l->setWhatsThis( 1911 i18n("Specifies the actions that all users, who are neither " 1912 "owner nor in the group, are allowed to do.")); 1913 1914 if (!isLink) { 1915 l = d->extraCheckbox = new QCheckBox(hasDir ? i18n("Only own&er can rename and delete folder content") : i18n("Is &executable"), gb); 1916 connect(d->extraCheckbox, &QAbstractButton::clicked, this, &KPropertiesDialogPlugin::changed); 1917 gl->addWidget(l, 4, 1); 1918 l->setWhatsThis(hasDir ? i18n("Enable this option to allow only the folder's owner to " 1919 "delete or rename the contained files and folders. Other " 1920 "users can only add new files, which requires the 'Modify " 1921 "Content' permission.") 1922 : i18n("Enable this option to mark the file as executable. This only makes " 1923 "sense for programs and scripts. It is required when you want to " 1924 "execute them.")); 1925 1926 QLayoutItem *spacer = new QSpacerItem(0, 20, QSizePolicy::Minimum, QSizePolicy::Expanding); 1927 gl->addItem(spacer, 5, 0, 1, 3); 1928 1929 pbAdvancedPerm = new QPushButton(i18n("A&dvanced Permissions"), gb); 1930 gl->addWidget(pbAdvancedPerm, 6, 0, 1, 2, Qt::AlignRight); 1931 connect(pbAdvancedPerm, &QAbstractButton::clicked, this, &KFilePermissionsPropsPlugin::slotShowAdvancedPermissions); 1932 } else { 1933 d->extraCheckbox = nullptr; 1934 } 1935 1936 /**** Group: Ownership ****/ 1937 gb = new QGroupBox(i18n("Ownership"), d->m_frame); 1938 box->addWidget(gb); 1939 1940 gl = new QGridLayout(gb); 1941 gl->addItem(new QSpacerItem(0, 10), 0, 0); 1942 1943 /*** Set Owner ***/ 1944 l = new QLabel(i18n("User:"), gb); 1945 gl->addWidget(l, 1, 0, Qt::AlignRight); 1946 1947 /* GJ: Don't autocomplete more than 1000 users. This is a kind of random 1948 * value. Huge sites having 10.000+ user have a fair chance of using NIS, 1949 * (possibly) making this unacceptably slow. 1950 * OTOH, it is nice to offer this functionality for the standard user. 1951 */ 1952 int maxEntries = 1000; 1953 1954 /* File owner: For root, offer a KLineEdit with autocompletion. 1955 * For a user, who can never chown() a file, offer a QLabel. 1956 */ 1957 if (IamRoot && isLocal) { 1958 d->usrEdit = new KLineEdit(gb); 1959 KCompletion *kcom = d->usrEdit->completionObject(); 1960 kcom->setOrder(KCompletion::Sorted); 1961 QStringList userNames = KUser::allUserNames(maxEntries); 1962 kcom->setItems(userNames); 1963 d->usrEdit->setCompletionMode((userNames.size() < maxEntries) ? KCompletion::CompletionAuto : KCompletion::CompletionNone); 1964 d->usrEdit->setText(d->strOwner); 1965 gl->addWidget(d->usrEdit, 1, 1); 1966 connect(d->usrEdit, &QLineEdit::textChanged, this, &KPropertiesDialogPlugin::changed); 1967 } else { 1968 l = new QLabel(d->strOwner, gb); 1969 gl->addWidget(l, 1, 1); 1970 } 1971 1972 /*** Set Group ***/ 1973 QStringList groupList = myself.groupNames(); 1974 const bool isMyGroup = groupList.contains(d->strGroup); 1975 1976 /* add the group the file currently belongs to .. 1977 * .. if it is not there already 1978 */ 1979 if (!isMyGroup) { 1980 groupList += d->strGroup; 1981 } 1982 1983 l = new QLabel(i18n("Group:"), gb); 1984 gl->addWidget(l, 2, 0, Qt::AlignRight); 1985 1986 /* Set group: if possible to change: 1987 * - Offer a KLineEdit for root, since he can change to any group. 1988 * - Offer a QComboBox for a normal user, since he can change to a fixed 1989 * (small) set of groups only. 1990 * If not changeable: offer a QLabel. 1991 */ 1992 if (IamRoot && isLocal) { 1993 d->grpEdit = new KLineEdit(gb); 1994 KCompletion *kcom = new KCompletion; 1995 kcom->setItems(groupList); 1996 d->grpEdit->setCompletionObject(kcom, true); 1997 d->grpEdit->setAutoDeleteCompletionObject(true); 1998 d->grpEdit->setCompletionMode(KCompletion::CompletionAuto); 1999 d->grpEdit->setText(d->strGroup); 2000 gl->addWidget(d->grpEdit, 2, 1); 2001 connect(d->grpEdit, &QLineEdit::textChanged, this, &KPropertiesDialogPlugin::changed); 2002 } else if ((groupList.count() > 1) && isMyFile && isLocal) { 2003 d->grpCombo = new QComboBox(gb); 2004 d->grpCombo->setObjectName(QStringLiteral("combogrouplist")); 2005 d->grpCombo->addItems(groupList); 2006 d->grpCombo->setCurrentIndex(groupList.indexOf(d->strGroup)); 2007 gl->addWidget(d->grpCombo, 2, 1); 2008 connect(d->grpCombo, qOverload<int>(&QComboBox::activated), this, &KPropertiesDialogPlugin::changed); 2009 } else { 2010 l = new QLabel(d->strGroup, gb); 2011 gl->addWidget(l, 2, 1); 2012 } 2013 2014 gl->setColumnStretch(2, 10); 2015 2016 // "Apply recursive" checkbox 2017 if (hasDir && !isLink && !isTrash) { 2018 d->cbRecursive = new QCheckBox(i18n("Apply changes to all subfolders and their contents"), d->m_frame); 2019 connect(d->cbRecursive, &QAbstractButton::clicked, this, &KPropertiesDialogPlugin::changed); 2020 box->addWidget(d->cbRecursive); 2021 } 2022 2023 updateAccessControls(); 2024 2025 if (isTrash) { 2026 // don't allow to change properties for file into trash 2027 enableAccessControls(false); 2028 if (pbAdvancedPerm) { 2029 pbAdvancedPerm->setEnabled(false); 2030 } 2031 } 2032 2033 box->addStretch(10); 2034 } 2035 2036 #if HAVE_POSIX_ACL 2037 static bool fileSystemSupportsACL(const QByteArray &path) 2038 { 2039 bool fileSystemSupportsACLs = false; 2040 #ifdef Q_OS_FREEBSD 2041 // FIXME: unbreak and enable this 2042 // Maybe use pathconf(2) to perform this check? 2043 // struct statfs buf; 2044 // fileSystemSupportsACLs = (statfs(path.data(), &buf) == 0) && (buf.f_flags & MNT_ACLS); 2045 Q_UNUSED(path); 2046 #elif defined Q_OS_MACOS 2047 fileSystemSupportsACLs = getxattr(path.data(), "system.posix_acl_access", nullptr, 0, 0, XATTR_NOFOLLOW) >= 0 || errno == ENODATA; 2048 #else 2049 fileSystemSupportsACLs = getxattr(path.data(), "system.posix_acl_access", nullptr, 0) >= 0 || errno == ENODATA; 2050 #endif 2051 return fileSystemSupportsACLs; 2052 } 2053 #endif 2054 2055 void KFilePermissionsPropsPlugin::slotShowAdvancedPermissions() 2056 { 2057 bool isDir = (d->pmode == PermissionsOnlyDirs) || (d->pmode == PermissionsMixed); 2058 QDialog dlg(properties); 2059 dlg.setModal(true); 2060 dlg.setWindowTitle(i18n("Advanced Permissions")); 2061 2062 QLabel *l; 2063 QLabel *cl[3]; 2064 QGroupBox *gb; 2065 QGridLayout *gl; 2066 2067 QVBoxLayout *vbox = new QVBoxLayout(&dlg); 2068 // Group: Access Permissions 2069 gb = new QGroupBox(i18n("Access Permissions"), &dlg); 2070 vbox->addWidget(gb); 2071 2072 gl = new QGridLayout(gb); 2073 gl->addItem(new QSpacerItem(0, 10), 0, 0); 2074 2075 QVector<QWidget *> theNotSpecials; 2076 2077 l = new QLabel(i18n("Class"), gb); 2078 gl->addWidget(l, 1, 0); 2079 theNotSpecials.append(l); 2080 2081 QString readWhatsThis; 2082 QString readLabel; 2083 if (isDir) { 2084 readLabel = i18n("Show\nEntries"); 2085 readWhatsThis = i18n("This flag allows viewing the content of the folder."); 2086 } else { 2087 readLabel = i18n("Read"); 2088 readWhatsThis = i18n("The Read flag allows viewing the content of the file."); 2089 } 2090 2091 QString writeWhatsThis; 2092 QString writeLabel; 2093 if (isDir) { 2094 writeLabel = i18n("Write\nEntries"); 2095 writeWhatsThis = i18n( 2096 "This flag allows adding, renaming and deleting of files. " 2097 "Note that deleting and renaming can be limited using the Sticky flag."); 2098 } else { 2099 writeLabel = i18n("Write"); 2100 writeWhatsThis = i18n("The Write flag allows modifying the content of the file."); 2101 } 2102 2103 QString execLabel; 2104 QString execWhatsThis; 2105 if (isDir) { 2106 execLabel = i18nc("Enter folder", "Enter"); 2107 execWhatsThis = i18n("Enable this flag to allow entering the folder."); 2108 } else { 2109 execLabel = i18n("Exec"); 2110 execWhatsThis = i18n("Enable this flag to allow executing the file as a program."); 2111 } 2112 // GJ: Add space between normal and special modes 2113 QSize size = l->sizeHint(); 2114 size.setWidth(size.width() + 15); 2115 l->setFixedSize(size); 2116 gl->addWidget(l, 1, 3); 2117 2118 l = new QLabel(i18n("Special"), gb); 2119 gl->addWidget(l, 1, 4, 1, 1); 2120 QString specialWhatsThis; 2121 if (isDir) { 2122 specialWhatsThis = i18n( 2123 "Special flag. Valid for the whole folder, the exact " 2124 "meaning of the flag can be seen in the right hand column."); 2125 } else { 2126 specialWhatsThis = i18n( 2127 "Special flag. The exact meaning of the flag can be seen " 2128 "in the right hand column."); 2129 } 2130 l->setWhatsThis(specialWhatsThis); 2131 2132 cl[0] = new QLabel(i18n("User"), gb); 2133 gl->addWidget(cl[0], 2, 0); 2134 theNotSpecials.append(cl[0]); 2135 2136 cl[1] = new QLabel(i18n("Group"), gb); 2137 gl->addWidget(cl[1], 3, 0); 2138 theNotSpecials.append(cl[1]); 2139 2140 cl[2] = new QLabel(i18n("Others"), gb); 2141 gl->addWidget(cl[2], 4, 0); 2142 theNotSpecials.append(cl[2]); 2143 2144 QString setUidWhatsThis; 2145 if (isDir) { 2146 setUidWhatsThis = i18n( 2147 "If this flag is set, the owner of this folder will be " 2148 "the owner of all new files."); 2149 } else { 2150 setUidWhatsThis = i18n( 2151 "If this file is an executable and the flag is set, it will " 2152 "be executed with the permissions of the owner."); 2153 } 2154 2155 QString setGidWhatsThis; 2156 if (isDir) { 2157 setGidWhatsThis = i18n( 2158 "If this flag is set, the group of this folder will be " 2159 "set for all new files."); 2160 } else { 2161 setGidWhatsThis = i18n( 2162 "If this file is an executable and the flag is set, it will " 2163 "be executed with the permissions of the group."); 2164 } 2165 2166 QString stickyWhatsThis; 2167 if (isDir) { 2168 stickyWhatsThis = i18n( 2169 "If the Sticky flag is set on a folder, only the owner " 2170 "and root can delete or rename files. Otherwise everybody " 2171 "with write permissions can do this."); 2172 } else { 2173 stickyWhatsThis = i18n( 2174 "The Sticky flag on a file is ignored on Linux, but may " 2175 "be used on some systems"); 2176 } 2177 mode_t aPermissions = 0; 2178 mode_t aPartialPermissions = 0; 2179 mode_t dummy1 = 0; 2180 mode_t dummy2 = 0; 2181 2182 if (!d->isIrregular) { 2183 switch (d->pmode) { 2184 case PermissionsOnlyFiles: 2185 getPermissionMasks(aPartialPermissions, dummy1, aPermissions, dummy2); 2186 break; 2187 case PermissionsOnlyDirs: 2188 case PermissionsMixed: 2189 getPermissionMasks(dummy1, aPartialPermissions, dummy2, aPermissions); 2190 break; 2191 case PermissionsOnlyLinks: 2192 aPermissions = UniRead | UniWrite | UniExec | UniSpecial; 2193 break; 2194 } 2195 } else { 2196 aPermissions = d->permissions; 2197 aPartialPermissions = d->partialPermissions; 2198 } 2199 2200 // Draw Checkboxes 2201 QCheckBox *cba[3][4]; 2202 for (int row = 0; row < 3; ++row) { 2203 for (int col = 0; col < 4; ++col) { 2204 QCheckBox *cb = new QCheckBox(gb); 2205 if (col != 3) { 2206 theNotSpecials.append(cb); 2207 } 2208 cba[row][col] = cb; 2209 cb->setChecked(aPermissions & fperm[row][col]); 2210 if (aPartialPermissions & fperm[row][col]) { 2211 cb->setTristate(); 2212 cb->setCheckState(Qt::PartiallyChecked); 2213 } else if (d->cbRecursive && d->cbRecursive->isChecked()) { 2214 cb->setTristate(); 2215 } 2216 2217 cb->setEnabled(d->canChangePermissions); 2218 gl->addWidget(cb, row + 2, col + 1); 2219 switch (col) { 2220 case 0: 2221 cb->setText(readLabel); 2222 cb->setWhatsThis(readWhatsThis); 2223 break; 2224 case 1: 2225 cb->setText(writeLabel); 2226 cb->setWhatsThis(writeWhatsThis); 2227 break; 2228 case 2: 2229 cb->setText(execLabel); 2230 cb->setWhatsThis(execWhatsThis); 2231 break; 2232 case 3: 2233 switch (row) { 2234 case 0: 2235 cb->setText(i18n("Set UID")); 2236 cb->setWhatsThis(setUidWhatsThis); 2237 break; 2238 case 1: 2239 cb->setText(i18n("Set GID")); 2240 cb->setWhatsThis(setGidWhatsThis); 2241 break; 2242 case 2: 2243 cb->setText(i18nc("File permission", "Sticky")); 2244 cb->setWhatsThis(stickyWhatsThis); 2245 break; 2246 } 2247 break; 2248 } 2249 } 2250 } 2251 gl->setColumnStretch(6, 10); 2252 2253 #if HAVE_POSIX_ACL 2254 KACLEditWidget *extendedACLs = nullptr; 2255 2256 // FIXME make it work with partial entries 2257 if (properties->items().count() == 1) { 2258 QByteArray path = QFile::encodeName(properties->item().url().toLocalFile()); 2259 d->fileSystemSupportsACLs = fileSystemSupportsACL(path); 2260 } 2261 if (d->fileSystemSupportsACLs) { 2262 std::for_each(theNotSpecials.begin(), theNotSpecials.end(), std::mem_fn(&QWidget::hide)); 2263 extendedACLs = new KACLEditWidget(&dlg); 2264 extendedACLs->setEnabled(d->canChangePermissions); 2265 vbox->addWidget(extendedACLs); 2266 if (d->extendedACL.isValid() && d->extendedACL.isExtended()) { 2267 extendedACLs->setACL(d->extendedACL); 2268 } else { 2269 extendedACLs->setACL(KACL(aPermissions)); 2270 } 2271 2272 if (d->defaultACL.isValid()) { 2273 extendedACLs->setDefaultACL(d->defaultACL); 2274 } 2275 2276 if (properties->items().constFirst().isDir()) { 2277 extendedACLs->setAllowDefaults(true); 2278 } 2279 } 2280 #endif 2281 2282 QDialogButtonBox *buttonBox = new QDialogButtonBox(&dlg); 2283 buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); 2284 connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); 2285 connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); 2286 vbox->addWidget(buttonBox); 2287 2288 if (dlg.exec() != QDialog::Accepted) { 2289 return; 2290 } 2291 2292 mode_t andPermissions = mode_t(~0); 2293 mode_t orPermissions = 0; 2294 for (int row = 0; row < 3; ++row) { 2295 for (int col = 0; col < 4; ++col) { 2296 switch (cba[row][col]->checkState()) { 2297 case Qt::Checked: 2298 orPermissions |= fperm[row][col]; 2299 // fall through 2300 case Qt::Unchecked: 2301 andPermissions &= ~fperm[row][col]; 2302 break; 2303 case Qt::PartiallyChecked: 2304 break; 2305 } 2306 } 2307 } 2308 2309 const KFileItemList items = properties->items(); 2310 d->isIrregular = std::any_of(items.cbegin(), items.cend(), [this, andPermissions, orPermissions](const KFileItem &item) { 2311 return isIrregular((item.permissions() & andPermissions) | orPermissions, item.isDir(), item.isLink()); 2312 }); 2313 2314 d->permissions = orPermissions; 2315 d->partialPermissions = andPermissions; 2316 2317 #if HAVE_POSIX_ACL 2318 // override with the acls, if present 2319 if (extendedACLs) { 2320 d->extendedACL = extendedACLs->getACL(); 2321 d->defaultACL = extendedACLs->getDefaultACL(); 2322 d->hasExtendedACL = d->extendedACL.isExtended() || d->defaultACL.isValid(); 2323 d->permissions = d->extendedACL.basePermissions(); 2324 d->permissions |= (andPermissions | orPermissions) & (S_ISUID | S_ISGID | S_ISVTX); 2325 } 2326 #endif 2327 2328 updateAccessControls(); 2329 Q_EMIT changed(); 2330 } 2331 2332 KFilePermissionsPropsPlugin::~KFilePermissionsPropsPlugin() = default; 2333 2334 bool KFilePermissionsPropsPlugin::supports(const KFileItemList &items) 2335 { 2336 return std::any_of(items.cbegin(), items.cend(), [](const KFileItem &item) { 2337 return KProtocolManager::supportsPermissions(item.url()); 2338 }); 2339 } 2340 2341 // sets a combo box in the Access Control frame 2342 void KFilePermissionsPropsPlugin::setComboContent(QComboBox *combo, PermissionsTarget target, mode_t permissions, mode_t partial) 2343 { 2344 combo->clear(); 2345 if (d->isIrregular) { // #176876 2346 return; 2347 } 2348 2349 if (d->pmode == PermissionsOnlyLinks) { 2350 combo->addItem(i18n("Link")); 2351 combo->setCurrentIndex(0); 2352 return; 2353 } 2354 2355 mode_t tMask = permissionsMasks[target]; 2356 int textIndex; 2357 for (textIndex = 0; standardPermissions[textIndex] != s_invalid_mode_t; ++textIndex) { 2358 if ((standardPermissions[textIndex] & tMask) == (permissions & tMask & (UniRead | UniWrite))) { 2359 break; 2360 } 2361 } 2362 Q_ASSERT(standardPermissions[textIndex] != s_invalid_mode_t); // must not happen, would be irreglar 2363 2364 const auto permsTexts = permissionsTexts[static_cast<int>(d->pmode)]; 2365 for (int i = 0; !permsTexts[i].isEmpty(); ++i) { 2366 combo->addItem(permsTexts[i].toString()); 2367 } 2368 2369 if (partial & tMask & ~UniExec) { 2370 combo->addItem(i18n("Varying (No Change)")); 2371 combo->setCurrentIndex(3); 2372 } else { 2373 combo->setCurrentIndex(textIndex); 2374 } 2375 } 2376 2377 // permissions are irregular if they can't be displayed in a combo box. 2378 bool KFilePermissionsPropsPlugin::isIrregular(mode_t permissions, bool isDir, bool isLink) 2379 { 2380 if (isLink) { // links are always ok 2381 return false; 2382 } 2383 2384 mode_t p = permissions; 2385 if (p & (S_ISUID | S_ISGID)) { // setuid/setgid -> irregular 2386 return true; 2387 } 2388 if (isDir) { 2389 p &= ~S_ISVTX; // ignore sticky on dirs 2390 2391 // check supported flag combinations 2392 mode_t p0 = p & UniOwner; 2393 if ((p0 != 0) && (p0 != (S_IRUSR | S_IXUSR)) && (p0 != UniOwner)) { 2394 return true; 2395 } 2396 p0 = p & UniGroup; 2397 if ((p0 != 0) && (p0 != (S_IRGRP | S_IXGRP)) && (p0 != UniGroup)) { 2398 return true; 2399 } 2400 p0 = p & UniOthers; 2401 if ((p0 != 0) && (p0 != (S_IROTH | S_IXOTH)) && (p0 != UniOthers)) { 2402 return true; 2403 } 2404 return false; 2405 } 2406 if (p & S_ISVTX) { // sticky on file -> irregular 2407 return true; 2408 } 2409 2410 // check supported flag combinations 2411 mode_t p0 = p & UniOwner; 2412 bool usrXPossible = !p0; // true if this file could be an executable 2413 if (p0 & S_IXUSR) { 2414 if ((p0 == S_IXUSR) || (p0 == (S_IWUSR | S_IXUSR))) { 2415 return true; 2416 } 2417 usrXPossible = true; 2418 } else if (p0 == S_IWUSR) { 2419 return true; 2420 } 2421 2422 p0 = p & UniGroup; 2423 bool grpXPossible = !p0; // true if this file could be an executable 2424 if (p0 & S_IXGRP) { 2425 if ((p0 == S_IXGRP) || (p0 == (S_IWGRP | S_IXGRP))) { 2426 return true; 2427 } 2428 grpXPossible = true; 2429 } else if (p0 == S_IWGRP) { 2430 return true; 2431 } 2432 if (p0 == 0) { 2433 grpXPossible = true; 2434 } 2435 2436 p0 = p & UniOthers; 2437 bool othXPossible = !p0; // true if this file could be an executable 2438 if (p0 & S_IXOTH) { 2439 if ((p0 == S_IXOTH) || (p0 == (S_IWOTH | S_IXOTH))) { 2440 return true; 2441 } 2442 othXPossible = true; 2443 } else if (p0 == S_IWOTH) { 2444 return true; 2445 } 2446 2447 // check that there either all targets are executable-compatible, or none 2448 return (p & UniExec) && !(usrXPossible && grpXPossible && othXPossible); 2449 } 2450 2451 // enables/disabled the widgets in the Access Control frame 2452 void KFilePermissionsPropsPlugin::enableAccessControls(bool enable) 2453 { 2454 d->ownerPermCombo->setEnabled(enable); 2455 d->groupPermCombo->setEnabled(enable); 2456 d->othersPermCombo->setEnabled(enable); 2457 if (d->extraCheckbox) { 2458 d->extraCheckbox->setEnabled(enable); 2459 } 2460 if (d->cbRecursive) { 2461 d->cbRecursive->setEnabled(enable); 2462 } 2463 } 2464 2465 // updates all widgets in the Access Control frame 2466 void KFilePermissionsPropsPlugin::updateAccessControls() 2467 { 2468 setComboContent(d->ownerPermCombo, PermissionsOwner, d->permissions, d->partialPermissions); 2469 setComboContent(d->groupPermCombo, PermissionsGroup, d->permissions, d->partialPermissions); 2470 setComboContent(d->othersPermCombo, PermissionsOthers, d->permissions, d->partialPermissions); 2471 2472 switch (d->pmode) { 2473 case PermissionsOnlyLinks: 2474 enableAccessControls(false); 2475 break; 2476 case PermissionsOnlyFiles: 2477 enableAccessControls(d->canChangePermissions && !d->isIrregular && !d->hasExtendedACL); 2478 if (d->canChangePermissions) { 2479 d->explanationLabel->setText( 2480 d->isIrregular || d->hasExtendedACL 2481 ? i18np("This file uses advanced permissions", "These files use advanced permissions.", properties->items().count()) 2482 : QString()); 2483 } 2484 if (d->partialPermissions & UniExec) { 2485 d->extraCheckbox->setTristate(); 2486 d->extraCheckbox->setCheckState(Qt::PartiallyChecked); 2487 } else { 2488 d->extraCheckbox->setTristate(false); 2489 d->extraCheckbox->setChecked(d->permissions & UniExec); 2490 } 2491 break; 2492 case PermissionsOnlyDirs: 2493 enableAccessControls(d->canChangePermissions && !d->isIrregular && !d->hasExtendedACL); 2494 // if this is a dir, and we can change permissions, don't dis-allow 2495 // recursive, we can do that for ACL setting. 2496 if (d->cbRecursive) { 2497 d->cbRecursive->setEnabled(d->canChangePermissions && !d->isIrregular); 2498 } 2499 2500 if (d->canChangePermissions) { 2501 d->explanationLabel->setText( 2502 d->isIrregular || d->hasExtendedACL 2503 ? i18np("This folder uses advanced permissions.", "These folders use advanced permissions.", properties->items().count()) 2504 : QString()); 2505 } 2506 if (d->partialPermissions & S_ISVTX) { 2507 d->extraCheckbox->setTristate(); 2508 d->extraCheckbox->setCheckState(Qt::PartiallyChecked); 2509 } else { 2510 d->extraCheckbox->setTristate(false); 2511 d->extraCheckbox->setChecked(d->permissions & S_ISVTX); 2512 } 2513 break; 2514 case PermissionsMixed: 2515 enableAccessControls(d->canChangePermissions && !d->isIrregular && !d->hasExtendedACL); 2516 if (d->canChangePermissions) { 2517 d->explanationLabel->setText(d->isIrregular || d->hasExtendedACL ? i18n("These files use advanced permissions.") : QString()); 2518 } 2519 if (d->partialPermissions & S_ISVTX) { 2520 d->extraCheckbox->setTristate(); 2521 d->extraCheckbox->setCheckState(Qt::PartiallyChecked); 2522 } else { 2523 d->extraCheckbox->setTristate(false); 2524 d->extraCheckbox->setChecked(d->permissions & S_ISVTX); 2525 } 2526 break; 2527 } 2528 } 2529 2530 // gets masks for files and dirs from the Access Control frame widgets 2531 void KFilePermissionsPropsPlugin::getPermissionMasks(mode_t &andFilePermissions, mode_t &andDirPermissions, mode_t &orFilePermissions, mode_t &orDirPermissions) 2532 { 2533 andFilePermissions = mode_t(~UniSpecial); 2534 andDirPermissions = mode_t(~(S_ISUID | S_ISGID)); 2535 orFilePermissions = 0; 2536 orDirPermissions = 0; 2537 if (d->isIrregular) { 2538 return; 2539 } 2540 2541 mode_t m = standardPermissions[d->ownerPermCombo->currentIndex()]; 2542 if (m != s_invalid_mode_t) { 2543 orFilePermissions |= m & UniOwner; 2544 if ((m & UniOwner) 2545 && ((d->pmode == PermissionsMixed) || ((d->pmode == PermissionsOnlyFiles) && (d->extraCheckbox->checkState() == Qt::PartiallyChecked)))) { 2546 andFilePermissions &= ~(S_IRUSR | S_IWUSR); 2547 } else { 2548 andFilePermissions &= ~(S_IRUSR | S_IWUSR | S_IXUSR); 2549 if ((m & S_IRUSR) && (d->extraCheckbox->checkState() == Qt::Checked)) { 2550 orFilePermissions |= S_IXUSR; 2551 } 2552 } 2553 2554 orDirPermissions |= m & UniOwner; 2555 if (m & S_IRUSR) { 2556 orDirPermissions |= S_IXUSR; 2557 } 2558 andDirPermissions &= ~(S_IRUSR | S_IWUSR | S_IXUSR); 2559 } 2560 2561 m = standardPermissions[d->groupPermCombo->currentIndex()]; 2562 if (m != s_invalid_mode_t) { 2563 orFilePermissions |= m & UniGroup; 2564 if ((m & UniGroup) 2565 && ((d->pmode == PermissionsMixed) || ((d->pmode == PermissionsOnlyFiles) && (d->extraCheckbox->checkState() == Qt::PartiallyChecked)))) { 2566 andFilePermissions &= ~(S_IRGRP | S_IWGRP); 2567 } else { 2568 andFilePermissions &= ~(S_IRGRP | S_IWGRP | S_IXGRP); 2569 if ((m & S_IRGRP) && (d->extraCheckbox->checkState() == Qt::Checked)) { 2570 orFilePermissions |= S_IXGRP; 2571 } 2572 } 2573 2574 orDirPermissions |= m & UniGroup; 2575 if (m & S_IRGRP) { 2576 orDirPermissions |= S_IXGRP; 2577 } 2578 andDirPermissions &= ~(S_IRGRP | S_IWGRP | S_IXGRP); 2579 } 2580 2581 m = d->othersPermCombo->currentIndex() >= 0 ? standardPermissions[d->othersPermCombo->currentIndex()] : s_invalid_mode_t; 2582 if (m != s_invalid_mode_t) { 2583 orFilePermissions |= m & UniOthers; 2584 if ((m & UniOthers) 2585 && ((d->pmode == PermissionsMixed) || ((d->pmode == PermissionsOnlyFiles) && (d->extraCheckbox->checkState() == Qt::PartiallyChecked)))) { 2586 andFilePermissions &= ~(S_IROTH | S_IWOTH); 2587 } else { 2588 andFilePermissions &= ~(S_IROTH | S_IWOTH | S_IXOTH); 2589 if ((m & S_IROTH) && (d->extraCheckbox->checkState() == Qt::Checked)) { 2590 orFilePermissions |= S_IXOTH; 2591 } 2592 } 2593 2594 orDirPermissions |= m & UniOthers; 2595 if (m & S_IROTH) { 2596 orDirPermissions |= S_IXOTH; 2597 } 2598 andDirPermissions &= ~(S_IROTH | S_IWOTH | S_IXOTH); 2599 } 2600 2601 if (((d->pmode == PermissionsMixed) || (d->pmode == PermissionsOnlyDirs)) && (d->extraCheckbox->checkState() != Qt::PartiallyChecked)) { 2602 andDirPermissions &= ~S_ISVTX; 2603 if (d->extraCheckbox->checkState() == Qt::Checked) { 2604 orDirPermissions |= S_ISVTX; 2605 } 2606 } 2607 } 2608 2609 void KFilePermissionsPropsPlugin::applyChanges() 2610 { 2611 mode_t orFilePermissions; 2612 mode_t orDirPermissions; 2613 mode_t andFilePermissions; 2614 mode_t andDirPermissions; 2615 2616 if (!d->canChangePermissions) { 2617 properties->abortApplying(); 2618 return; 2619 } 2620 2621 if (!d->isIrregular) { 2622 getPermissionMasks(andFilePermissions, andDirPermissions, orFilePermissions, orDirPermissions); 2623 } else { 2624 orFilePermissions = d->permissions; 2625 andFilePermissions = d->partialPermissions; 2626 orDirPermissions = d->permissions; 2627 andDirPermissions = d->partialPermissions; 2628 } 2629 2630 QString owner; 2631 QString group; 2632 if (d->usrEdit) { 2633 owner = d->usrEdit->text(); 2634 } 2635 if (d->grpEdit) { 2636 group = d->grpEdit->text(); 2637 } else if (d->grpCombo) { 2638 group = d->grpCombo->currentText(); 2639 } 2640 2641 const bool recursive = d->cbRecursive && d->cbRecursive->isChecked(); 2642 2643 if (!recursive) { 2644 if (owner == d->strOwner) { 2645 owner.clear(); 2646 } 2647 2648 if (group == d->strGroup) { 2649 group.clear(); 2650 } 2651 } 2652 2653 bool permissionChange = false; 2654 2655 const KFileItemList items = properties->items(); 2656 KFileItemList files; 2657 KFileItemList dirs; 2658 for (const auto &item : items) { 2659 const auto perms = item.permissions(); 2660 if (item.isDir()) { 2661 dirs.append(item); 2662 if (!permissionChange && (recursive || perms != ((perms & andDirPermissions) | orDirPermissions))) { 2663 permissionChange = true; 2664 } 2665 continue; 2666 } 2667 2668 if (item.isFile()) { 2669 files.append(item); 2670 if (!permissionChange && perms != ((perms & andFilePermissions) | orFilePermissions)) { 2671 permissionChange = true; 2672 } 2673 } 2674 } 2675 2676 const bool ACLChange = (d->extendedACL != properties->item().ACL()); 2677 const bool defaultACLChange = (d->defaultACL != properties->item().defaultACL()); 2678 2679 if (owner.isEmpty() && group.isEmpty() && !recursive && !permissionChange && !ACLChange && !defaultACLChange) { 2680 return; 2681 } 2682 2683 auto processACLChanges = [this, ACLChange, defaultACLChange](KIO::ChmodJob *chmodJob) { 2684 if (!d->fileSystemSupportsACLs) { 2685 return; 2686 } 2687 2688 if (ACLChange) { 2689 chmodJob->addMetaData(QStringLiteral("ACL_STRING"), d->extendedACL.isValid() ? d->extendedACL.asString() : QStringLiteral("ACL_DELETE")); 2690 } 2691 2692 if (defaultACLChange) { 2693 chmodJob->addMetaData(QStringLiteral("DEFAULT_ACL_STRING"), d->defaultACL.isValid() ? d->defaultACL.asString() : QStringLiteral("ACL_DELETE")); 2694 } 2695 }; 2696 2697 auto chmodDirs = [=]() { 2698 if (dirs.isEmpty()) { 2699 setDirty(false); 2700 Q_EMIT changesApplied(); 2701 return; 2702 } 2703 2704 auto *dirsJob = KIO::chmod(dirs, orDirPermissions, ~andDirPermissions, owner, group, recursive); 2705 processACLChanges(dirsJob); 2706 2707 connect(dirsJob, &KJob::result, this, [this, dirsJob]() { 2708 if (dirsJob->error()) { 2709 dirsJob->uiDelegate()->showErrorMessage(); 2710 } 2711 2712 setDirty(false); 2713 Q_EMIT changesApplied(); 2714 }); 2715 }; 2716 2717 // Change permissions in two steps, first files, then dirs 2718 2719 if (!files.isEmpty()) { 2720 auto *filesJob = KIO::chmod(files, orFilePermissions, ~andFilePermissions, owner, group, false); 2721 processACLChanges(filesJob); 2722 2723 connect(filesJob, &KJob::result, this, [=]() { 2724 if (filesJob->error()) { 2725 filesJob->uiDelegate()->showErrorMessage(); 2726 } 2727 2728 chmodDirs(); 2729 }); 2730 return; 2731 } 2732 2733 // No files to change? OK, now process dirs (if any) 2734 chmodDirs(); 2735 } 2736 2737 class KChecksumsPlugin::KChecksumsPluginPrivate 2738 { 2739 public: 2740 KChecksumsPluginPrivate() 2741 { 2742 } 2743 2744 ~KChecksumsPluginPrivate() 2745 { 2746 } 2747 2748 QWidget m_widget; 2749 Ui::ChecksumsWidget m_ui; 2750 2751 QFileSystemWatcher fileWatcher; 2752 QString m_md5; 2753 QString m_sha1; 2754 QString m_sha256; 2755 QString m_sha512; 2756 }; 2757 2758 KChecksumsPlugin::KChecksumsPlugin(KPropertiesDialog *dialog) 2759 : KPropertiesDialogPlugin(dialog) 2760 , d(new KChecksumsPluginPrivate) 2761 { 2762 d->m_ui.setupUi(&d->m_widget); 2763 properties->addPage(&d->m_widget, i18nc("@title:tab", "C&hecksums")); 2764 2765 d->m_ui.md5CopyButton->hide(); 2766 d->m_ui.sha1CopyButton->hide(); 2767 d->m_ui.sha256CopyButton->hide(); 2768 d->m_ui.sha512CopyButton->hide(); 2769 2770 connect(d->m_ui.lineEdit, &QLineEdit::textChanged, this, [=](const QString &text) { 2771 slotVerifyChecksum(text.toLower()); 2772 }); 2773 2774 connect(d->m_ui.md5Button, &QPushButton::clicked, this, &KChecksumsPlugin::slotShowMd5); 2775 connect(d->m_ui.sha1Button, &QPushButton::clicked, this, &KChecksumsPlugin::slotShowSha1); 2776 connect(d->m_ui.sha256Button, &QPushButton::clicked, this, &KChecksumsPlugin::slotShowSha256); 2777 connect(d->m_ui.sha512Button, &QPushButton::clicked, this, &KChecksumsPlugin::slotShowSha512); 2778 2779 d->fileWatcher.addPath(properties->item().localPath()); 2780 connect(&d->fileWatcher, &QFileSystemWatcher::fileChanged, this, &KChecksumsPlugin::slotInvalidateCache); 2781 2782 auto clipboard = QApplication::clipboard(); 2783 connect(d->m_ui.md5CopyButton, &QPushButton::clicked, this, [=]() { 2784 clipboard->setText(d->m_md5); 2785 }); 2786 2787 connect(d->m_ui.sha1CopyButton, &QPushButton::clicked, this, [=]() { 2788 clipboard->setText(d->m_sha1); 2789 }); 2790 2791 connect(d->m_ui.sha256CopyButton, &QPushButton::clicked, this, [=]() { 2792 clipboard->setText(d->m_sha256); 2793 }); 2794 2795 connect(d->m_ui.sha512CopyButton, &QPushButton::clicked, this, [=]() { 2796 clipboard->setText(d->m_sha512); 2797 }); 2798 2799 connect(d->m_ui.pasteButton, &QPushButton::clicked, this, [=]() { 2800 d->m_ui.lineEdit->setText(clipboard->text()); 2801 }); 2802 2803 setDefaultState(); 2804 } 2805 2806 KChecksumsPlugin::~KChecksumsPlugin() = default; 2807 2808 bool KChecksumsPlugin::supports(const KFileItemList &items) 2809 { 2810 if (items.count() != 1) { 2811 return false; 2812 } 2813 2814 const KFileItem &item = items.first(); 2815 return item.isFile() && !item.localPath().isEmpty() && item.isReadable() && !item.isDesktopFile() && !item.isLink(); 2816 } 2817 2818 void KChecksumsPlugin::slotInvalidateCache() 2819 { 2820 d->m_md5 = QString(); 2821 d->m_sha1 = QString(); 2822 d->m_sha256 = QString(); 2823 d->m_sha512 = QString(); 2824 } 2825 2826 void KChecksumsPlugin::slotShowMd5() 2827 { 2828 auto label = new QLabel(i18nc("@action:button", "Calculating..."), &d->m_widget); 2829 label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); 2830 2831 d->m_ui.calculateWidget->layout()->replaceWidget(d->m_ui.md5Button, label); 2832 d->m_ui.md5Button->hide(); 2833 2834 showChecksum(QCryptographicHash::Md5, label, d->m_ui.md5CopyButton); 2835 } 2836 2837 void KChecksumsPlugin::slotShowSha1() 2838 { 2839 auto label = new QLabel(i18nc("@action:button", "Calculating..."), &d->m_widget); 2840 label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); 2841 2842 d->m_ui.calculateWidget->layout()->replaceWidget(d->m_ui.sha1Button, label); 2843 d->m_ui.sha1Button->hide(); 2844 2845 showChecksum(QCryptographicHash::Sha1, label, d->m_ui.sha1CopyButton); 2846 } 2847 2848 void KChecksumsPlugin::slotShowSha256() 2849 { 2850 auto label = new QLabel(i18nc("@action:button", "Calculating..."), &d->m_widget); 2851 label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); 2852 2853 d->m_ui.calculateWidget->layout()->replaceWidget(d->m_ui.sha256Button, label); 2854 d->m_ui.sha256Button->hide(); 2855 2856 showChecksum(QCryptographicHash::Sha256, label, d->m_ui.sha256CopyButton); 2857 } 2858 2859 void KChecksumsPlugin::slotShowSha512() 2860 { 2861 auto label = new QLabel(i18nc("@action:button", "Calculating..."), &d->m_widget); 2862 label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); 2863 2864 d->m_ui.calculateWidget->layout()->replaceWidget(d->m_ui.sha512Button, label); 2865 d->m_ui.sha512Button->hide(); 2866 2867 showChecksum(QCryptographicHash::Sha512, label, d->m_ui.sha512CopyButton); 2868 } 2869 2870 void KChecksumsPlugin::slotVerifyChecksum(const QString &input) 2871 { 2872 auto algorithm = detectAlgorithm(input); 2873 2874 // Input is not a supported hash algorithm. 2875 if (algorithm == QCryptographicHash::Md4) { 2876 if (input.isEmpty()) { 2877 setDefaultState(); 2878 } else { 2879 setInvalidChecksumState(); 2880 } 2881 return; 2882 } 2883 2884 const QString checksum = cachedChecksum(algorithm); 2885 2886 // Checksum already in cache. 2887 if (!checksum.isEmpty()) { 2888 const bool isMatch = (checksum == input); 2889 if (isMatch) { 2890 setMatchState(); 2891 } else { 2892 setMismatchState(); 2893 } 2894 2895 return; 2896 } 2897 2898 // Calculate checksum in another thread. 2899 auto futureWatcher = new QFutureWatcher<QString>(this); 2900 connect(futureWatcher, &QFutureWatcher<QString>::finished, this, [=]() { 2901 const QString checksum = futureWatcher->result(); 2902 futureWatcher->deleteLater(); 2903 2904 cacheChecksum(checksum, algorithm); 2905 2906 switch (algorithm) { 2907 case QCryptographicHash::Md5: 2908 slotShowMd5(); 2909 break; 2910 case QCryptographicHash::Sha1: 2911 slotShowSha1(); 2912 break; 2913 case QCryptographicHash::Sha256: 2914 slotShowSha256(); 2915 break; 2916 case QCryptographicHash::Sha512: 2917 slotShowSha512(); 2918 break; 2919 default: 2920 break; 2921 } 2922 2923 const bool isMatch = (checksum == input); 2924 if (isMatch) { 2925 setMatchState(); 2926 } else { 2927 setMismatchState(); 2928 } 2929 }); 2930 2931 // Notify the user about the background computation. 2932 setVerifyState(); 2933 2934 auto future = QtConcurrent::run(&KChecksumsPlugin::computeChecksum, algorithm, properties->item().localPath()); 2935 futureWatcher->setFuture(future); 2936 } 2937 2938 bool KChecksumsPlugin::isMd5(const QString &input) 2939 { 2940 QRegularExpression regex(QStringLiteral("^[a-f0-9]{32}$")); 2941 return regex.match(input).hasMatch(); 2942 } 2943 2944 bool KChecksumsPlugin::isSha1(const QString &input) 2945 { 2946 QRegularExpression regex(QStringLiteral("^[a-f0-9]{40}$")); 2947 return regex.match(input).hasMatch(); 2948 } 2949 2950 bool KChecksumsPlugin::isSha256(const QString &input) 2951 { 2952 QRegularExpression regex(QStringLiteral("^[a-f0-9]{64}$")); 2953 return regex.match(input).hasMatch(); 2954 } 2955 2956 bool KChecksumsPlugin::isSha512(const QString &input) 2957 { 2958 QRegularExpression regex(QStringLiteral("^[a-f0-9]{128}$")); 2959 return regex.match(input).hasMatch(); 2960 } 2961 2962 QString KChecksumsPlugin::computeChecksum(QCryptographicHash::Algorithm algorithm, const QString &path) 2963 { 2964 QFile file(path); 2965 if (!file.open(QIODevice::ReadOnly)) { 2966 return QString(); 2967 } 2968 2969 QCryptographicHash hash(algorithm); 2970 hash.addData(&file); 2971 2972 return QString::fromLatin1(hash.result().toHex()); 2973 } 2974 2975 QCryptographicHash::Algorithm KChecksumsPlugin::detectAlgorithm(const QString &input) 2976 { 2977 if (isMd5(input)) { 2978 return QCryptographicHash::Md5; 2979 } 2980 2981 if (isSha1(input)) { 2982 return QCryptographicHash::Sha1; 2983 } 2984 2985 if (isSha256(input)) { 2986 return QCryptographicHash::Sha256; 2987 } 2988 2989 if (isSha512(input)) { 2990 return QCryptographicHash::Sha512; 2991 } 2992 2993 // Md4 used as negative error code. 2994 return QCryptographicHash::Md4; 2995 } 2996 2997 void KChecksumsPlugin::setDefaultState() 2998 { 2999 QColor defaultColor = d->m_widget.palette().color(QPalette::Base); 3000 3001 QPalette palette = d->m_widget.palette(); 3002 palette.setColor(QPalette::Base, defaultColor); 3003 3004 d->m_ui.feedbackLabel->hide(); 3005 d->m_ui.lineEdit->setPalette(palette); 3006 d->m_ui.lineEdit->setToolTip(QString()); 3007 } 3008 3009 void KChecksumsPlugin::setInvalidChecksumState() 3010 { 3011 KColorScheme colorScheme(QPalette::Active, KColorScheme::View); 3012 QColor warningColor = colorScheme.background(KColorScheme::NegativeBackground).color(); 3013 3014 QPalette palette = d->m_widget.palette(); 3015 palette.setColor(QPalette::Base, warningColor); 3016 3017 d->m_ui.feedbackLabel->setText(i18n("Invalid checksum.")); 3018 d->m_ui.feedbackLabel->show(); 3019 d->m_ui.lineEdit->setPalette(palette); 3020 d->m_ui.lineEdit->setToolTip(i18nc("@info:tooltip", "The given input is not a valid MD5, SHA1 or SHA256 checksum.")); 3021 } 3022 3023 void KChecksumsPlugin::setMatchState() 3024 { 3025 KColorScheme colorScheme(QPalette::Active, KColorScheme::View); 3026 QColor positiveColor = colorScheme.background(KColorScheme::PositiveBackground).color(); 3027 3028 QPalette palette = d->m_widget.palette(); 3029 palette.setColor(QPalette::Base, positiveColor); 3030 3031 d->m_ui.feedbackLabel->setText(i18n("Checksums match.")); 3032 d->m_ui.feedbackLabel->show(); 3033 d->m_ui.lineEdit->setPalette(palette); 3034 d->m_ui.lineEdit->setToolTip(i18nc("@info:tooltip", "The computed checksum and the expected checksum match.")); 3035 } 3036 3037 void KChecksumsPlugin::setMismatchState() 3038 { 3039 KColorScheme colorScheme(QPalette::Active, KColorScheme::View); 3040 QColor warningColor = colorScheme.background(KColorScheme::NegativeBackground).color(); 3041 3042 QPalette palette = d->m_widget.palette(); 3043 palette.setColor(QPalette::Base, warningColor); 3044 3045 d->m_ui.feedbackLabel->setText( 3046 i18n("<p>Checksums do not match.</p>" 3047 "This may be due to a faulty download. Try re-downloading the file.<br/>" 3048 "If the verification still fails, contact the source of the file.")); 3049 d->m_ui.feedbackLabel->show(); 3050 d->m_ui.lineEdit->setPalette(palette); 3051 d->m_ui.lineEdit->setToolTip(i18nc("@info:tooltip", "The computed checksum and the expected checksum differ.")); 3052 } 3053 3054 void KChecksumsPlugin::setVerifyState() 3055 { 3056 // Users can paste a checksum at any time, so reset to default. 3057 setDefaultState(); 3058 3059 d->m_ui.feedbackLabel->setText(i18nc("notify the user about a computation in the background", "Verifying checksum...")); 3060 d->m_ui.feedbackLabel->show(); 3061 } 3062 3063 void KChecksumsPlugin::showChecksum(QCryptographicHash::Algorithm algorithm, QLabel *label, QPushButton *copyButton) 3064 { 3065 const QString checksum = cachedChecksum(algorithm); 3066 3067 // Checksum in cache, nothing else to do. 3068 if (!checksum.isEmpty()) { 3069 label->setText(checksum); 3070 return; 3071 } 3072 3073 // Calculate checksum in another thread. 3074 auto futureWatcher = new QFutureWatcher<QString>(this); 3075 connect(futureWatcher, &QFutureWatcher<QString>::finished, this, [=]() { 3076 const QString checksum = futureWatcher->result(); 3077 futureWatcher->deleteLater(); 3078 3079 label->setText(checksum); 3080 cacheChecksum(checksum, algorithm); 3081 3082 copyButton->show(); 3083 }); 3084 3085 auto future = QtConcurrent::run(&KChecksumsPlugin::computeChecksum, algorithm, properties->item().localPath()); 3086 futureWatcher->setFuture(future); 3087 } 3088 3089 QString KChecksumsPlugin::cachedChecksum(QCryptographicHash::Algorithm algorithm) const 3090 { 3091 switch (algorithm) { 3092 case QCryptographicHash::Md5: 3093 return d->m_md5; 3094 case QCryptographicHash::Sha1: 3095 return d->m_sha1; 3096 case QCryptographicHash::Sha256: 3097 return d->m_sha256; 3098 case QCryptographicHash::Sha512: 3099 return d->m_sha512; 3100 default: 3101 break; 3102 } 3103 3104 return QString(); 3105 } 3106 3107 void KChecksumsPlugin::cacheChecksum(const QString &checksum, QCryptographicHash::Algorithm algorithm) 3108 { 3109 switch (algorithm) { 3110 case QCryptographicHash::Md5: 3111 d->m_md5 = checksum; 3112 break; 3113 case QCryptographicHash::Sha1: 3114 d->m_sha1 = checksum; 3115 break; 3116 case QCryptographicHash::Sha256: 3117 d->m_sha256 = checksum; 3118 break; 3119 case QCryptographicHash::Sha512: 3120 d->m_sha512 = checksum; 3121 break; 3122 default: 3123 return; 3124 } 3125 } 3126 3127 class KUrlPropsPlugin::KUrlPropsPluginPrivate 3128 { 3129 public: 3130 KUrlPropsPluginPrivate() 3131 { 3132 } 3133 ~KUrlPropsPluginPrivate() 3134 { 3135 } 3136 3137 QFrame *m_frame; 3138 KUrlRequester *URLEdit; 3139 QString URLStr; 3140 bool fileNameReadOnly = false; 3141 }; 3142 3143 KUrlPropsPlugin::KUrlPropsPlugin(KPropertiesDialog *_props) 3144 : KPropertiesDialogPlugin(_props) 3145 , d(new KUrlPropsPluginPrivate) 3146 { 3147 d->m_frame = new QFrame(); 3148 properties->addPage(d->m_frame, i18n("U&RL")); 3149 QVBoxLayout *layout = new QVBoxLayout(d->m_frame); 3150 layout->setContentsMargins(0, 0, 0, 0); 3151 3152 QLabel *l; 3153 l = new QLabel(d->m_frame); 3154 l->setObjectName(QStringLiteral("Label_1")); 3155 l->setText(i18n("URL:")); 3156 layout->addWidget(l, Qt::AlignRight); 3157 3158 d->URLEdit = new KUrlRequester(d->m_frame); 3159 layout->addWidget(d->URLEdit); 3160 3161 KIO::StatJob *job = KIO::mostLocalUrl(properties->url()); 3162 KJobWidgets::setWindow(job, properties); 3163 job->exec(); 3164 QUrl url = job->mostLocalUrl(); 3165 3166 if (url.isLocalFile()) { 3167 QString path = url.toLocalFile(); 3168 3169 QFile f(path); 3170 if (!f.open(QIODevice::ReadOnly)) { 3171 return; 3172 } 3173 3174 KDesktopFile config(path); 3175 const KConfigGroup dg = config.desktopGroup(); 3176 d->URLStr = dg.readPathEntry("URL", QString()); 3177 3178 if (!d->URLStr.isEmpty()) { 3179 d->URLEdit->setUrl(QUrl(d->URLStr)); 3180 } 3181 } 3182 3183 connect(d->URLEdit, &KUrlRequester::textChanged, this, &KPropertiesDialogPlugin::changed); 3184 3185 layout->addStretch(1); 3186 } 3187 3188 KUrlPropsPlugin::~KUrlPropsPlugin() = default; 3189 3190 void KUrlPropsPlugin::setFileNameReadOnly(bool ro) 3191 { 3192 d->fileNameReadOnly = ro; 3193 } 3194 3195 bool KUrlPropsPlugin::supports(const KFileItemList &_items) 3196 { 3197 if (_items.count() != 1) { 3198 return false; 3199 } 3200 const KFileItem &item = _items.first(); 3201 // check if desktop file 3202 if (!item.isDesktopFile()) { 3203 return false; 3204 } 3205 3206 // open file and check type 3207 const auto [url, isLocal] = item.isMostLocalUrl(); 3208 if (!isLocal) { 3209 return false; 3210 } 3211 3212 KDesktopFile config(url.toLocalFile()); 3213 return config.hasLinkType(); 3214 } 3215 3216 void KUrlPropsPlugin::applyChanges() 3217 { 3218 KIO::StatJob *job = KIO::mostLocalUrl(properties->url()); 3219 KJobWidgets::setWindow(job, properties); 3220 job->exec(); 3221 const QUrl url = job->mostLocalUrl(); 3222 3223 if (!url.isLocalFile()) { 3224 KMessageBox::error(nullptr, i18n("Could not save properties. Only entries on local file systems are supported.")); 3225 properties->abortApplying(); 3226 return; 3227 } 3228 3229 QString path = url.toLocalFile(); 3230 QFile f(path); 3231 if (!f.open(QIODevice::ReadWrite)) { 3232 KMessageBox::error(nullptr, couldNotSaveMsg(path)); 3233 properties->abortApplying(); 3234 return; 3235 } 3236 3237 KDesktopFile config(path); 3238 KConfigGroup dg = config.desktopGroup(); 3239 dg.writeEntry("Type", QStringLiteral("Link")); 3240 dg.writePathEntry("URL", d->URLEdit->url().toString()); 3241 // Users can't create a Link .desktop file with a Name field, 3242 // but distributions can. Update the Name field in that case, 3243 // if the file name could have been changed. 3244 if (!d->fileNameReadOnly && dg.hasKey("Name")) { 3245 const QString nameStr = nameFromFileName(properties->url().fileName()); 3246 dg.writeEntry("Name", nameStr); 3247 dg.writeEntry("Name", nameStr, KConfigBase::Persistent | KConfigBase::Localized); 3248 } 3249 3250 setDirty(false); 3251 } 3252 3253 /* ---------------------------------------------------- 3254 * 3255 * KDesktopPropsPlugin 3256 * 3257 * -------------------------------------------------- */ 3258 3259 class KDesktopPropsPlugin::KDesktopPropsPluginPrivate 3260 { 3261 public: 3262 KDesktopPropsPluginPrivate() 3263 : w(new Ui_KPropertiesDesktopBase) 3264 , m_frame(new QFrame()) 3265 { 3266 } 3267 ~KDesktopPropsPluginPrivate() 3268 { 3269 delete w; 3270 } 3271 QString command() const; 3272 Ui_KPropertiesDesktopBase *w; 3273 QWidget *m_frame = nullptr; 3274 std::unique_ptr<Ui_KPropertiesDesktopAdvBase> m_uiAdvanced; 3275 3276 QString m_origCommandStr; 3277 QString m_terminalOptionStr; 3278 QString m_suidUserStr; 3279 QString m_origDesktopFile; 3280 bool m_terminalBool; 3281 bool m_suidBool; 3282 // Corresponds to "PrefersNonDefaultGPU=" (added in destop-entry-spec 1.4) 3283 // or to "X-KDE-RunOnDiscreteGpu=" for backwards compatibility 3284 bool m_runOnDiscreteGpuBool; 3285 bool m_startupBool; 3286 }; 3287 3288 KDesktopPropsPlugin::KDesktopPropsPlugin(KPropertiesDialog *_props) 3289 : KPropertiesDialogPlugin(_props) 3290 , d(new KDesktopPropsPluginPrivate) 3291 { 3292 QMimeDatabase db; 3293 3294 d->w->setupUi(d->m_frame); 3295 3296 properties->addPage(d->m_frame, i18n("&Application")); 3297 3298 bool bKDesktopMode = properties->url().scheme() == QLatin1String("desktop") || properties->currentDir().scheme() == QLatin1String("desktop"); 3299 3300 d->w->pathEdit->setMode(KFile::Directory | KFile::LocalOnly); 3301 d->w->pathEdit->lineEdit()->setAcceptDrops(false); 3302 3303 connect(d->w->nameEdit, &QLineEdit::textChanged, this, &KPropertiesDialogPlugin::changed); 3304 connect(d->w->genNameEdit, &QLineEdit::textChanged, this, &KPropertiesDialogPlugin::changed); 3305 connect(d->w->commentEdit, &QLineEdit::textChanged, this, &KPropertiesDialogPlugin::changed); 3306 connect(d->w->envarsEdit, &QLineEdit::textChanged, this, &KPropertiesDialogPlugin::changed); 3307 connect(d->w->programEdit, &QLineEdit::textChanged, this, &KPropertiesDialogPlugin::changed); 3308 connect(d->w->argumentsEdit, &QLineEdit::textChanged, this, &KPropertiesDialogPlugin::changed); 3309 connect(d->w->pathEdit, &KUrlRequester::textChanged, this, &KPropertiesDialogPlugin::changed); 3310 3311 connect(d->w->browseButton, &QAbstractButton::clicked, this, &KDesktopPropsPlugin::slotBrowseExec); 3312 connect(d->w->addFiletypeButton, &QAbstractButton::clicked, this, &KDesktopPropsPlugin::slotAddFiletype); 3313 connect(d->w->delFiletypeButton, &QAbstractButton::clicked, this, &KDesktopPropsPlugin::slotDelFiletype); 3314 connect(d->w->advancedButton, &QAbstractButton::clicked, this, &KDesktopPropsPlugin::slotAdvanced); 3315 3316 // now populate the page 3317 3318 KIO::StatJob *job = KIO::mostLocalUrl(_props->url()); 3319 KJobWidgets::setWindow(job, _props); 3320 job->exec(); 3321 QUrl url = job->mostLocalUrl(); 3322 3323 if (!url.isLocalFile()) { 3324 return; 3325 } 3326 3327 d->m_origDesktopFile = url.toLocalFile(); 3328 3329 QFile f(d->m_origDesktopFile); 3330 if (!f.open(QIODevice::ReadOnly)) { 3331 return; 3332 } 3333 3334 KDesktopFile _config(d->m_origDesktopFile); 3335 KConfigGroup config = _config.desktopGroup(); 3336 QString nameStr = _config.readName(); 3337 QString genNameStr = _config.readGenericName(); 3338 QString commentStr = _config.readComment(); 3339 QString commandStr = config.readEntry("Exec", QString()); 3340 3341 d->m_origCommandStr = commandStr; 3342 QString pathStr = config.readEntry("Path", QString()); // not readPathEntry, see kservice.cpp 3343 d->m_terminalBool = config.readEntry("Terminal", false); 3344 d->m_terminalOptionStr = config.readEntry("TerminalOptions"); 3345 d->m_suidBool = config.readEntry("X-KDE-SubstituteUID", false); 3346 d->m_suidUserStr = config.readEntry("X-KDE-Username"); 3347 if (KIO::hasDiscreteGpu()) { 3348 if (config.hasKey("PrefersNonDefaultGPU")) { 3349 d->m_runOnDiscreteGpuBool = config.readEntry("PrefersNonDefaultGPU", false); 3350 } else { 3351 d->m_runOnDiscreteGpuBool = config.readEntry("X-KDE-RunOnDiscreteGpu", false); 3352 } 3353 } 3354 if (config.hasKey("StartupNotify")) { 3355 d->m_startupBool = config.readEntry("StartupNotify", true); 3356 } else { 3357 d->m_startupBool = config.readEntry("X-KDE-StartupNotify", true); 3358 } 3359 3360 const QStringList mimeTypes = config.readXdgListEntry("MimeType"); 3361 3362 if (nameStr.isEmpty() || bKDesktopMode) { 3363 // We'll use the file name if no name is specified 3364 // because we _need_ a Name for a valid file. 3365 // But let's do it in apply, not here, so that we pick up the right name. 3366 setDirty(); 3367 } 3368 d->w->nameEdit->setText(nameStr); 3369 d->w->genNameEdit->setText(genNameStr); 3370 d->w->commentEdit->setText(commentStr); 3371 3372 QStringList execLine = KShell::splitArgs(commandStr); 3373 QStringList enVars = {}; 3374 3375 if (!execLine.isEmpty()) { 3376 // check for apps that use the env executable 3377 // to set the environment 3378 if (execLine[0] == QLatin1String("env")) { 3379 execLine.pop_front(); 3380 } 3381 for (auto env : execLine) { 3382 if (!env.contains(QLatin1String("="))) { 3383 break; 3384 } 3385 enVars += env; 3386 execLine.pop_front(); 3387 } 3388 3389 d->w->programEdit->setText(execLine.takeFirst()); 3390 } else { 3391 d->w->programEdit->clear(); 3392 } 3393 d->w->argumentsEdit->setText(KShell::joinArgs(execLine)); 3394 d->w->envarsEdit->setText(KShell::joinArgs(enVars)); 3395 3396 d->w->pathEdit->lineEdit()->setText(pathStr); 3397 3398 // was: d->w->filetypeList->setFullWidth(true); 3399 // d->w->filetypeList->header()->setStretchEnabled(true, d->w->filetypeList->columns()-1); 3400 3401 for (QStringList::ConstIterator it = mimeTypes.begin(); it != mimeTypes.end();) { 3402 QMimeType p = db.mimeTypeForName(*it); 3403 ++it; 3404 QString preference; 3405 if (it != mimeTypes.end()) { 3406 bool numeric; 3407 (*it).toInt(&numeric); 3408 if (numeric) { 3409 preference = *it; 3410 ++it; 3411 } 3412 } 3413 if (p.isValid()) { 3414 QTreeWidgetItem *item = new QTreeWidgetItem(); 3415 item->setText(0, p.name()); 3416 item->setText(1, p.comment()); 3417 item->setText(2, preference); 3418 d->w->filetypeList->addTopLevelItem(item); 3419 } 3420 } 3421 d->w->filetypeList->resizeColumnToContents(0); 3422 } 3423 3424 KDesktopPropsPlugin::~KDesktopPropsPlugin() = default; 3425 3426 void KDesktopPropsPlugin::slotAddFiletype() 3427 { 3428 QMimeDatabase db; 3429 KMimeTypeChooserDialog dlg(i18n("Add File Type for %1", properties->url().fileName()), 3430 i18n("Select one or more file types to add:"), 3431 QStringList(), // no preselected mimetypes 3432 QString(), 3433 QStringList(), 3434 KMimeTypeChooser::Comments | KMimeTypeChooser::Patterns, 3435 d->m_frame); 3436 3437 if (dlg.exec() == QDialog::Accepted) { 3438 const QStringList list = dlg.chooser()->mimeTypes(); 3439 for (const QString &mimetype : list) { 3440 QMimeType p = db.mimeTypeForName(mimetype); 3441 if (!p.isValid()) { 3442 continue; 3443 } 3444 3445 bool found = false; 3446 int count = d->w->filetypeList->topLevelItemCount(); 3447 for (int i = 0; !found && i < count; ++i) { 3448 if (d->w->filetypeList->topLevelItem(i)->text(0) == mimetype) { 3449 found = true; 3450 } 3451 } 3452 if (!found) { 3453 QTreeWidgetItem *item = new QTreeWidgetItem(); 3454 item->setText(0, p.name()); 3455 item->setText(1, p.comment()); 3456 d->w->filetypeList->addTopLevelItem(item); 3457 } 3458 d->w->filetypeList->resizeColumnToContents(0); 3459 } 3460 } 3461 Q_EMIT changed(); 3462 } 3463 3464 void KDesktopPropsPlugin::slotDelFiletype() 3465 { 3466 QTreeWidgetItem *cur = d->w->filetypeList->currentItem(); 3467 if (cur) { 3468 delete cur; 3469 Q_EMIT changed(); 3470 } 3471 } 3472 3473 void KDesktopPropsPlugin::checkCommandChanged() 3474 { 3475 if (KIO::DesktopExecParser::executableName(d->command()) != KIO::DesktopExecParser::executableName(d->m_origCommandStr)) { 3476 d->m_origCommandStr = d->command(); 3477 } 3478 } 3479 3480 void KDesktopPropsPlugin::applyChanges() 3481 { 3482 // qDebug(); 3483 KIO::StatJob *job = KIO::mostLocalUrl(properties->url()); 3484 KJobWidgets::setWindow(job, properties); 3485 job->exec(); 3486 const QUrl url = job->mostLocalUrl(); 3487 3488 if (!url.isLocalFile()) { 3489 KMessageBox::error(nullptr, i18n("Could not save properties. Only entries on local file systems are supported.")); 3490 properties->abortApplying(); 3491 return; 3492 } 3493 3494 const QString path(url.toLocalFile()); 3495 3496 // make sure the directory exists 3497 QDir().mkpath(QFileInfo(path).absolutePath()); 3498 QFile f(path); 3499 if (!f.open(QIODevice::ReadWrite)) { 3500 KMessageBox::error(nullptr, couldNotSaveMsg(path)); 3501 properties->abortApplying(); 3502 return; 3503 } 3504 3505 // If the command is changed we reset certain settings that are strongly 3506 // coupled to the command. 3507 checkCommandChanged(); 3508 3509 KDesktopFile origConfig(d->m_origDesktopFile); 3510 std::unique_ptr<KDesktopFile> _config(origConfig.copyTo(path)); 3511 KConfigGroup config = _config->desktopGroup(); 3512 config.writeEntry("Type", QStringLiteral("Application")); 3513 config.writeEntry("Comment", d->w->commentEdit->text()); 3514 config.writeEntry("Comment", d->w->commentEdit->text(), KConfigGroup::Persistent | KConfigGroup::Localized); // for compat 3515 config.writeEntry("GenericName", d->w->genNameEdit->text()); 3516 config.writeEntry("GenericName", d->w->genNameEdit->text(), KConfigGroup::Persistent | KConfigGroup::Localized); // for compat 3517 config.writeEntry("Exec", d->command()); 3518 config.writeEntry("Path", d->w->pathEdit->lineEdit()->text()); // not writePathEntry, see kservice.cpp 3519 3520 // Write mimeTypes 3521 QStringList mimeTypes; 3522 int count = d->w->filetypeList->topLevelItemCount(); 3523 for (int i = 0; i < count; ++i) { 3524 QTreeWidgetItem *item = d->w->filetypeList->topLevelItem(i); 3525 QString preference = item->text(2); 3526 mimeTypes.append(item->text(0)); 3527 if (!preference.isEmpty()) { 3528 mimeTypes.append(preference); 3529 } 3530 } 3531 3532 // qDebug() << mimeTypes; 3533 config.writeXdgListEntry("MimeType", mimeTypes); 3534 3535 if (!d->w->nameEdit->isHidden()) { 3536 QString nameStr = d->w->nameEdit->text(); 3537 config.writeEntry("Name", nameStr); 3538 config.writeEntry("Name", nameStr, KConfigGroup::Persistent | KConfigGroup::Localized); 3539 } 3540 3541 config.writeEntry("Terminal", d->m_terminalBool); 3542 config.writeEntry("TerminalOptions", d->m_terminalOptionStr); 3543 config.writeEntry("X-KDE-SubstituteUID", d->m_suidBool); 3544 config.writeEntry("X-KDE-Username", d->m_suidUserStr); 3545 if (KIO::hasDiscreteGpu()) { 3546 if (config.hasKey("PrefersNonDefaultGPU")) { 3547 config.writeEntry("PrefersNonDefaultGPU", d->m_runOnDiscreteGpuBool); 3548 } else { 3549 config.writeEntry("X-KDE-RunOnDiscreteGpu", d->m_runOnDiscreteGpuBool); 3550 } 3551 } 3552 config.writeEntry("StartupNotify", d->m_startupBool); 3553 config.sync(); 3554 3555 // KSycoca update needed? 3556 bool updateNeeded = !relativeAppsLocation(path).isEmpty(); 3557 if (updateNeeded) { 3558 KBuildSycocaProgressDialog::rebuildKSycoca(d->m_frame); 3559 } 3560 3561 setDirty(false); 3562 } 3563 3564 void KDesktopPropsPlugin::slotBrowseExec() 3565 { 3566 QUrl f = QFileDialog::getOpenFileUrl(d->m_frame); 3567 if (f.isEmpty()) { 3568 return; 3569 } 3570 3571 if (!f.isLocalFile()) { 3572 KMessageBox::information(d->m_frame, i18n("Only executables on local file systems are supported.")); 3573 return; 3574 } 3575 3576 const QString path = f.toLocalFile(); 3577 d->w->programEdit->setText(path); 3578 } 3579 3580 void KDesktopPropsPlugin::slotAdvanced() 3581 { 3582 auto *dlg = new QDialog(d->m_frame); 3583 dlg->setObjectName(QStringLiteral("KPropertiesDesktopAdv")); 3584 dlg->setModal(true); 3585 dlg->setAttribute(Qt::WA_DeleteOnClose); 3586 dlg->setWindowTitle(i18n("Advanced Options for %1", properties->url().fileName())); 3587 3588 d->m_uiAdvanced.reset(new Ui_KPropertiesDesktopAdvBase); 3589 QWidget *mainWidget = new QWidget(dlg); 3590 d->m_uiAdvanced->setupUi(mainWidget); 3591 3592 QDialogButtonBox *buttonBox = new QDialogButtonBox(dlg); 3593 buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); 3594 connect(buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept); 3595 connect(buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject); 3596 3597 QVBoxLayout *layout = new QVBoxLayout(dlg); 3598 layout->addWidget(mainWidget); 3599 layout->addWidget(buttonBox); 3600 3601 // If the command is changed we reset certain settings that are strongly 3602 // coupled to the command. 3603 checkCommandChanged(); 3604 3605 // check to see if we use konsole if not do not add the nocloseonexit 3606 // because we don't know how to do this on other terminal applications 3607 KConfigGroup confGroup(KSharedConfig::openConfig(), QStringLiteral("General")); 3608 QString preferredTerminal = confGroup.readPathEntry("TerminalApplication", QStringLiteral("konsole")); 3609 3610 bool terminalCloseBool = false; 3611 3612 if (preferredTerminal == QLatin1String("konsole")) { 3613 terminalCloseBool = d->m_terminalOptionStr.contains(QLatin1String("--noclose")); 3614 d->m_uiAdvanced->terminalCloseCheck->setChecked(terminalCloseBool); 3615 d->m_terminalOptionStr.remove(QStringLiteral("--noclose")); 3616 } else { 3617 d->m_uiAdvanced->terminalCloseCheck->hide(); 3618 } 3619 3620 d->m_uiAdvanced->terminalCheck->setChecked(d->m_terminalBool); 3621 d->m_uiAdvanced->terminalEdit->setText(d->m_terminalOptionStr); 3622 d->m_uiAdvanced->terminalCloseCheck->setEnabled(d->m_terminalBool); 3623 d->m_uiAdvanced->terminalEdit->setEnabled(d->m_terminalBool); 3624 d->m_uiAdvanced->terminalEditLabel->setEnabled(d->m_terminalBool); 3625 3626 d->m_uiAdvanced->suidCheck->setChecked(d->m_suidBool); 3627 d->m_uiAdvanced->suidEdit->setText(d->m_suidUserStr); 3628 d->m_uiAdvanced->suidEdit->setEnabled(d->m_suidBool); 3629 d->m_uiAdvanced->suidEditLabel->setEnabled(d->m_suidBool); 3630 3631 if (KIO::hasDiscreteGpu()) { 3632 d->m_uiAdvanced->discreteGpuCheck->setChecked(d->m_runOnDiscreteGpuBool); 3633 } else { 3634 d->m_uiAdvanced->discreteGpuGroupBox->hide(); 3635 } 3636 3637 d->m_uiAdvanced->startupInfoCheck->setChecked(d->m_startupBool); 3638 3639 // Provide username completion up to 1000 users. 3640 const int maxEntries = 1000; 3641 QStringList userNames = KUser::allUserNames(maxEntries); 3642 if (userNames.size() < maxEntries) { 3643 KCompletion *kcom = new KCompletion; 3644 kcom->setOrder(KCompletion::Sorted); 3645 d->m_uiAdvanced->suidEdit->setCompletionObject(kcom, true); 3646 d->m_uiAdvanced->suidEdit->setAutoDeleteCompletionObject(true); 3647 d->m_uiAdvanced->suidEdit->setCompletionMode(KCompletion::CompletionAuto); 3648 kcom->setItems(userNames); 3649 } 3650 3651 connect(d->m_uiAdvanced->terminalEdit, &QLineEdit::textChanged, this, &KPropertiesDialogPlugin::changed); 3652 connect(d->m_uiAdvanced->terminalCloseCheck, &QAbstractButton::toggled, this, &KPropertiesDialogPlugin::changed); 3653 connect(d->m_uiAdvanced->terminalCheck, &QAbstractButton::toggled, this, &KPropertiesDialogPlugin::changed); 3654 connect(d->m_uiAdvanced->suidCheck, &QAbstractButton::toggled, this, &KPropertiesDialogPlugin::changed); 3655 connect(d->m_uiAdvanced->suidEdit, &QLineEdit::textChanged, this, &KPropertiesDialogPlugin::changed); 3656 connect(d->m_uiAdvanced->discreteGpuCheck, &QAbstractButton::toggled, this, &KPropertiesDialogPlugin::changed); 3657 connect(d->m_uiAdvanced->startupInfoCheck, &QAbstractButton::toggled, this, &KPropertiesDialogPlugin::changed); 3658 3659 QObject::connect(dlg, &QDialog::accepted, this, [this]() { 3660 d->m_terminalOptionStr = d->m_uiAdvanced->terminalEdit->text().trimmed(); 3661 d->m_terminalBool = d->m_uiAdvanced->terminalCheck->isChecked(); 3662 d->m_suidBool = d->m_uiAdvanced->suidCheck->isChecked(); 3663 d->m_suidUserStr = d->m_uiAdvanced->suidEdit->text().trimmed(); 3664 if (KIO::hasDiscreteGpu()) { 3665 d->m_runOnDiscreteGpuBool = d->m_uiAdvanced->discreteGpuCheck->isChecked(); 3666 } 3667 d->m_startupBool = d->m_uiAdvanced->startupInfoCheck->isChecked(); 3668 3669 if (d->m_uiAdvanced->terminalCloseCheck->isChecked()) { 3670 d->m_terminalOptionStr.append(QLatin1String(" --noclose")); 3671 } 3672 }); 3673 3674 dlg->show(); 3675 } 3676 3677 bool KDesktopPropsPlugin::supports(const KFileItemList &_items) 3678 { 3679 if (_items.count() != 1) { 3680 return false; 3681 } 3682 3683 const KFileItem &item = _items.first(); 3684 3685 // check if desktop file 3686 if (!item.isDesktopFile()) { 3687 return false; 3688 } 3689 3690 // open file and check type 3691 const auto [url, isLocal] = item.isMostLocalUrl(); 3692 if (!isLocal) { 3693 return false; 3694 } 3695 3696 KDesktopFile config(url.toLocalFile()); 3697 return config.hasApplicationType() && KAuthorized::authorize(KAuthorized::RUN_DESKTOP_FILES) && KAuthorized::authorize(KAuthorized::SHELL_ACCESS); 3698 } 3699 3700 QString KDesktopPropsPlugin::KDesktopPropsPluginPrivate::command() const 3701 { 3702 QStringList execSplit = KShell::splitArgs(w->envarsEdit->text()) + QStringList(w->programEdit->text()) + KShell::splitArgs(w->argumentsEdit->text()); 3703 3704 if (KShell::splitArgs(w->envarsEdit->text()).length()) { 3705 execSplit.push_front(QLatin1String("env")); 3706 } 3707 3708 return KShell::joinArgs(execSplit); 3709 } 3710 3711 #include "moc_kpropertiesdialog.cpp" 3712 #include "moc_kpropertiesdialog_p.cpp"