1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2014 David Faure <faure@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6*/
7
8#include "dropjob.h"
9
10#include "job_p.h"
11#include "jobuidelegate.h"
12#include "jobuidelegateextension.h"
13#include "kio_widgets_debug.h"
14#include "pastejob.h"
15#include "pastejob_p.h"
16
17#include <KConfigGroup>
18#include <KCoreDirLister>
19#include <KDesktopFile>
20#include <KFileItem>
21#include <KFileItemListProperties>
22#include <KIO/ApplicationLauncherJob>
23#include <KIO/CopyJob>
24#include <KIO/DndPopupMenuPlugin>
25#include <KIO/FileUndoManager>
26#include <KJobWidgets>
27#include <KLocalizedString>
28#include <KPluginFactory>
29#include <KPluginMetaData>
30#include <KProtocolManager>
31#include <KService>
32#include <KUrlMimeData>
33
34#include <QDBusConnection>
35#include <QDBusPendingCall>
36#include <QDropEvent>
37#include <QFileInfo>
38#include <QMenu>
39#include <QMimeData>
40#include <QProcess>
41#include <QTimer>
42
43using namespace KIO;
44
45Q_DECLARE_METATYPE(Qt::DropAction)
46
47namespace KIO
48{
49class DropMenu;
50}
51
52class KIO::DropMenu : public QMenu
53{
54 Q_OBJECT
55public:
56 explicit DropMenu(QWidget *parent = nullptr);
57 ~DropMenu() override;
58
59 void addCancelAction();
60 void addExtraActions(const QList<QAction *> &appActions, const QList<QAction *> &pluginActions);
61
62private:
63 QList<QAction *> m_appActions;
64 QList<QAction *> m_pluginActions;
65 QAction *m_lastSeparator;
66 QAction *m_extraActionsSeparator;
67 QAction *m_cancelAction;
68};
69
70static const QString s_applicationSlashXDashKDEDashArkDashDnDExtractDashService = //
71 QStringLiteral("application/x-kde-ark-dndextract-service");
72static const QString s_applicationSlashXDashKDEDashArkDashDnDExtractDashPath = //
73 QStringLiteral("application/x-kde-ark-dndextract-path");
74
75class KIO::DropJobPrivate : public KIO::JobPrivate
76{
77public:
78 DropJobPrivate(const QDropEvent *dropEvent, const QUrl &destUrl, DropJobFlags dropjobFlags, JobFlags flags)
79 : JobPrivate()
80 , m_mimeData(dropEvent->mimeData()) // Extract everything from the dropevent, since it will be deleted before the job starts
81 , m_urls(KUrlMimeData::urlsFromMimeData(mimeData: m_mimeData, decodeOptions: KUrlMimeData::PreferLocalUrls, metaData: &m_metaData))
82 , m_dropAction(dropEvent->dropAction())
83 , m_relativePos(dropEvent->position().toPoint())
84 , m_keyboardModifiers(dropEvent->modifiers())
85 , m_hasArkFormat(m_mimeData->hasFormat(mimetype: s_applicationSlashXDashKDEDashArkDashDnDExtractDashService)
86 && m_mimeData->hasFormat(mimetype: s_applicationSlashXDashKDEDashArkDashDnDExtractDashPath))
87 , m_destUrl(destUrl)
88 , m_destItem(KCoreDirLister::cachedItemForUrl(url: destUrl))
89 , m_flags(flags)
90 , m_dropjobFlags(dropjobFlags)
91 , m_triggered(false)
92 {
93 // Check for the drop of a bookmark -> we want a Link action
94 if (m_mimeData->hasFormat(QStringLiteral("application/x-xbel"))) {
95 m_keyboardModifiers |= Qt::KeyboardModifiers(Qt::ControlModifier | Qt::ShiftModifier);
96 m_dropAction = Qt::LinkAction;
97 }
98 if (m_destItem.isNull() && m_destUrl.isLocalFile()) {
99 m_destItem = KFileItem(m_destUrl);
100 }
101
102 if (m_hasArkFormat) {
103 m_remoteArkDBusClient = QString::fromUtf8(ba: m_mimeData->data(mimetype: s_applicationSlashXDashKDEDashArkDashDnDExtractDashService));
104 m_remoteArkDBusPath = QString::fromUtf8(ba: m_mimeData->data(mimetype: s_applicationSlashXDashKDEDashArkDashDnDExtractDashPath));
105 }
106
107 if (!(m_flags & KIO::NoPrivilegeExecution)) {
108 m_privilegeExecutionEnabled = true;
109 switch (m_dropAction) {
110 case Qt::CopyAction:
111 m_operationType = Copy;
112 break;
113 case Qt::MoveAction:
114 m_operationType = Move;
115 break;
116 case Qt::LinkAction:
117 m_operationType = Symlink;
118 break;
119 default:
120 m_operationType = Other;
121 break;
122 }
123 }
124 }
125
126 bool destIsDirectory() const
127 {
128 if (!m_destItem.isNull()) {
129 return m_destItem.isDir();
130 }
131 // We support local dir, remote dir, local desktop file, local executable.
132 // So for remote URLs, we just assume they point to a directory, the user will get an error from KIO::copy if not.
133 return true;
134 }
135 void handleCopyToDirectory();
136 void slotDropActionDetermined(int error);
137 void handleDropToDesktopFile();
138 void handleDropToExecutable();
139 void fillPopupMenu(KIO::DropMenu *popup);
140 void addPluginActions(KIO::DropMenu *popup, const KFileItemListProperties &itemProps);
141 void doCopyToDirectory();
142
143 QPointer<const QMimeData> m_mimeData;
144 const QList<QUrl> m_urls;
145 QMap<QString, QString> m_metaData;
146 Qt::DropAction m_dropAction;
147 QPoint m_relativePos;
148 Qt::KeyboardModifiers m_keyboardModifiers;
149 bool m_hasArkFormat;
150 QString m_remoteArkDBusClient;
151 QString m_remoteArkDBusPath;
152 QUrl m_destUrl;
153 KFileItem m_destItem; // null for remote URLs not found in the dirlister cache
154 const JobFlags m_flags;
155 const DropJobFlags m_dropjobFlags;
156 QList<QAction *> m_appActions;
157 QList<QAction *> m_pluginActions;
158 bool m_triggered; // Tracks whether an action has been triggered in the popup menu.
159 QSet<KIO::DropMenu *> m_menus;
160
161 Q_DECLARE_PUBLIC(DropJob)
162
163 void slotStart();
164 void slotTriggered(QAction *);
165 void slotAboutToHide();
166
167 static inline DropJob *newJob(const QDropEvent *dropEvent, const QUrl &destUrl, DropJobFlags dropjobFlags, JobFlags flags)
168 {
169 DropJob *job = new DropJob(*new DropJobPrivate(dropEvent, destUrl, dropjobFlags, flags));
170 job->setUiDelegate(KIO::createDefaultJobUiDelegate());
171 // Note: never KIO::getJobTracker()->registerJob here.
172 // We don't want a progress dialog during the copy/move/link popup, it would in fact close
173 // the popup
174 return job;
175 }
176};
177
178DropMenu::DropMenu(QWidget *parent)
179 : QMenu(parent)
180 , m_extraActionsSeparator(nullptr)
181{
182 m_cancelAction = new QAction(i18n("C&ancel") + QLatin1Char('\t') + QKeySequence(Qt::Key_Escape).toString(format: QKeySequence::NativeText), this);
183 m_cancelAction->setIcon(QIcon::fromTheme(QStringLiteral("process-stop")));
184
185 m_lastSeparator = new QAction(this);
186 m_lastSeparator->setSeparator(true);
187}
188
189DropMenu::~DropMenu()
190{
191}
192
193void DropMenu::addExtraActions(const QList<QAction *> &appActions, const QList<QAction *> &pluginActions)
194{
195 removeAction(action: m_lastSeparator);
196 removeAction(action: m_cancelAction);
197
198 removeAction(action: m_extraActionsSeparator);
199 for (QAction *action : std::as_const(t&: m_appActions)) {
200 removeAction(action);
201 }
202 for (QAction *action : std::as_const(t&: m_pluginActions)) {
203 removeAction(action);
204 }
205
206 m_appActions = appActions;
207 m_pluginActions = pluginActions;
208
209 if (!m_appActions.isEmpty() || !m_pluginActions.isEmpty()) {
210 QAction *firstExtraAction = m_appActions.value(i: 0, defaultValue: m_pluginActions.value(i: 0, defaultValue: nullptr));
211 if (firstExtraAction && !firstExtraAction->isSeparator()) {
212 if (!m_extraActionsSeparator) {
213 m_extraActionsSeparator = new QAction(this);
214 m_extraActionsSeparator->setSeparator(true);
215 }
216 addAction(action: m_extraActionsSeparator);
217 }
218 addActions(actions: appActions);
219 addActions(actions: pluginActions);
220 }
221
222 addAction(action: m_lastSeparator);
223 addAction(action: m_cancelAction);
224}
225
226DropJob::DropJob(DropJobPrivate &dd)
227 : Job(dd)
228{
229 Q_D(DropJob);
230
231 QTimer::singleShot(interval: 0, receiver: this, slot: [d]() {
232 d->slotStart();
233 });
234}
235
236DropJob::~DropJob()
237{
238}
239
240void DropJobPrivate::slotStart()
241{
242 Q_Q(DropJob);
243
244 if (m_hasArkFormat) {
245 QDBusMessage message = QDBusMessage::createMethodCall(destination: m_remoteArkDBusClient,
246 path: m_remoteArkDBusPath,
247 QStringLiteral("org.kde.ark.DndExtract"),
248 QStringLiteral("extractSelectedFilesTo"));
249 message.setArguments({m_destUrl.toDisplayString(options: QUrl::PreferLocalFile)});
250 const auto pending = QDBusConnection::sessionBus().asyncCall(message);
251 auto watcher = std::make_shared<QDBusPendingCallWatcher>(args: pending);
252 QObject::connect(sender: watcher.get(), signal: &QDBusPendingCallWatcher::finished, context: q, slot: [this, watcher] {
253 Q_Q(DropJob);
254
255 if (watcher->isError()) {
256 q->setError(KIO::ERR_UNKNOWN);
257 }
258 q->emitResult();
259 });
260
261 return;
262 }
263
264 if (!m_urls.isEmpty()) {
265 if (destIsDirectory()) {
266 handleCopyToDirectory();
267 } else { // local file
268 const QString destFile = m_destUrl.toLocalFile();
269 if (KDesktopFile::isDesktopFile(path: destFile)) {
270 handleDropToDesktopFile();
271 } else if (QFileInfo(destFile).isExecutable()) {
272 handleDropToExecutable();
273 } else {
274 // should not happen, if KDirModel::flags is correct
275 q->setError(KIO::ERR_ACCESS_DENIED);
276 q->emitResult();
277 }
278 }
279 } else if (m_mimeData) {
280 // Dropping raw data
281 KIO::PasteJob *job = KIO::PasteJobPrivate::newJob(mimeData: m_mimeData, destDir: m_destUrl, flags: KIO::HideProgressInfo, clipboard: false /*not clipboard*/);
282 QObject::connect(sender: job, signal: &KIO::PasteJob::itemCreated, context: q, slot: &KIO::DropJob::itemCreated);
283 q->addSubjob(job);
284 }
285}
286
287void DropJobPrivate::fillPopupMenu(KIO::DropMenu *popup)
288{
289 Q_Q(DropJob);
290
291 // Check what the source can do
292 // TODO: Determining the MIME type of the source URLs is difficult for remote URLs,
293 // we would need to KIO::stat each URL in turn, asynchronously....
294 KFileItemList fileItems;
295 fileItems.reserve(asize: m_urls.size());
296 for (const QUrl &url : m_urls) {
297 fileItems.append(t: KFileItem(url));
298 }
299 const KFileItemListProperties itemProps(fileItems);
300
301 Q_EMIT q->popupMenuAboutToShow(itemProps);
302
303 const bool sReading = itemProps.supportsReading();
304 const bool sDeleting = itemProps.supportsDeleting();
305 const bool sMoving = itemProps.supportsMoving();
306
307 const int separatorLength = QCoreApplication::translate(context: "QShortcut", key: "+").size();
308 QString seq = QKeySequence(Qt::ShiftModifier).toString(format: QKeySequence::NativeText);
309 seq.chop(n: separatorLength); // chop superfluous '+'
310 QAction *popupMoveAction = new QAction(i18n("&Move Here") + QLatin1Char('\t') + seq, popup);
311 popupMoveAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-move"), fallback: QIcon::fromTheme(QStringLiteral("go-jump"))));
312 popupMoveAction->setData(QVariant::fromValue(value: Qt::MoveAction));
313 seq = QKeySequence(Qt::ControlModifier).toString(format: QKeySequence::NativeText);
314 seq.chop(n: separatorLength);
315 QAction *popupCopyAction = new QAction(i18n("&Copy Here") + QLatin1Char('\t') + seq, popup);
316 popupCopyAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
317 popupCopyAction->setData(QVariant::fromValue(value: Qt::CopyAction));
318 seq = QKeySequence(Qt::ControlModifier | Qt::ShiftModifier).toString(format: QKeySequence::NativeText);
319 seq.chop(n: separatorLength);
320 QAction *popupLinkAction = new QAction(i18n("&Link Here") + QLatin1Char('\t') + seq, popup);
321 popupLinkAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-link")));
322 popupLinkAction->setData(QVariant::fromValue(value: Qt::LinkAction));
323
324 if (sMoving || (sReading && sDeleting)) {
325 const bool equalDestination = std::all_of(first: m_urls.cbegin(), last: m_urls.cend(), pred: [this](const QUrl &src) {
326 return m_destUrl.matches(url: src.adjusted(options: QUrl::RemoveFilename), options: QUrl::StripTrailingSlash);
327 });
328
329 if (!equalDestination) {
330 popup->addAction(action: popupMoveAction);
331 }
332 }
333
334 if (sReading) {
335 popup->addAction(action: popupCopyAction);
336 }
337
338 popup->addAction(action: popupLinkAction);
339
340 addPluginActions(popup, itemProps);
341}
342
343void DropJobPrivate::addPluginActions(KIO::DropMenu *popup, const KFileItemListProperties &itemProps)
344{
345 const QList<KPluginMetaData> plugin_offers = KPluginMetaData::findPlugins(QStringLiteral("kf6/kio_dnd"));
346 for (const KPluginMetaData &data : plugin_offers) {
347 if (auto plugin = KPluginFactory::instantiatePlugin<KIO::DndPopupMenuPlugin>(data).plugin) {
348 const auto actions = plugin->setup(popupMenuInfo: itemProps, destination: m_destUrl);
349 for (auto action : actions) {
350 action->setParent(popup);
351 }
352 m_pluginActions += actions;
353 }
354 }
355
356 popup->addExtraActions(appActions: m_appActions, pluginActions: m_pluginActions);
357}
358
359void DropJob::setApplicationActions(const QList<QAction *> &actions)
360{
361 Q_D(DropJob);
362
363 d->m_appActions = actions;
364
365 for (KIO::DropMenu *menu : std::as_const(t&: d->m_menus)) {
366 menu->addExtraActions(appActions: d->m_appActions, pluginActions: d->m_pluginActions);
367 }
368}
369
370void DropJob::showMenu(const QPoint &p, QAction *atAction)
371{
372 Q_D(DropJob);
373
374 if (!(d->m_dropjobFlags & KIO::ShowMenuManually)) {
375 return;
376 }
377
378 for (KIO::DropMenu *menu : std::as_const(t&: d->m_menus)) {
379 menu->popup(pos: p, at: atAction);
380 }
381}
382
383void DropJobPrivate::slotTriggered(QAction *action)
384{
385 Q_Q(DropJob);
386 if (m_appActions.contains(t: action) || m_pluginActions.contains(t: action)) {
387 q->emitResult();
388 return;
389 }
390 const QVariant data = action->data();
391 if (!data.canConvert<Qt::DropAction>()) {
392 q->setError(KIO::ERR_USER_CANCELED);
393 q->emitResult();
394 return;
395 }
396 m_dropAction = data.value<Qt::DropAction>();
397 doCopyToDirectory();
398}
399
400void DropJobPrivate::slotAboutToHide()
401{
402 Q_Q(DropJob);
403 // QMenu emits aboutToHide before triggered.
404 // So we need to give the menu time in case it needs to emit triggered.
405 // If it does, the cleanup will be done by slotTriggered.
406 QTimer::singleShot(interval: 0, receiver: q, slot: [=, this]() {
407 if (!m_triggered) {
408 q->setError(KIO::ERR_USER_CANCELED);
409 q->emitResult();
410 }
411 });
412}
413
414void DropJobPrivate::handleCopyToDirectory()
415{
416 Q_Q(DropJob);
417
418 // Process m_dropAction as set by Qt at the time of the drop event
419 if (!KProtocolManager::supportsWriting(url: m_destUrl)) {
420 slotDropActionDetermined(error: KIO::ERR_CANNOT_WRITE);
421 return;
422 }
423
424 if (!m_destItem.isNull() && !m_destItem.isWritable() && (m_flags & KIO::NoPrivilegeExecution)) {
425 slotDropActionDetermined(error: KIO::ERR_WRITE_ACCESS_DENIED);
426 return;
427 }
428
429 bool allItemsAreFromTrash = true;
430 bool containsTrashRoot = false;
431 for (const QUrl &url : m_urls) {
432 const bool local = url.isLocalFile();
433 if (!local /*optimization*/ && url.scheme() == QLatin1String("trash")) {
434 if (url.path().isEmpty() || url.path() == QLatin1String("/")) {
435 containsTrashRoot = true;
436 }
437 } else {
438 allItemsAreFromTrash = false;
439 }
440 if (url.matches(url: m_destUrl, options: QUrl::StripTrailingSlash)) {
441 slotDropActionDetermined(error: KIO::ERR_DROP_ON_ITSELF);
442 return;
443 }
444 }
445
446 const bool trashing = m_destUrl.scheme() == QLatin1String("trash");
447 if (trashing) {
448 if (allItemsAreFromTrash) {
449 qCDebug(KIO_WIDGETS) << "Dropping items from trash to trash";
450 slotDropActionDetermined(error: KIO::ERR_DROP_ON_ITSELF);
451 return;
452 }
453 m_dropAction = Qt::MoveAction;
454
455 auto *askUserInterface = KIO::delegateExtension<AskUserActionInterface *>(job: q);
456
457 // No UI Delegate set for this job, or a delegate that doesn't implement
458 // AskUserActionInterface, then just proceed with the job without asking.
459 // This is useful for non-interactive usage, (which doesn't actually apply
460 // here as a DropJob is always interactive), but this is useful for unittests,
461 // which are typically non-interactive.
462 if (!askUserInterface) {
463 slotDropActionDetermined(error: KJob::NoError);
464 return;
465 }
466
467 QObject::connect(sender: askUserInterface, signal: &KIO::AskUserActionInterface::askUserDeleteResult, context: q, slot: [this](bool allowDelete) {
468 if (allowDelete) {
469 slotDropActionDetermined(error: KJob::NoError);
470 } else {
471 slotDropActionDetermined(error: KIO::ERR_USER_CANCELED);
472 }
473 });
474
475 askUserInterface->askUserDelete(urls: m_urls, deletionType: KIO::AskUserActionInterface::Trash, confirmationType: KIO::AskUserActionInterface::DefaultConfirmation, parent: KJobWidgets::window(job: q));
476 return;
477 }
478
479 // If we can't determine the action below, we use ERR::UNKNOWN as we need to ask
480 // the user via a popup menu.
481 int err = KIO::ERR_UNKNOWN;
482 const bool implicitCopy = m_destUrl.scheme() == QLatin1String("stash");
483 if (implicitCopy) {
484 m_dropAction = Qt::CopyAction;
485 err = KJob::NoError; // Ok
486 } else if (containsTrashRoot) {
487 // Dropping a link to the trash: don't move the full contents, just make a link (#319660)
488 m_dropAction = Qt::LinkAction;
489 err = KJob::NoError; // Ok
490 } else if (allItemsAreFromTrash) {
491 // No point in asking copy/move/link when using dragging from the trash, just move the file out.
492 m_dropAction = Qt::MoveAction;
493 err = KJob::NoError; // Ok
494 } else if (m_keyboardModifiers & (Qt::ControlModifier | Qt::ShiftModifier | Qt::AltModifier)) {
495 // Qt determined m_dropAction from the modifiers already
496 err = KJob::NoError; // Ok
497 }
498 slotDropActionDetermined(error: err);
499}
500
501void DropJobPrivate::slotDropActionDetermined(int error)
502{
503 Q_Q(DropJob);
504
505 if (error == KJob::NoError) {
506 doCopyToDirectory();
507 return;
508 }
509
510 // There was an error, handle it
511 if (error == KIO::ERR_UNKNOWN) {
512 auto *window = KJobWidgets::window(job: q);
513 KIO::DropMenu *menu = new KIO::DropMenu(window);
514 QObject::connect(sender: menu, signal: &QMenu::aboutToHide, context: menu, slot: &QObject::deleteLater);
515
516 // If the user clicks outside the menu, it will be destroyed without emitting the triggered signal.
517 QObject::connect(sender: menu, signal: &QMenu::aboutToHide, context: q, slot: [this]() {
518 slotAboutToHide();
519 });
520
521 fillPopupMenu(popup: menu);
522 QObject::connect(sender: menu, signal: &QMenu::triggered, context: q, slot: [this](QAction *action) {
523 m_triggered = true;
524 slotTriggered(action);
525 });
526
527 if (!(m_dropjobFlags & KIO::ShowMenuManually)) {
528 menu->popup(pos: window ? window->mapToGlobal(m_relativePos) : QCursor::pos());
529 }
530 m_menus.insert(value: menu);
531 QObject::connect(sender: menu, signal: &QObject::destroyed, context: q, slot: [this, menu]() {
532 m_menus.remove(value: menu);
533 });
534 } else {
535 q->setError(error);
536 q->emitResult();
537 }
538}
539
540void DropJobPrivate::doCopyToDirectory()
541{
542 Q_Q(DropJob);
543 KIO::CopyJob *job = nullptr;
544 switch (m_dropAction) {
545 case Qt::MoveAction:
546 job = KIO::move(src: m_urls, dest: m_destUrl, flags: m_flags);
547 KIO::FileUndoManager::self()->recordJob(op: m_destUrl.scheme() == QLatin1String("trash") ? KIO::FileUndoManager::Trash : KIO::FileUndoManager::Move,
548 src: m_urls,
549 dst: m_destUrl,
550 job);
551 break;
552 case Qt::CopyAction:
553 job = KIO::copy(src: m_urls, dest: m_destUrl, flags: m_flags);
554 KIO::FileUndoManager::self()->recordCopyJob(copyJob: job);
555 break;
556 case Qt::LinkAction:
557 job = KIO::link(src: m_urls, destDir: m_destUrl, flags: m_flags);
558 KIO::FileUndoManager::self()->recordCopyJob(copyJob: job);
559 break;
560 default:
561 qCWarning(KIO_WIDGETS) << "Unknown drop action" << int(m_dropAction);
562 q->setError(KIO::ERR_UNSUPPORTED_ACTION);
563 q->emitResult();
564 return;
565 }
566 Q_ASSERT(job);
567 job->setParentJob(q);
568 job->setMetaData(m_metaData);
569 QObject::connect(sender: job, signal: &KIO::CopyJob::copyingDone, context: q, slot: [q](KIO::Job *, const QUrl &, const QUrl &to) {
570 Q_EMIT q->itemCreated(url: to);
571 });
572 QObject::connect(sender: job, signal: &KIO::CopyJob::copyingLinkDone, context: q, slot: [q](KIO::Job *, const QUrl &, const QString &, const QUrl &to) {
573 Q_EMIT q->itemCreated(url: to);
574 });
575 q->addSubjob(job);
576
577 Q_EMIT q->copyJobStarted(job);
578}
579
580void DropJobPrivate::handleDropToDesktopFile()
581{
582 Q_Q(DropJob);
583 const QString urlKey = QStringLiteral("URL");
584 const QString destFile = m_destUrl.toLocalFile();
585 const KDesktopFile desktopFile(destFile);
586 const KConfigGroup desktopGroup = desktopFile.desktopGroup();
587 if (desktopFile.hasApplicationType()) {
588 // Drop to application -> start app with urls as argument
589 KService::Ptr service(new KService(destFile));
590 // Can't use setParentJob() because ApplicationLauncherJob isn't a KIO::Job,
591 // instead pass q as parent so that KIO::delegateExtension() can find a delegate
592 KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(service, q);
593 job->setUrls(m_urls);
594 QObject::connect(sender: job, signal: &KJob::result, context: q, slot: [=]() {
595 if (job->error()) {
596 q->setError(KIO::ERR_CANNOT_LAUNCH_PROCESS);
597 q->setErrorText(destFile);
598 }
599 q->emitResult();
600 });
601 job->start();
602 } else if (desktopFile.hasLinkType() && desktopGroup.hasKey(key: urlKey)) {
603 // Drop to link -> adjust destination directory
604 m_destUrl = QUrl::fromUserInput(userInput: desktopGroup.readPathEntry(pKey: urlKey, aDefault: QString()));
605 handleCopyToDirectory();
606 } else {
607 if (desktopFile.hasDeviceType()) {
608 qCWarning(KIO_WIDGETS) << "Not re-implemented; please email kde-frameworks-devel@kde.org if you need this.";
609 // take code from libkonq's old konq_operations.cpp
610 // for now, fallback
611 }
612 // Some other kind of .desktop file (service, servicetype...)
613 q->setError(KIO::ERR_UNSUPPORTED_ACTION);
614 q->emitResult();
615 }
616}
617
618void DropJobPrivate::handleDropToExecutable()
619{
620 Q_Q(DropJob);
621 // Launch executable for each of the files
622 QStringList args;
623 args.reserve(asize: m_urls.size());
624 for (const QUrl &url : std::as_const(t: m_urls)) {
625 args << url.toLocalFile(); // assume local files
626 }
627 QProcess::startDetached(program: m_destUrl.toLocalFile(), arguments: args);
628 q->emitResult();
629}
630
631void DropJob::slotResult(KJob *job)
632{
633 if (job->error()) {
634 KIO::Job::slotResult(job); // will set the error and emit result(this)
635 return;
636 }
637 removeSubjob(job);
638 emitResult();
639}
640
641DropJob *KIO::drop(const QDropEvent *dropEvent, const QUrl &destUrl, JobFlags flags)
642{
643 return DropJobPrivate::newJob(dropEvent, destUrl, dropjobFlags: KIO::DropJobDefaultFlags, flags);
644}
645
646DropJob *KIO::drop(const QDropEvent *dropEvent, const QUrl &destUrl, DropJobFlags dropjobFlags, JobFlags flags)
647{
648 return DropJobPrivate::newJob(dropEvent, destUrl, dropjobFlags, flags);
649}
650
651#include "dropjob.moc"
652#include "moc_dropjob.cpp"
653

source code of kio/src/widgets/dropjob.cpp