File indexing completed on 2024-04-28 03:55:32

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2016 Kai Uwe Broulik <kde@privat.broulik.de>
0004     SPDX-FileCopyrightText: 2023 g10 Code GmbH
0005     SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "openfilemanagerwindowjob.h"
0011 #include "openfilemanagerwindowjob_p.h"
0012 
0013 #if USE_DBUS
0014 #include <QDBusConnection>
0015 #include <QDBusMessage>
0016 #include <QDBusPendingCallWatcher>
0017 #include <QDBusPendingReply>
0018 #endif
0019 #if defined(Q_OS_WINDOWS)
0020 #include <QDir>
0021 #include <shlobj.h>
0022 #include <vector>
0023 #endif
0024 #include <QGuiApplication>
0025 
0026 #include <KWindowSystem>
0027 
0028 #include "config-kiogui.h"
0029 #if HAVE_WAYLAND
0030 #include <KWaylandExtras>
0031 #endif
0032 
0033 #include <KIO/OpenUrlJob>
0034 
0035 namespace KIO
0036 {
0037 class OpenFileManagerWindowJobPrivate
0038 {
0039 public:
0040     OpenFileManagerWindowJobPrivate(OpenFileManagerWindowJob *qq)
0041         : q(qq)
0042         , strategy(nullptr)
0043     {
0044     }
0045 
0046     ~OpenFileManagerWindowJobPrivate() = default;
0047 
0048 #if USE_DBUS
0049     void createDBusStrategy()
0050     {
0051         strategy = std::make_unique<OpenFileManagerWindowDBusStrategy>(q);
0052     }
0053 #endif
0054 #if defined(Q_OS_WINDOWS)
0055     void createWindowsShellStrategy()
0056     {
0057         strategy = std::make_unique<OpenFileManagerWindowWindowsShellStrategy>(q);
0058     }
0059 #endif
0060 
0061     void createKRunStrategy()
0062     {
0063         strategy = std::make_unique<OpenFileManagerWindowKRunStrategy>(q);
0064     }
0065 
0066     OpenFileManagerWindowJob *const q;
0067     QList<QUrl> highlightUrls;
0068     QByteArray startupId;
0069 
0070     std::unique_ptr<AbstractOpenFileManagerWindowStrategy> strategy;
0071 };
0072 
0073 OpenFileManagerWindowJob::OpenFileManagerWindowJob(QObject *parent)
0074     : KJob(parent)
0075     , d(new OpenFileManagerWindowJobPrivate(this))
0076 {
0077 #if USE_DBUS
0078     d->createDBusStrategy();
0079 #elif defined(Q_OS_WINDOWS)
0080     d->createWindowsShellStrategy();
0081 #else
0082     d->createKRunStrategy();
0083 #endif
0084 }
0085 
0086 OpenFileManagerWindowJob::~OpenFileManagerWindowJob() = default;
0087 
0088 QList<QUrl> OpenFileManagerWindowJob::highlightUrls() const
0089 {
0090     return d->highlightUrls;
0091 }
0092 
0093 void OpenFileManagerWindowJob::setHighlightUrls(const QList<QUrl> &highlightUrls)
0094 {
0095     d->highlightUrls = highlightUrls;
0096 }
0097 
0098 QByteArray OpenFileManagerWindowJob::startupId() const
0099 {
0100     return d->startupId;
0101 }
0102 
0103 void OpenFileManagerWindowJob::setStartupId(const QByteArray &startupId)
0104 {
0105     d->startupId = startupId;
0106 }
0107 
0108 void OpenFileManagerWindowJob::start()
0109 {
0110     if (d->highlightUrls.isEmpty()) {
0111         setError(NoValidUrlsError);
0112         emitResult();
0113         return;
0114     }
0115 
0116     d->strategy->start(d->highlightUrls, d->startupId);
0117 }
0118 
0119 OpenFileManagerWindowJob *highlightInFileManager(const QList<QUrl> &urls, const QByteArray &asn)
0120 {
0121     auto *job = new OpenFileManagerWindowJob();
0122     job->setHighlightUrls(urls);
0123     job->setStartupId(asn);
0124     job->start();
0125 
0126     return job;
0127 }
0128 
0129 #if USE_DBUS
0130 void OpenFileManagerWindowDBusStrategy::start(const QList<QUrl> &urls, const QByteArray &asn)
0131 {
0132     // see the spec at: https://www.freedesktop.org/wiki/Specifications/file-manager-interface/
0133 
0134     auto runWithToken = [this, urls](const QByteArray &asn) {
0135         QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.FileManager1"),
0136                                                           QStringLiteral("/org/freedesktop/FileManager1"),
0137                                                           QStringLiteral("org.freedesktop.FileManager1"),
0138                                                           QStringLiteral("ShowItems"));
0139 
0140         msg << QUrl::toStringList(urls) << QString::fromUtf8(asn);
0141 
0142         QDBusPendingReply<void> reply = QDBusConnection::sessionBus().asyncCall(msg);
0143         QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, m_job);
0144         QObject::connect(watcher, &QDBusPendingCallWatcher::finished, m_job, [=](QDBusPendingCallWatcher *watcher) {
0145             QDBusPendingReply<void> reply = *watcher;
0146             watcher->deleteLater();
0147 
0148             if (reply.isError()) {
0149                 // Try the KRun strategy as fallback, also calls emitResult inside
0150                 m_job->d->createKRunStrategy();
0151                 m_job->d->strategy->start(urls, asn);
0152                 return;
0153             }
0154 
0155             emitResultProxy();
0156         });
0157     };
0158 
0159     if (asn.isEmpty()) {
0160 #if HAVE_WAYLAND
0161         if (KWindowSystem::isPlatformWayland()) {
0162             auto window = qGuiApp->focusWindow();
0163             if (!window && !qGuiApp->allWindows().isEmpty()) {
0164                 window = qGuiApp->allWindows().constFirst();
0165             }
0166             const int launchedSerial = KWaylandExtras::lastInputSerial(window);
0167             QObject::connect(
0168                 KWaylandExtras::self(),
0169                 &KWaylandExtras::xdgActivationTokenArrived,
0170                 m_job,
0171                 [launchedSerial, runWithToken](int serial, const QString &token) {
0172                     if (serial == launchedSerial) {
0173                         runWithToken(token.toUtf8());
0174                     }
0175                 },
0176                 Qt::SingleShotConnection);
0177             KWaylandExtras::requestXdgActivationToken(window, launchedSerial, {});
0178         } else {
0179             runWithToken({});
0180         }
0181 #else
0182         runWithToken({});
0183 #endif
0184     } else {
0185         runWithToken(asn);
0186     }
0187 }
0188 #endif
0189 
0190 void OpenFileManagerWindowKRunStrategy::start(const QList<QUrl> &urls, const QByteArray &asn)
0191 {
0192     KIO::OpenUrlJob *urlJob = new KIO::OpenUrlJob(urls.at(0).adjusted(QUrl::RemoveFilename), QStringLiteral("inode/directory"));
0193     urlJob->setUiDelegate(m_job->uiDelegate());
0194     urlJob->setStartupId(asn);
0195     QObject::connect(urlJob, &KJob::result, m_job, [this](KJob *urlJob) {
0196         if (urlJob->error()) {
0197             emitResultProxy(OpenFileManagerWindowJob::LaunchFailedError);
0198         } else {
0199             emitResultProxy();
0200         }
0201     });
0202     urlJob->start();
0203 }
0204 
0205 #if defined(Q_OS_WINDOWS)
0206 void OpenFileManagerWindowWindowsShellStrategy::start(const QList<QUrl> &urls, const QByteArray &asn)
0207 {
0208     Q_UNUSED(asn);
0209     LPITEMIDLIST dir = ILCreateFromPathW(QDir::toNativeSeparators(urls.at(0).adjusted(QUrl::RemoveFilename).toLocalFile()).toStdWString().data());
0210 
0211     std::vector<LPCITEMIDLIST> items;
0212     for (const auto &url : urls) {
0213         LPITEMIDLIST item = ILCreateFromPathW(QDir::toNativeSeparators(url.toLocalFile()).toStdWString().data());
0214         items.push_back(item);
0215     }
0216 
0217     auto result = SHOpenFolderAndSelectItems(dir, items.size(), items.data(), 0);
0218     if (SUCCEEDED(result)) {
0219         emitResultProxy();
0220     } else {
0221         emitResultProxy(OpenFileManagerWindowJob::LaunchFailedError);
0222     }
0223     ILFree(dir);
0224     for (auto &item : items) {
0225         ILFree(const_cast<LPITEMIDLIST>(item));
0226     }
0227 }
0228 #endif
0229 } // namespace KIO
0230 
0231 #include "moc_openfilemanagerwindowjob.cpp"