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