File indexing completed on 2024-12-08 09:41:31
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2014 David Faure <faure@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0006 */ 0007 0008 #include "dropjob.h" 0009 0010 #include "job_p.h" 0011 #include "jobuidelegate.h" 0012 #include "jobuidelegateextension.h" 0013 #include "kio_widgets_debug.h" 0014 #include "pastejob.h" 0015 #include "pastejob_p.h" 0016 0017 #include <KConfigGroup> 0018 #include <KCoreDirLister> 0019 #include <KDesktopFile> 0020 #include <KFileItem> 0021 #include <KFileItemListProperties> 0022 #include <KIO/ApplicationLauncherJob> 0023 #include <KIO/CopyJob> 0024 #include <KIO/DndPopupMenuPlugin> 0025 #include <KIO/FileUndoManager> 0026 #include <KJobWidgets> 0027 #include <KLocalizedString> 0028 #include <KPluginFactory> 0029 #include <KPluginMetaData> 0030 #include <KProtocolManager> 0031 #include <KService> 0032 #include <KUrlMimeData> 0033 0034 #include <QDBusPendingCall> 0035 #include <QDropEvent> 0036 #include <QFileInfo> 0037 #include <QMenu> 0038 #include <QMimeData> 0039 #include <QProcess> 0040 #include <QTimer> 0041 0042 using namespace KIO; 0043 0044 Q_DECLARE_METATYPE(Qt::DropAction) 0045 0046 namespace KIO 0047 { 0048 class DropMenu; 0049 } 0050 0051 class KIO::DropMenu : public QMenu 0052 { 0053 Q_OBJECT 0054 public: 0055 explicit DropMenu(QWidget *parent = nullptr); 0056 ~DropMenu() override; 0057 0058 void addCancelAction(); 0059 void addExtraActions(const QList<QAction *> &appActions, const QList<QAction *> &pluginActions); 0060 0061 private: 0062 QList<QAction *> m_appActions; 0063 QList<QAction *> m_pluginActions; 0064 QAction *m_lastSeparator; 0065 QAction *m_extraActionsSeparator; 0066 QAction *m_cancelAction; 0067 }; 0068 0069 static const QString s_applicationSlashXDashKDEDashArkDashDnDExtractDashService = // 0070 QStringLiteral("application/x-kde-ark-dndextract-service"); 0071 static const QString s_applicationSlashXDashKDEDashArkDashDnDExtractDashPath = // 0072 QStringLiteral("application/x-kde-ark-dndextract-path"); 0073 0074 class KIO::DropJobPrivate : public KIO::JobPrivate 0075 { 0076 public: 0077 DropJobPrivate(const QDropEvent *dropEvent, const QUrl &destUrl, DropJobFlags dropjobFlags, JobFlags flags) 0078 : JobPrivate() 0079 , m_mimeData(dropEvent->mimeData()) // Extract everything from the dropevent, since it will be deleted before the job starts 0080 , m_urls(KUrlMimeData::urlsFromMimeData(m_mimeData, KUrlMimeData::PreferLocalUrls, &m_metaData)) 0081 , m_dropAction(dropEvent->dropAction()) 0082 , m_relativePos(dropEvent->pos()) 0083 , m_keyboardModifiers(dropEvent->keyboardModifiers()) 0084 , m_hasArkFormat(m_mimeData->hasFormat(s_applicationSlashXDashKDEDashArkDashDnDExtractDashService) 0085 && m_mimeData->hasFormat(s_applicationSlashXDashKDEDashArkDashDnDExtractDashPath)) 0086 , m_destUrl(destUrl) 0087 , m_destItem(KCoreDirLister::cachedItemForUrl(destUrl)) 0088 , m_flags(flags) 0089 , m_dropjobFlags(dropjobFlags) 0090 , m_triggered(false) 0091 { 0092 // Check for the drop of a bookmark -> we want a Link action 0093 if (m_mimeData->hasFormat(QStringLiteral("application/x-xbel"))) { 0094 m_keyboardModifiers |= Qt::KeyboardModifiers(Qt::ControlModifier | Qt::ShiftModifier); 0095 m_dropAction = Qt::LinkAction; 0096 } 0097 if (m_destItem.isNull() && m_destUrl.isLocalFile()) { 0098 m_destItem = KFileItem(m_destUrl); 0099 } 0100 0101 if (m_hasArkFormat) { 0102 m_remoteArkDBusClient = QString::fromUtf8(m_mimeData->data(s_applicationSlashXDashKDEDashArkDashDnDExtractDashService)); 0103 m_remoteArkDBusPath = QString::fromUtf8(m_mimeData->data(s_applicationSlashXDashKDEDashArkDashDnDExtractDashPath)); 0104 } 0105 0106 if (!(m_flags & KIO::NoPrivilegeExecution)) { 0107 m_privilegeExecutionEnabled = true; 0108 switch (m_dropAction) { 0109 case Qt::CopyAction: 0110 m_operationType = Copy; 0111 break; 0112 case Qt::MoveAction: 0113 m_operationType = Move; 0114 break; 0115 case Qt::LinkAction: 0116 m_operationType = Symlink; 0117 break; 0118 default: 0119 m_operationType = Other; 0120 break; 0121 } 0122 } 0123 } 0124 0125 bool destIsDirectory() const 0126 { 0127 if (!m_destItem.isNull()) { 0128 return m_destItem.isDir(); 0129 } 0130 // We support local dir, remote dir, local desktop file, local executable. 0131 // So for remote URLs, we just assume they point to a directory, the user will get an error from KIO::copy if not. 0132 return true; 0133 } 0134 void handleCopyToDirectory(); 0135 void slotDropActionDetermined(int error); 0136 void handleDropToDesktopFile(); 0137 void handleDropToExecutable(); 0138 void fillPopupMenu(KIO::DropMenu *popup); 0139 void addPluginActions(KIO::DropMenu *popup, const KFileItemListProperties &itemProps); 0140 void doCopyToDirectory(); 0141 0142 QPointer<const QMimeData> m_mimeData; 0143 const QList<QUrl> m_urls; 0144 QMap<QString, QString> m_metaData; 0145 Qt::DropAction m_dropAction; 0146 QPoint m_relativePos; 0147 Qt::KeyboardModifiers m_keyboardModifiers; 0148 bool m_hasArkFormat; 0149 QString m_remoteArkDBusClient; 0150 QString m_remoteArkDBusPath; 0151 QUrl m_destUrl; 0152 KFileItem m_destItem; // null for remote URLs not found in the dirlister cache 0153 const JobFlags m_flags; 0154 const DropJobFlags m_dropjobFlags; 0155 QList<QAction *> m_appActions; 0156 QList<QAction *> m_pluginActions; 0157 bool m_triggered; // Tracks whether an action has been triggered in the popup menu. 0158 QSet<KIO::DropMenu *> m_menus; 0159 0160 Q_DECLARE_PUBLIC(DropJob) 0161 0162 void slotStart(); 0163 void slotTriggered(QAction *); 0164 void slotAboutToHide(); 0165 0166 static inline DropJob *newJob(const QDropEvent *dropEvent, const QUrl &destUrl, DropJobFlags dropjobFlags, JobFlags flags) 0167 { 0168 DropJob *job = new DropJob(*new DropJobPrivate(dropEvent, destUrl, dropjobFlags, flags)); 0169 job->setUiDelegate(KIO::createDefaultJobUiDelegate()); 0170 // Note: never KIO::getJobTracker()->registerJob here. 0171 // We don't want a progress dialog during the copy/move/link popup, it would in fact close 0172 // the popup 0173 return job; 0174 } 0175 }; 0176 0177 DropMenu::DropMenu(QWidget *parent) 0178 : QMenu(parent) 0179 , m_extraActionsSeparator(nullptr) 0180 { 0181 m_cancelAction = new QAction(i18n("C&ancel") + QLatin1Char('\t') + QKeySequence(Qt::Key_Escape).toString(QKeySequence::NativeText), this); 0182 m_cancelAction->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); 0183 0184 m_lastSeparator = new QAction(this); 0185 m_lastSeparator->setSeparator(true); 0186 } 0187 0188 DropMenu::~DropMenu() 0189 { 0190 } 0191 0192 void DropMenu::addExtraActions(const QList<QAction *> &appActions, const QList<QAction *> &pluginActions) 0193 { 0194 removeAction(m_lastSeparator); 0195 removeAction(m_cancelAction); 0196 0197 removeAction(m_extraActionsSeparator); 0198 for (QAction *action : std::as_const(m_appActions)) { 0199 removeAction(action); 0200 } 0201 for (QAction *action : std::as_const(m_pluginActions)) { 0202 removeAction(action); 0203 } 0204 0205 m_appActions = appActions; 0206 m_pluginActions = pluginActions; 0207 0208 if (!m_appActions.isEmpty() || !m_pluginActions.isEmpty()) { 0209 QAction *firstExtraAction = m_appActions.value(0, m_pluginActions.value(0, nullptr)); 0210 if (firstExtraAction && !firstExtraAction->isSeparator()) { 0211 if (!m_extraActionsSeparator) { 0212 m_extraActionsSeparator = new QAction(this); 0213 m_extraActionsSeparator->setSeparator(true); 0214 } 0215 addAction(m_extraActionsSeparator); 0216 } 0217 addActions(appActions); 0218 addActions(pluginActions); 0219 } 0220 0221 addAction(m_lastSeparator); 0222 addAction(m_cancelAction); 0223 } 0224 0225 DropJob::DropJob(DropJobPrivate &dd) 0226 : Job(dd) 0227 { 0228 Q_D(DropJob); 0229 0230 QTimer::singleShot(0, this, [d]() { 0231 d->slotStart(); 0232 }); 0233 } 0234 0235 DropJob::~DropJob() 0236 { 0237 } 0238 0239 void DropJobPrivate::slotStart() 0240 { 0241 Q_Q(DropJob); 0242 0243 if (m_hasArkFormat) { 0244 QDBusMessage message = QDBusMessage::createMethodCall(m_remoteArkDBusClient, 0245 m_remoteArkDBusPath, 0246 QStringLiteral("org.kde.ark.DndExtract"), 0247 QStringLiteral("extractSelectedFilesTo")); 0248 message.setArguments({m_destUrl.toDisplayString(QUrl::PreferLocalFile)}); 0249 const auto pending = QDBusConnection::sessionBus().asyncCall(message); 0250 auto watcher = std::make_shared<QDBusPendingCallWatcher>(pending); 0251 QObject::connect(watcher.get(), &QDBusPendingCallWatcher::finished, q, [this, watcher] { 0252 Q_Q(DropJob); 0253 0254 if (watcher->isError()) { 0255 q->setError(KIO::ERR_UNKNOWN); 0256 } 0257 q->emitResult(); 0258 }); 0259 0260 return; 0261 } 0262 0263 if (!m_urls.isEmpty()) { 0264 if (destIsDirectory()) { 0265 handleCopyToDirectory(); 0266 } else { // local file 0267 const QString destFile = m_destUrl.toLocalFile(); 0268 if (KDesktopFile::isDesktopFile(destFile)) { 0269 handleDropToDesktopFile(); 0270 } else if (QFileInfo(destFile).isExecutable()) { 0271 handleDropToExecutable(); 0272 } else { 0273 // should not happen, if KDirModel::flags is correct 0274 q->setError(KIO::ERR_ACCESS_DENIED); 0275 q->emitResult(); 0276 } 0277 } 0278 } else if (m_mimeData) { 0279 // Dropping raw data 0280 KIO::PasteJob *job = KIO::PasteJobPrivate::newJob(m_mimeData, m_destUrl, KIO::HideProgressInfo, false /*not clipboard*/); 0281 QObject::connect(job, &KIO::PasteJob::itemCreated, q, &KIO::DropJob::itemCreated); 0282 q->addSubjob(job); 0283 } 0284 } 0285 0286 void DropJobPrivate::fillPopupMenu(KIO::DropMenu *popup) 0287 { 0288 Q_Q(DropJob); 0289 0290 // Check what the source can do 0291 // TODO: Determining the MIME type of the source URLs is difficult for remote URLs, 0292 // we would need to KIO::stat each URL in turn, asynchronously.... 0293 KFileItemList fileItems; 0294 fileItems.reserve(m_urls.size()); 0295 for (const QUrl &url : m_urls) { 0296 fileItems.append(KFileItem(url)); 0297 } 0298 const KFileItemListProperties itemProps(fileItems); 0299 0300 Q_EMIT q->popupMenuAboutToShow(itemProps); 0301 0302 const bool sReading = itemProps.supportsReading(); 0303 const bool sDeleting = itemProps.supportsDeleting(); 0304 const bool sMoving = itemProps.supportsMoving(); 0305 0306 const int separatorLength = QCoreApplication::translate("QShortcut", "+").size(); 0307 QString seq = QKeySequence(Qt::ShiftModifier).toString(QKeySequence::NativeText); 0308 seq.chop(separatorLength); // chop superfluous '+' 0309 QAction *popupMoveAction = new QAction(i18n("&Move Here") + QLatin1Char('\t') + seq, popup); 0310 popupMoveAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-move"), QIcon::fromTheme(QStringLiteral("go-jump")))); 0311 popupMoveAction->setData(QVariant::fromValue(Qt::MoveAction)); 0312 seq = QKeySequence(Qt::ControlModifier).toString(QKeySequence::NativeText); 0313 seq.chop(separatorLength); 0314 QAction *popupCopyAction = new QAction(i18n("&Copy Here") + QLatin1Char('\t') + seq, popup); 0315 popupCopyAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); 0316 popupCopyAction->setData(QVariant::fromValue(Qt::CopyAction)); 0317 seq = QKeySequence(Qt::ControlModifier | Qt::ShiftModifier).toString(QKeySequence::NativeText); 0318 seq.chop(separatorLength); 0319 QAction *popupLinkAction = new QAction(i18n("&Link Here") + QLatin1Char('\t') + seq, popup); 0320 popupLinkAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-link"))); 0321 popupLinkAction->setData(QVariant::fromValue(Qt::LinkAction)); 0322 0323 if (sMoving || (sReading && sDeleting)) { 0324 const bool equalDestination = std::all_of(m_urls.cbegin(), m_urls.cend(), [this](const QUrl &src) { 0325 return m_destUrl.matches(src.adjusted(QUrl::RemoveFilename), QUrl::StripTrailingSlash); 0326 }); 0327 0328 if (!equalDestination) { 0329 popup->addAction(popupMoveAction); 0330 } 0331 } 0332 0333 if (sReading) { 0334 popup->addAction(popupCopyAction); 0335 } 0336 0337 popup->addAction(popupLinkAction); 0338 0339 addPluginActions(popup, itemProps); 0340 } 0341 0342 void DropJobPrivate::addPluginActions(KIO::DropMenu *popup, const KFileItemListProperties &itemProps) 0343 { 0344 const QVector<KPluginMetaData> plugin_offers = KPluginMetaData::findPlugins(QStringLiteral("kf" QT_STRINGIFY(QT_VERSION_MAJOR) "/kio_dnd")); 0345 for (const KPluginMetaData &data : plugin_offers) { 0346 if (auto plugin = KPluginFactory::instantiatePlugin<KIO::DndPopupMenuPlugin>(data).plugin) { 0347 const auto actions = plugin->setup(itemProps, m_destUrl); 0348 for (auto action : actions) { 0349 action->setParent(popup); 0350 } 0351 m_pluginActions += actions; 0352 } 0353 } 0354 0355 popup->addExtraActions(m_appActions, m_pluginActions); 0356 } 0357 0358 void DropJob::setApplicationActions(const QList<QAction *> &actions) 0359 { 0360 Q_D(DropJob); 0361 0362 d->m_appActions = actions; 0363 0364 for (KIO::DropMenu *menu : std::as_const(d->m_menus)) { 0365 menu->addExtraActions(d->m_appActions, d->m_pluginActions); 0366 } 0367 } 0368 0369 void DropJob::showMenu(const QPoint &p, QAction *atAction) 0370 { 0371 Q_D(DropJob); 0372 0373 if (!(d->m_dropjobFlags & KIO::ShowMenuManually)) { 0374 return; 0375 } 0376 0377 for (KIO::DropMenu *menu : std::as_const(d->m_menus)) { 0378 menu->popup(p, atAction); 0379 } 0380 } 0381 0382 void DropJobPrivate::slotTriggered(QAction *action) 0383 { 0384 Q_Q(DropJob); 0385 if (m_appActions.contains(action) || m_pluginActions.contains(action)) { 0386 q->emitResult(); 0387 return; 0388 } 0389 const QVariant data = action->data(); 0390 if (!data.canConvert<Qt::DropAction>()) { 0391 q->setError(KIO::ERR_USER_CANCELED); 0392 q->emitResult(); 0393 return; 0394 } 0395 m_dropAction = data.value<Qt::DropAction>(); 0396 doCopyToDirectory(); 0397 } 0398 0399 void DropJobPrivate::slotAboutToHide() 0400 { 0401 Q_Q(DropJob); 0402 // QMenu emits aboutToHide before triggered. 0403 // So we need to give the menu time in case it needs to emit triggered. 0404 // If it does, the cleanup will be done by slotTriggered. 0405 QTimer::singleShot(0, q, [=]() { 0406 if (!m_triggered) { 0407 q->setError(KIO::ERR_USER_CANCELED); 0408 q->emitResult(); 0409 } 0410 }); 0411 } 0412 0413 void DropJobPrivate::handleCopyToDirectory() 0414 { 0415 Q_Q(DropJob); 0416 0417 // Process m_dropAction as set by Qt at the time of the drop event 0418 if (!KProtocolManager::supportsWriting(m_destUrl)) { 0419 slotDropActionDetermined(KIO::ERR_CANNOT_WRITE); 0420 return; 0421 } 0422 0423 if (!m_destItem.isNull() && !m_destItem.isWritable() && (m_flags & KIO::NoPrivilegeExecution)) { 0424 slotDropActionDetermined(KIO::ERR_WRITE_ACCESS_DENIED); 0425 return; 0426 } 0427 0428 bool allItemsAreFromTrash = true; 0429 bool containsTrashRoot = false; 0430 for (const QUrl &url : m_urls) { 0431 const bool local = url.isLocalFile(); 0432 if (!local /*optimization*/ && url.scheme() == QLatin1String("trash")) { 0433 if (url.path().isEmpty() || url.path() == QLatin1String("/")) { 0434 containsTrashRoot = true; 0435 } 0436 } else { 0437 allItemsAreFromTrash = false; 0438 } 0439 if (url.matches(m_destUrl, QUrl::StripTrailingSlash)) { 0440 slotDropActionDetermined(KIO::ERR_DROP_ON_ITSELF); 0441 return; 0442 } 0443 } 0444 0445 const bool trashing = m_destUrl.scheme() == QLatin1String("trash"); 0446 if (trashing) { 0447 if (allItemsAreFromTrash) { 0448 qCDebug(KIO_WIDGETS) << "Dropping items from trash to trash"; 0449 slotDropActionDetermined(KIO::ERR_DROP_ON_ITSELF); 0450 return; 0451 } 0452 m_dropAction = Qt::MoveAction; 0453 0454 auto *askUserInterface = KIO::delegateExtension<AskUserActionInterface *>(q); 0455 0456 // No UI Delegate set for this job, or a delegate that doesn't implement 0457 // AskUserActionInterface, then just proceed with the job without asking. 0458 // This is useful for non-interactive usage, (which doesn't actually apply 0459 // here as a DropJob is always interactive), but this is useful for unittests, 0460 // which are typically non-interactive. 0461 if (!askUserInterface) { 0462 slotDropActionDetermined(KJob::NoError); 0463 return; 0464 } 0465 0466 QObject::connect(askUserInterface, &KIO::AskUserActionInterface::askUserDeleteResult, q, [this](bool allowDelete) { 0467 if (allowDelete) { 0468 slotDropActionDetermined(KJob::NoError); 0469 } else { 0470 slotDropActionDetermined(KIO::ERR_USER_CANCELED); 0471 } 0472 }); 0473 0474 askUserInterface->askUserDelete(m_urls, KIO::AskUserActionInterface::Trash, KIO::AskUserActionInterface::DefaultConfirmation, KJobWidgets::window(q)); 0475 return; 0476 } 0477 0478 // If we can't determine the action below, we use ERR::UNKNOWN as we need to ask 0479 // the user via a popup menu. 0480 int err = KIO::ERR_UNKNOWN; 0481 const bool implicitCopy = m_destUrl.scheme() == QLatin1String("stash"); 0482 if (implicitCopy) { 0483 m_dropAction = Qt::CopyAction; 0484 err = KJob::NoError; // Ok 0485 } else if (containsTrashRoot) { 0486 // Dropping a link to the trash: don't move the full contents, just make a link (#319660) 0487 m_dropAction = Qt::LinkAction; 0488 err = KJob::NoError; // Ok 0489 } else if (allItemsAreFromTrash) { 0490 // No point in asking copy/move/link when using dragging from the trash, just move the file out. 0491 m_dropAction = Qt::MoveAction; 0492 err = KJob::NoError; // Ok 0493 } else if (m_keyboardModifiers & (Qt::ControlModifier | Qt::ShiftModifier | Qt::AltModifier)) { 0494 // Qt determined m_dropAction from the modifiers already 0495 err = KJob::NoError; // Ok 0496 } 0497 slotDropActionDetermined(err); 0498 } 0499 0500 void DropJobPrivate::slotDropActionDetermined(int error) 0501 { 0502 Q_Q(DropJob); 0503 0504 if (error == KJob::NoError) { 0505 doCopyToDirectory(); 0506 return; 0507 } 0508 0509 // There was an error, handle it 0510 if (error == KIO::ERR_UNKNOWN) { 0511 auto *window = KJobWidgets::window(q); 0512 KIO::DropMenu *menu = new KIO::DropMenu(window); 0513 QObject::connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater); 0514 0515 // If the user clicks outside the menu, it will be destroyed without emitting the triggered signal. 0516 QObject::connect(menu, &QMenu::aboutToHide, q, [this]() { 0517 slotAboutToHide(); 0518 }); 0519 0520 fillPopupMenu(menu); 0521 QObject::connect(menu, &QMenu::triggered, q, [this](QAction *action) { 0522 m_triggered = true; 0523 slotTriggered(action); 0524 }); 0525 0526 if (!(m_dropjobFlags & KIO::ShowMenuManually)) { 0527 menu->popup(window ? window->mapToGlobal(m_relativePos) : QCursor::pos()); 0528 } 0529 m_menus.insert(menu); 0530 QObject::connect(menu, &QObject::destroyed, q, [this, menu]() { 0531 m_menus.remove(menu); 0532 }); 0533 } else { 0534 q->setError(error); 0535 q->emitResult(); 0536 } 0537 } 0538 0539 void DropJobPrivate::doCopyToDirectory() 0540 { 0541 Q_Q(DropJob); 0542 KIO::CopyJob *job = nullptr; 0543 switch (m_dropAction) { 0544 case Qt::MoveAction: 0545 job = KIO::move(m_urls, m_destUrl, m_flags); 0546 KIO::FileUndoManager::self()->recordJob(m_destUrl.scheme() == QLatin1String("trash") ? KIO::FileUndoManager::Trash : KIO::FileUndoManager::Move, 0547 m_urls, 0548 m_destUrl, 0549 job); 0550 break; 0551 case Qt::CopyAction: 0552 job = KIO::copy(m_urls, m_destUrl, m_flags); 0553 KIO::FileUndoManager::self()->recordCopyJob(job); 0554 break; 0555 case Qt::LinkAction: 0556 job = KIO::link(m_urls, m_destUrl, m_flags); 0557 KIO::FileUndoManager::self()->recordCopyJob(job); 0558 break; 0559 default: 0560 qCWarning(KIO_WIDGETS) << "Unknown drop action" << int(m_dropAction); 0561 q->setError(KIO::ERR_UNSUPPORTED_ACTION); 0562 q->emitResult(); 0563 return; 0564 } 0565 Q_ASSERT(job); 0566 job->setParentJob(q); 0567 job->setMetaData(m_metaData); 0568 QObject::connect(job, &KIO::CopyJob::copyingDone, q, [q](KIO::Job *, const QUrl &, const QUrl &to) { 0569 Q_EMIT q->itemCreated(to); 0570 }); 0571 QObject::connect(job, &KIO::CopyJob::copyingLinkDone, q, [q](KIO::Job *, const QUrl &, const QString &, const QUrl &to) { 0572 Q_EMIT q->itemCreated(to); 0573 }); 0574 q->addSubjob(job); 0575 0576 Q_EMIT q->copyJobStarted(job); 0577 } 0578 0579 void DropJobPrivate::handleDropToDesktopFile() 0580 { 0581 Q_Q(DropJob); 0582 const QString urlKey = QStringLiteral("URL"); 0583 const QString destFile = m_destUrl.toLocalFile(); 0584 const KDesktopFile desktopFile(destFile); 0585 const KConfigGroup desktopGroup = desktopFile.desktopGroup(); 0586 if (desktopFile.hasApplicationType()) { 0587 // Drop to application -> start app with urls as argument 0588 KService::Ptr service(new KService(destFile)); 0589 // Can't use setParentJob() because ApplicationLauncherJob isn't a KIO::Job, 0590 // instead pass q as parent so that KIO::delegateExtension() can find a delegate 0591 KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(service, q); 0592 job->setUrls(m_urls); 0593 QObject::connect(job, &KJob::result, q, [=]() { 0594 if (job->error()) { 0595 q->setError(KIO::ERR_CANNOT_LAUNCH_PROCESS); 0596 q->setErrorText(destFile); 0597 } 0598 q->emitResult(); 0599 }); 0600 job->start(); 0601 } else if (desktopFile.hasLinkType() && desktopGroup.hasKey(urlKey)) { 0602 // Drop to link -> adjust destination directory 0603 m_destUrl = QUrl::fromUserInput(desktopGroup.readPathEntry(urlKey, QString())); 0604 handleCopyToDirectory(); 0605 } else { 0606 if (desktopFile.hasDeviceType()) { 0607 qCWarning(KIO_WIDGETS) << "Not re-implemented; please email kde-frameworks-devel@kde.org if you need this."; 0608 // take code from libkonq's old konq_operations.cpp 0609 // for now, fallback 0610 } 0611 // Some other kind of .desktop file (service, servicetype...) 0612 q->setError(KIO::ERR_UNSUPPORTED_ACTION); 0613 q->emitResult(); 0614 } 0615 } 0616 0617 void DropJobPrivate::handleDropToExecutable() 0618 { 0619 Q_Q(DropJob); 0620 // Launch executable for each of the files 0621 QStringList args; 0622 args.reserve(m_urls.size()); 0623 for (const QUrl &url : std::as_const(m_urls)) { 0624 args << url.toLocalFile(); // assume local files 0625 } 0626 QProcess::startDetached(m_destUrl.toLocalFile(), args); 0627 q->emitResult(); 0628 } 0629 0630 void DropJob::slotResult(KJob *job) 0631 { 0632 if (job->error()) { 0633 KIO::Job::slotResult(job); // will set the error and emit result(this) 0634 return; 0635 } 0636 removeSubjob(job); 0637 emitResult(); 0638 } 0639 0640 DropJob *KIO::drop(const QDropEvent *dropEvent, const QUrl &destUrl, JobFlags flags) 0641 { 0642 return DropJobPrivate::newJob(dropEvent, destUrl, KIO::DropJobDefaultFlags, flags); 0643 } 0644 0645 DropJob *KIO::drop(const QDropEvent *dropEvent, const QUrl &destUrl, DropJobFlags dropjobFlags, JobFlags flags) 0646 { 0647 return DropJobPrivate::newJob(dropEvent, destUrl, dropjobFlags, flags); 0648 } 0649 0650 #include "dropjob.moc" 0651 #include "moc_dropjob.cpp"