File indexing completed on 2025-03-23 12:45:07
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org> 0004 SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org> 0005 SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org> 0006 SPDX-FileCopyrightText: 2013 Dawit Alemayehu <adawit@kde.org> 0007 SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org> 0008 0009 SPDX-License-Identifier: LGPL-2.0-or-later 0010 */ 0011 0012 #include "jobuidelegate.h" 0013 #include "kio_widgets_debug.h" 0014 #include "kiogui_export.h" 0015 #include "widgetsaskuseractionhandler.h" 0016 #include "widgetsopenorexecutefilehandler.h" 0017 #include "widgetsopenwithhandler.h" 0018 #include "widgetsuntrustedprogramhandler.h" 0019 #include <kio/jobuidelegatefactory.h> 0020 0021 #include <KConfigGroup> 0022 #include <KJob> 0023 #include <KJobWidgets> 0024 #include <KLocalizedString> 0025 #include <KMessageBox> 0026 #include <KSharedConfig> 0027 #include <clipboardupdater_p.h> 0028 #include <ksslinfodialog.h> 0029 0030 #ifndef KIO_ANDROID_STUB 0031 #include <QDBusInterface> 0032 #endif 0033 #include <QGuiApplication> 0034 #include <QIcon> 0035 #include <QPointer> 0036 #include <QRegularExpression> 0037 #include <QUrl> 0038 #include <QWidget> 0039 0040 class KIO::JobUiDelegatePrivate 0041 { 0042 public: 0043 JobUiDelegatePrivate(KIO::JobUiDelegate *qq, const QList<QObject *> &ifaces) 0044 { 0045 for (auto iface : ifaces) { 0046 iface->setParent(qq); 0047 if (auto obj = qobject_cast<UntrustedProgramHandlerInterface *>(iface)) { 0048 m_untrustedProgramHandler = obj; 0049 } else if (auto obj = qobject_cast<OpenWithHandlerInterface *>(iface)) { 0050 m_openWithHandler = obj; 0051 } else if (auto obj = qobject_cast<OpenOrExecuteFileInterface *>(iface)) { 0052 m_openOrExecuteFileHandler = obj; 0053 } else if (auto obj = qobject_cast<AskUserActionInterface *>(iface)) { 0054 m_askUserActionHandler = obj; 0055 } 0056 } 0057 0058 if (!m_untrustedProgramHandler) { 0059 m_untrustedProgramHandler = new WidgetsUntrustedProgramHandler(qq); 0060 } 0061 if (!m_openWithHandler) { 0062 m_openWithHandler = new WidgetsOpenWithHandler(qq); 0063 } 0064 if (!m_openOrExecuteFileHandler) { 0065 m_openOrExecuteFileHandler = new WidgetsOpenOrExecuteFileHandler(qq); 0066 } 0067 if (!m_askUserActionHandler) { 0068 m_askUserActionHandler = new WidgetsAskUserActionHandler(qq); 0069 } 0070 } 0071 0072 UntrustedProgramHandlerInterface *m_untrustedProgramHandler = nullptr; 0073 OpenWithHandlerInterface *m_openWithHandler = nullptr; 0074 OpenOrExecuteFileInterface *m_openOrExecuteFileHandler = nullptr; 0075 AskUserActionInterface *m_askUserActionHandler = nullptr; 0076 }; 0077 0078 #if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 98) 0079 KIO::JobUiDelegate::JobUiDelegate() 0080 : JobUiDelegate(Version::V2) 0081 { 0082 } 0083 #endif 0084 0085 KIO::JobUiDelegate::~JobUiDelegate() = default; 0086 0087 /* 0088 Returns the top most window associated with widget. 0089 0090 Unlike QWidget::window(), this function does its best to find and return the 0091 main application window associated with the given widget. 0092 0093 If widget itself is a dialog or its parent is a dialog, and that dialog has a 0094 parent widget then this function will iterate through all those widgets to 0095 find the top most window, which most of the time is the main window of the 0096 application. By contrast, QWidget::window() would simply return the first 0097 file dialog it encountered since it is the "next ancestor widget that has (or 0098 could have) a window-system frame". 0099 */ 0100 static QWidget *topLevelWindow(QWidget *widget) 0101 { 0102 QWidget *w = widget; 0103 while (w && w->parentWidget()) { 0104 w = w->parentWidget(); 0105 } 0106 return (w ? w->window() : nullptr); 0107 } 0108 0109 class JobUiDelegateStatic : public QObject 0110 { 0111 Q_OBJECT 0112 public: 0113 void registerWindow(QWidget *wid) 0114 { 0115 if (!wid) { 0116 return; 0117 } 0118 0119 QWidget *window = topLevelWindow(wid); 0120 QObject *obj = static_cast<QObject *>(window); 0121 if (!m_windowList.contains(obj)) { 0122 // We must store the window Id because by the time 0123 // the destroyed signal is emitted we can no longer 0124 // access QWidget::winId() (already destructed) 0125 WId windowId = window->winId(); 0126 m_windowList.insert(obj, windowId); 0127 connect(window, &QObject::destroyed, this, &JobUiDelegateStatic::slotUnregisterWindow); 0128 #ifndef KIO_ANDROID_STUB 0129 QDBusInterface(QStringLiteral("org.kde.kded5"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded5")) 0130 .call(QDBus::NoBlock, QStringLiteral("registerWindowId"), qlonglong(windowId)); 0131 #endif 0132 } 0133 } 0134 public Q_SLOTS: 0135 void slotUnregisterWindow(QObject *obj) 0136 { 0137 if (!obj) { 0138 return; 0139 } 0140 0141 QMap<QObject *, WId>::Iterator it = m_windowList.find(obj); 0142 if (it == m_windowList.end()) { 0143 return; 0144 } 0145 WId windowId = it.value(); 0146 disconnect(it.key(), &QObject::destroyed, this, &JobUiDelegateStatic::slotUnregisterWindow); 0147 m_windowList.erase(it); 0148 #ifndef KIO_ANDROID_STUB 0149 QDBusInterface(QStringLiteral("org.kde.kded5"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded5")) 0150 .call(QDBus::NoBlock, QStringLiteral("unregisterWindowId"), qlonglong(windowId)); 0151 #endif 0152 } 0153 0154 private: 0155 QMap<QObject *, WId> m_windowList; 0156 }; 0157 0158 Q_GLOBAL_STATIC(JobUiDelegateStatic, s_static) 0159 0160 #if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 98) 0161 KIO::JobUiDelegate::JobUiDelegate(KJobUiDelegate::Flags flags, QWidget *window) 0162 : JobUiDelegate(Version::V2, flags, window) 0163 { 0164 setWindow(window); 0165 } 0166 #endif 0167 0168 void KIO::JobUiDelegate::setWindow(QWidget *window) 0169 { 0170 KDialogJobUiDelegate::setWindow(window); 0171 0172 if (auto obj = qobject_cast<WidgetsUntrustedProgramHandler *>(d->m_openWithHandler)) { 0173 obj->setWindow(window); 0174 } 0175 if (auto obj = qobject_cast<WidgetsOpenWithHandler *>(d->m_untrustedProgramHandler)) { 0176 obj->setWindow(window); 0177 } 0178 if (auto obj = qobject_cast<WidgetsOpenOrExecuteFileHandler *>(d->m_openOrExecuteFileHandler)) { 0179 obj->setWindow(window); 0180 } 0181 if (auto obj = qobject_cast<WidgetsAskUserActionHandler *>(d->m_askUserActionHandler)) { 0182 obj->setWindow(window); 0183 } 0184 0185 s_static()->registerWindow(window); 0186 } 0187 0188 void KIO::JobUiDelegate::unregisterWindow(QWidget *window) 0189 { 0190 s_static()->slotUnregisterWindow(window); 0191 } 0192 0193 KIO::RenameDialog_Result KIO::JobUiDelegate::askFileRename(KJob *job, 0194 const QString &title, 0195 const QUrl &src, 0196 const QUrl &dest, 0197 KIO::RenameDialog_Options options, 0198 QString &newDest, 0199 KIO::filesize_t sizeSrc, 0200 KIO::filesize_t sizeDest, 0201 const QDateTime &ctimeSrc, 0202 const QDateTime &ctimeDest, 0203 const QDateTime &mtimeSrc, 0204 const QDateTime &mtimeDest) 0205 { 0206 // qDebug() << "job=" << job; 0207 // We now do it in process, so that opening the rename dialog 0208 // doesn't start uiserver for nothing if progressId=0 (e.g. F2 in konq) 0209 KIO::RenameDialog dlg(KJobWidgets::window(job), title, src, dest, options, sizeSrc, sizeDest, ctimeSrc, ctimeDest, mtimeSrc, mtimeDest); 0210 dlg.setWindowModality(Qt::WindowModal); 0211 connect(job, &KJob::finished, &dlg, &QDialog::reject); // #192976 0212 KIO::RenameDialog_Result res = static_cast<RenameDialog_Result>(dlg.exec()); 0213 if (res == Result_AutoRename) { 0214 newDest = dlg.autoDestUrl().path(); 0215 } else { 0216 newDest = dlg.newDestUrl().path(); 0217 } 0218 return res; 0219 } 0220 0221 KIO::SkipDialog_Result KIO::JobUiDelegate::askSkip(KJob *job, KIO::SkipDialog_Options options, const QString &error_text) 0222 { 0223 KIO::SkipDialog dlg(KJobWidgets::window(job), options, error_text); 0224 dlg.setWindowModality(Qt::WindowModal); 0225 connect(job, &KJob::finished, &dlg, &QDialog::reject); // #192976 0226 return static_cast<KIO::SkipDialog_Result>(dlg.exec()); 0227 } 0228 0229 bool KIO::JobUiDelegate::askDeleteConfirmation(const QList<QUrl> &urls, DeletionType deletionType, ConfirmationType confirmationType) 0230 { 0231 QString keyName; 0232 bool ask = (confirmationType == ForceConfirmation); 0233 if (!ask) { 0234 KSharedConfigPtr kioConfig = KSharedConfig::openConfig(QStringLiteral("kiorc"), KConfig::NoGlobals); 0235 0236 // The default value for confirmations is true for delete and false 0237 // for trash. If you change this, please also update: 0238 // dolphin/src/settings/general/confirmationssettingspage.cpp 0239 bool defaultValue = true; 0240 0241 switch (deletionType) { 0242 case Delete: 0243 keyName = QStringLiteral("ConfirmDelete"); 0244 break; 0245 case Trash: 0246 keyName = QStringLiteral("ConfirmTrash"); 0247 defaultValue = false; 0248 break; 0249 case EmptyTrash: 0250 keyName = QStringLiteral("ConfirmEmptyTrash"); 0251 break; 0252 } 0253 0254 ask = kioConfig->group("Confirmations").readEntry(keyName, defaultValue); 0255 } 0256 if (ask) { 0257 QStringList prettyList; 0258 prettyList.reserve(urls.size()); 0259 for (const QUrl &url : urls) { 0260 if (url.scheme() == QLatin1String("trash")) { 0261 QString path = url.path(); 0262 // HACK (#98983): remove "0-foo". Note that it works better than 0263 // displaying KFileItem::name(), for files under a subdir. 0264 path.remove(QRegularExpression(QStringLiteral("^/[0-9]*-"))); 0265 prettyList.append(path); 0266 } else { 0267 prettyList.append(url.toDisplayString(QUrl::PreferLocalFile)); 0268 } 0269 } 0270 0271 int result; 0272 QWidget *widget = window(); 0273 const KMessageBox::Options options(KMessageBox::Notify | KMessageBox::WindowModal); 0274 switch (deletionType) { 0275 case Delete: 0276 if (prettyList.count() == 1) { 0277 result = KMessageBox::warningContinueCancel( 0278 widget, 0279 xi18nc("@info", 0280 "Do you really want to permanently delete this item?<nl/><filename>%1</filename><nl/><nl/><emphasis strong='true'>This action " 0281 "cannot be undone.</emphasis>", 0282 prettyList.first()), 0283 i18n("Delete Permanently"), 0284 KGuiItem(i18nc("@action:button", "Delete Permanently"), QStringLiteral("edit-delete")), 0285 KStandardGuiItem::cancel(), 0286 keyName, 0287 options); 0288 } else { 0289 result = KMessageBox::warningContinueCancelList( 0290 widget, 0291 xi18ncp( 0292 "@info", 0293 "Do you really want to permanently delete this item?<nl/><nl/><emphasis strong='true'>This action cannot be undone.</emphasis>", 0294 "Do you really want to permanently delete these %1 items?<nl/><nl/><emphasis strong='true'>This action cannot be undone.</emphasis>", 0295 prettyList.count()), 0296 prettyList, 0297 i18n("Delete Permanently"), 0298 KGuiItem(i18nc("@action:button", "Delete Permanently"), QStringLiteral("edit-delete")), 0299 KStandardGuiItem::cancel(), 0300 keyName, 0301 options); 0302 } 0303 break; 0304 case EmptyTrash: 0305 result = KMessageBox::warningContinueCancel( 0306 widget, 0307 xi18nc("@info", 0308 "Do you want to permanently delete all items from the Trash?<nl/><nl/><emphasis strong='true'>This action cannot be undone.</emphasis>"), 0309 i18n("Delete Permanently"), 0310 KGuiItem(i18nc("@action:button", "Empty Trash"), QIcon::fromTheme(QStringLiteral("user-trash"))), 0311 KStandardGuiItem::cancel(), 0312 keyName, 0313 options); 0314 break; 0315 case Trash: 0316 default: 0317 if (prettyList.count() == 1) { 0318 result = KMessageBox::warningContinueCancel( 0319 widget, 0320 xi18nc("@info", "Do you really want to move this item to the Trash?<nl/><filename>%1</filename>", prettyList.first()), 0321 i18n("Move to Trash"), 0322 KGuiItem(i18n("Move to Trash"), QStringLiteral("user-trash")), 0323 KStandardGuiItem::cancel(), 0324 keyName, 0325 options); 0326 } else { 0327 result = KMessageBox::warningContinueCancelList( 0328 widget, 0329 i18np("Do you really want to move this item to the Trash?", "Do you really want to move these %1 items to the Trash?", prettyList.count()), 0330 prettyList, 0331 i18n("Move to Trash"), 0332 KGuiItem(i18n("Move to Trash"), QStringLiteral("user-trash")), 0333 KStandardGuiItem::cancel(), 0334 keyName, 0335 options); 0336 } 0337 } 0338 if (!keyName.isEmpty()) { 0339 // Check kmessagebox setting... erase & copy to konquerorrc. 0340 KSharedConfig::Ptr config = KSharedConfig::openConfig(); 0341 KConfigGroup notificationGroup(config, "Notification Messages"); 0342 if (!notificationGroup.readEntry(keyName, true)) { 0343 notificationGroup.writeEntry(keyName, true); 0344 notificationGroup.sync(); 0345 0346 KSharedConfigPtr kioConfig = KSharedConfig::openConfig(QStringLiteral("kiorc"), KConfig::NoGlobals); 0347 kioConfig->group("Confirmations").writeEntry(keyName, false); 0348 } 0349 } 0350 return (result == KMessageBox::Continue); 0351 } 0352 return true; 0353 } 0354 0355 int KIO::JobUiDelegate::requestMessageBox(KIO::JobUiDelegate::MessageBoxType type, 0356 const QString &text, 0357 const QString &title, 0358 const QString &primaryActionText, 0359 const QString &secondaryActionText, 0360 const QString &primaryActionIconName, 0361 const QString &secondaryActionIconName, 0362 const QString &dontAskAgainName, 0363 const KIO::MetaData &metaData) 0364 { 0365 int result = -1; 0366 0367 // qDebug() << type << text << "title=" << title; 0368 0369 KConfig config(QStringLiteral("kioslaverc")); 0370 KMessageBox::setDontShowAgainConfig(&config); 0371 0372 #if KWIDGETSADDONS_BUILD_DEPRECATED_SINCE(5, 100) 0373 QT_WARNING_PUSH 0374 QT_WARNING_DISABLE_DEPRECATED 0375 KGuiItem primaryActionTextGui = KStandardGuiItem::yes(); 0376 KGuiItem secondaryActionTextGui = KStandardGuiItem::no(); 0377 QT_WARNING_POP 0378 0379 if (!primaryActionText.isEmpty()) { 0380 primaryActionTextGui.setText(primaryActionText); 0381 } 0382 if (!primaryActionIconName.isNull()) { 0383 primaryActionTextGui.setIconName(primaryActionIconName); 0384 } 0385 0386 if (!secondaryActionText.isEmpty()) { 0387 secondaryActionTextGui.setText(secondaryActionText); 0388 } 0389 if (!secondaryActionIconName.isNull()) { 0390 secondaryActionTextGui.setIconName(secondaryActionIconName); 0391 } 0392 #else 0393 KGuiItem primaryActionTextGui(primaryActionText, primaryActionIconName); 0394 KGuiItem secondaryActionTextGui(secondaryActionText, secondaryActionIconName); 0395 #endif 0396 0397 KMessageBox::Options options(KMessageBox::Notify | KMessageBox::WindowModal); 0398 0399 switch (type) { 0400 case QuestionTwoActions: 0401 result = KMessageBox::questionTwoActions(window(), text, title, primaryActionTextGui, secondaryActionTextGui, dontAskAgainName, options); 0402 break; 0403 case WarningTwoActions: 0404 result = KMessageBox::warningTwoActions(window(), 0405 text, 0406 title, 0407 primaryActionTextGui, 0408 secondaryActionTextGui, 0409 dontAskAgainName, 0410 options | KMessageBox::Dangerous); 0411 break; 0412 case WarningTwoActionsCancel: 0413 result = KMessageBox::warningTwoActionsCancel(window(), 0414 text, 0415 title, 0416 primaryActionTextGui, 0417 secondaryActionTextGui, 0418 KStandardGuiItem::cancel(), 0419 dontAskAgainName, 0420 options); 0421 break; 0422 case WarningContinueCancel: 0423 result = KMessageBox::warningContinueCancel(window(), text, title, primaryActionTextGui, KStandardGuiItem::cancel(), dontAskAgainName, options); 0424 break; 0425 case Information: 0426 KMessageBox::information(window(), text, title, dontAskAgainName, options); 0427 result = 1; // whatever 0428 break; 0429 case SSLMessageBox: { 0430 QPointer<KSslInfoDialog> kid(new KSslInfoDialog(window())); 0431 // ### this is boilerplate code and appears in khtml_part.cpp almost unchanged! 0432 const QStringList sl = metaData.value(QStringLiteral("ssl_peer_chain")).split(QLatin1Char('\x01'), Qt::SkipEmptyParts); 0433 QList<QSslCertificate> certChain; 0434 bool decodedOk = true; 0435 for (const QString &s : sl) { 0436 certChain.append(QSslCertificate(s.toLatin1())); // or is it toLocal8Bit or whatever? 0437 if (certChain.last().isNull()) { 0438 decodedOk = false; 0439 break; 0440 } 0441 } 0442 0443 if (decodedOk) { 0444 result = 1; // whatever 0445 kid->setSslInfo(certChain, 0446 metaData.value(QStringLiteral("ssl_peer_ip")), 0447 text, // the URL 0448 metaData.value(QStringLiteral("ssl_protocol_version")), 0449 metaData.value(QStringLiteral("ssl_cipher")), 0450 metaData.value(QStringLiteral("ssl_cipher_used_bits")).toInt(), 0451 metaData.value(QStringLiteral("ssl_cipher_bits")).toInt(), 0452 KSslInfoDialog::certificateErrorsFromString(metaData.value(QStringLiteral("ssl_cert_errors")))); 0453 kid->exec(); 0454 } else { 0455 result = -1; 0456 KMessageBox::information(window(), i18n("The peer SSL certificate chain appears to be corrupt."), i18n("SSL"), QString(), options); 0457 } 0458 // KSslInfoDialog deletes itself (Qt::WA_DeleteOnClose). 0459 delete kid; 0460 break; 0461 } 0462 case WarningContinueCancelDetailed: { 0463 const QString details = metaData.value(QStringLiteral("privilege_conf_details")); 0464 result = KMessageBox::warningContinueCancelDetailed(window(), 0465 text, 0466 title, 0467 KStandardGuiItem::cont(), 0468 KStandardGuiItem::cancel(), 0469 dontAskAgainName, 0470 options | KMessageBox::Dangerous, 0471 details); 0472 break; 0473 } 0474 default: 0475 qCWarning(KIO_WIDGETS) << "Unknown type" << type; 0476 result = 0; 0477 break; 0478 } 0479 KMessageBox::setDontShowAgainConfig(nullptr); 0480 return result; 0481 } 0482 0483 KIO::ClipboardUpdater *KIO::JobUiDelegate::createClipboardUpdater(Job *job, ClipboardUpdaterMode mode) 0484 { 0485 if (qobject_cast<QGuiApplication *>(qApp)) { 0486 return new KIO::ClipboardUpdater(job, mode); 0487 } 0488 return nullptr; 0489 } 0490 0491 void KIO::JobUiDelegate::updateUrlInClipboard(const QUrl &src, const QUrl &dest) 0492 { 0493 if (qobject_cast<QGuiApplication *>(qApp)) { 0494 KIO::ClipboardUpdater::update(src, dest); 0495 } 0496 } 0497 0498 KIO::JobUiDelegate::JobUiDelegate(Version version, KJobUiDelegate::Flags /*flags*/, QWidget *window, const QList<QObject *> &ifaces) 0499 : d(new JobUiDelegatePrivate(this, ifaces)) 0500 { 0501 // TODO KF6: drop the version argument and replace the deprecated constructor 0502 // TODO KF6: change the API to accept QWindows rather than QWidgets (this also carries through to the Interfaces) 0503 if (window) { 0504 s_static()->registerWindow(window); 0505 setWindow(window); 0506 } 0507 0508 Q_UNUSED(version); // only serves to disambiguate constructors 0509 } 0510 0511 class KIOWidgetJobUiDelegateFactory : public KIO::JobUiDelegateFactoryV2 0512 { 0513 public: 0514 using KIO::JobUiDelegateFactoryV2::JobUiDelegateFactoryV2; 0515 0516 KJobUiDelegate *createDelegate() const override 0517 { 0518 return new KIO::JobUiDelegate(KIO::JobUiDelegate::Version::V2); 0519 } 0520 0521 KJobUiDelegate *createDelegate(KJobUiDelegate::Flags flags, QWidget *window) const override 0522 { 0523 return new KIO::JobUiDelegate(KIO::JobUiDelegate::Version::V2, flags, window); 0524 } 0525 0526 static void registerJobUiDelegate() 0527 { 0528 static KIOWidgetJobUiDelegateFactory factory; 0529 KIO::setDefaultJobUiDelegateFactoryV2(&factory); 0530 0531 static KIO::JobUiDelegate delegate(KIO::JobUiDelegate::Version::V2); 0532 KIO::setDefaultJobUiDelegateExtension(&delegate); 0533 } 0534 }; 0535 0536 // Simply linking to this library, creates a GUI job delegate and delegate extension for all KIO jobs 0537 static void registerJobUiDelegate() 0538 { 0539 // Inside the factory class so it is a friend of the delegate and can construct it. 0540 KIOWidgetJobUiDelegateFactory::registerJobUiDelegate(); 0541 } 0542 0543 Q_CONSTRUCTOR_FUNCTION(registerJobUiDelegate) 0544 0545 #include "jobuidelegate.moc" 0546 #include "moc_jobuidelegate.cpp"