1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2016 Kai Uwe Broulik <kde@privat.broulik.de>
4 SPDX-FileCopyrightText: 2023 g10 Code GmbH
5 SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "openfilemanagerwindowjob.h"
11#include "openfilemanagerwindowjob_p.h"
12
13#ifdef WITH_QTDBUS
14#include <QDBusConnection>
15#include <QDBusMessage>
16#include <QDBusPendingCallWatcher>
17#include <QDBusPendingReply>
18#endif
19#if defined(Q_OS_WINDOWS)
20#include <QDir>
21#include <shlobj.h>
22#include <vector>
23#endif
24#include <QGuiApplication>
25
26#include <KWindowSystem>
27
28#include "config-kiogui.h"
29#if HAVE_WAYLAND
30#include <KWaylandExtras>
31#endif
32
33#include <KIO/OpenUrlJob>
34
35namespace KIO
36{
37class OpenFileManagerWindowJobPrivate
38{
39public:
40 OpenFileManagerWindowJobPrivate(OpenFileManagerWindowJob *qq)
41 : q(qq)
42 , strategy(nullptr)
43 {
44 }
45
46 ~OpenFileManagerWindowJobPrivate() = default;
47
48#ifdef WITH_QTDBUS
49 void createDBusStrategy()
50 {
51 strategy = std::make_unique<OpenFileManagerWindowDBusStrategy>();
52 }
53#endif
54#if defined(Q_OS_WINDOWS)
55 void createWindowsShellStrategy()
56 {
57 strategy = std::make_unique<OpenFileManagerWindowWindowsShellStrategy>();
58 }
59#endif
60
61 void createKRunStrategy()
62 {
63 strategy = std::make_unique<OpenFileManagerWindowKRunStrategy>(args: q);
64 }
65
66 OpenFileManagerWindowJob *const q;
67 QList<QUrl> highlightUrls;
68 QByteArray startupId;
69
70 std::unique_ptr<AbstractOpenFileManagerWindowStrategy> strategy;
71};
72
73OpenFileManagerWindowJob::OpenFileManagerWindowJob(QObject *parent)
74 : KJob(parent)
75 , d(new OpenFileManagerWindowJobPrivate(this))
76{
77#ifdef WITH_QTDBUS
78 d->createDBusStrategy();
79#elif defined(Q_OS_WINDOWS)
80 d->createWindowsShellStrategy();
81#else
82 d->createKRunStrategy();
83#endif
84
85 connect(sender: d->strategy.get(), signal: &AbstractOpenFileManagerWindowStrategy::finished, context: this, slot: [this](int result) {
86 if (result == KJob::NoError) {
87 emitResult();
88 } else {
89#ifdef WITH_QTDBUS
90 // DBus strategy failed, fall back to KRun strategy
91 d->strategy = std::make_unique<OpenFileManagerWindowKRunStrategy>(args: this);
92 d->strategy->start(urls: d->highlightUrls, asn: d->startupId);
93
94 connect(sender: d->strategy.get(), signal: &KIO::AbstractOpenFileManagerWindowStrategy::finished, context: this, slot: [this](int result) {
95 setError(result);
96 emitResult();
97 });
98#else
99 setError(result);
100 emitResult();
101#endif
102 }
103 });
104}
105
106OpenFileManagerWindowJob::~OpenFileManagerWindowJob() = default;
107
108QList<QUrl> OpenFileManagerWindowJob::highlightUrls() const
109{
110 return d->highlightUrls;
111}
112
113void OpenFileManagerWindowJob::setHighlightUrls(const QList<QUrl> &highlightUrls)
114{
115 d->highlightUrls = highlightUrls;
116}
117
118QByteArray OpenFileManagerWindowJob::startupId() const
119{
120 return d->startupId;
121}
122
123void OpenFileManagerWindowJob::setStartupId(const QByteArray &startupId)
124{
125 d->startupId = startupId;
126}
127
128void OpenFileManagerWindowJob::start()
129{
130 if (d->highlightUrls.isEmpty()) {
131 setError(NoValidUrlsError);
132 emitResult();
133 return;
134 }
135
136 d->strategy->start(urls: d->highlightUrls, asn: d->startupId);
137}
138
139OpenFileManagerWindowJob *highlightInFileManager(const QList<QUrl> &urls, const QByteArray &asn)
140{
141 auto *job = new OpenFileManagerWindowJob();
142 job->setHighlightUrls(urls);
143 job->setStartupId(asn);
144 job->start();
145
146 return job;
147}
148
149#ifdef WITH_QTDBUS
150void OpenFileManagerWindowDBusStrategy::start(const QList<QUrl> &urls, const QByteArray &asn)
151{
152 // see the spec at: https://www.freedesktop.org/wiki/Specifications/file-manager-interface/
153
154 auto runWithToken = [this, urls](const QByteArray &asn) {
155 QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.FileManager1"),
156 QStringLiteral("/org/freedesktop/FileManager1"),
157 QStringLiteral("org.freedesktop.FileManager1"),
158 QStringLiteral("ShowItems"));
159
160 msg << QUrl::toStringList(uris: urls) << QString::fromUtf8(ba: asn);
161
162 QDBusPendingReply<void> reply = QDBusConnection::sessionBus().asyncCall(message: msg);
163 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
164 QObject::connect(sender: watcher, signal: &QDBusPendingCallWatcher::finished, context: this, slot: [urls, asn, this](QDBusPendingCallWatcher *watcher) {
165 QDBusPendingReply<void> reply = *watcher;
166 watcher->deleteLater();
167
168 Q_EMIT finished(error: reply.isError() ? KJob::UserDefinedError : KJob::NoError);
169 });
170 };
171
172 if (asn.isEmpty()) {
173#if HAVE_WAYLAND
174 if (KWindowSystem::isPlatformWayland()) {
175 auto window = qGuiApp->focusWindow();
176 if (!window && !qGuiApp->allWindows().isEmpty()) {
177 window = qGuiApp->allWindows().constFirst();
178 }
179 const int launchedSerial = KWaylandExtras::lastInputSerial(window);
180 QObject::connect(
181 sender: KWaylandExtras::self(),
182 signal: &KWaylandExtras::xdgActivationTokenArrived,
183 context: this,
184 slot: [launchedSerial, runWithToken](int serial, const QString &token) {
185 if (serial == launchedSerial) {
186 runWithToken(token.toUtf8());
187 }
188 },
189 type: Qt::SingleShotConnection);
190 KWaylandExtras::requestXdgActivationToken(win: window, serial: launchedSerial, app_id: {});
191 } else {
192 runWithToken({});
193 }
194#else
195 runWithToken({});
196#endif
197 } else {
198 runWithToken(asn);
199 }
200}
201#endif
202
203void OpenFileManagerWindowKRunStrategy::start(const QList<QUrl> &urls, const QByteArray &asn)
204{
205 KIO::OpenUrlJob *urlJob = new KIO::OpenUrlJob(urls.at(i: 0).adjusted(options: QUrl::RemoveFilename), QStringLiteral("inode/directory"));
206 urlJob->setUiDelegate(m_job->uiDelegate());
207 urlJob->setStartupId(asn);
208 QObject::connect(sender: urlJob, signal: &KJob::result, context: this, slot: [this](KJob *urlJob) {
209 if (urlJob->error()) {
210 Q_EMIT finished(error: OpenFileManagerWindowJob::LaunchFailedError);
211 } else {
212 Q_EMIT finished(error: KJob::NoError);
213 }
214 });
215 urlJob->start();
216}
217
218#if defined(Q_OS_WINDOWS)
219void OpenFileManagerWindowWindowsShellStrategy::start(const QList<QUrl> &urls, const QByteArray &asn)
220{
221 Q_UNUSED(asn);
222 LPITEMIDLIST dir = ILCreateFromPathW(QDir::toNativeSeparators(urls.at(0).adjusted(QUrl::RemoveFilename).toLocalFile()).toStdWString().data());
223
224 std::vector<LPCITEMIDLIST> items;
225 for (const auto &url : urls) {
226 LPITEMIDLIST item = ILCreateFromPathW(QDir::toNativeSeparators(url.toLocalFile()).toStdWString().data());
227 items.push_back(item);
228 }
229
230 auto result = SHOpenFolderAndSelectItems(dir, items.size(), items.data(), 0);
231 if (SUCCEEDED(result)) {
232 Q_EMIT finished(KJob::NoError);
233 } else {
234 Q_EMIT finished(OpenFileManagerWindowJob::LaunchFailedError);
235 }
236 ILFree(dir);
237 for (auto &item : items) {
238 ILFree(const_cast<LPITEMIDLIST>(item));
239 }
240}
241#endif
242} // namespace KIO
243
244#include "moc_openfilemanagerwindowjob.cpp"
245
246#include "moc_openfilemanagerwindowjob_p.cpp"
247

source code of kio/src/gui/openfilemanagerwindowjob.cpp