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