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#if USE_DBUS
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#if USE_DBUS
49 void createDBusStrategy()
50 {
51 strategy = std::make_unique<OpenFileManagerWindowDBusStrategy>(args: q);
52 }
53#endif
54#if defined(Q_OS_WINDOWS)
55 void createWindowsShellStrategy()
56 {
57 strategy = std::make_unique<OpenFileManagerWindowWindowsShellStrategy>(q);
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#if USE_DBUS
78 d->createDBusStrategy();
79#elif defined(Q_OS_WINDOWS)
80 d->createWindowsShellStrategy();
81#else
82 d->createKRunStrategy();
83#endif
84}
85
86OpenFileManagerWindowJob::~OpenFileManagerWindowJob() = default;
87
88QList<QUrl> OpenFileManagerWindowJob::highlightUrls() const
89{
90 return d->highlightUrls;
91}
92
93void OpenFileManagerWindowJob::setHighlightUrls(const QList<QUrl> &highlightUrls)
94{
95 d->highlightUrls = highlightUrls;
96}
97
98QByteArray OpenFileManagerWindowJob::startupId() const
99{
100 return d->startupId;
101}
102
103void OpenFileManagerWindowJob::setStartupId(const QByteArray &startupId)
104{
105 d->startupId = startupId;
106}
107
108void OpenFileManagerWindowJob::start()
109{
110 if (d->highlightUrls.isEmpty()) {
111 setError(NoValidUrlsError);
112 emitResult();
113 return;
114 }
115
116 d->strategy->start(urls: d->highlightUrls, asn: d->startupId);
117}
118
119OpenFileManagerWindowJob *highlightInFileManager(const QList<QUrl> &urls, const QByteArray &asn)
120{
121 auto *job = new OpenFileManagerWindowJob();
122 job->setHighlightUrls(urls);
123 job->setStartupId(asn);
124 job->start();
125
126 return job;
127}
128
129#if USE_DBUS
130void OpenFileManagerWindowDBusStrategy::start(const QList<QUrl> &urls, const QByteArray &asn)
131{
132 // see the spec at: https://www.freedesktop.org/wiki/Specifications/file-manager-interface/
133
134 auto runWithToken = [this, urls](const QByteArray &asn) {
135 QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.FileManager1"),
136 QStringLiteral("/org/freedesktop/FileManager1"),
137 QStringLiteral("org.freedesktop.FileManager1"),
138 QStringLiteral("ShowItems"));
139
140 msg << QUrl::toStringList(uris: urls) << QString::fromUtf8(ba: asn);
141
142 QDBusPendingReply<void> reply = QDBusConnection::sessionBus().asyncCall(message: msg);
143 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, m_job);
144 QObject::connect(sender: watcher, signal: &QDBusPendingCallWatcher::finished, context: m_job, slot: [=, this](QDBusPendingCallWatcher *watcher) {
145 QDBusPendingReply<void> reply = *watcher;
146 watcher->deleteLater();
147
148 if (reply.isError()) {
149 // Try the KRun strategy as fallback, also calls emitResult inside
150 m_job->d->createKRunStrategy();
151 m_job->d->strategy->start(urls, asn);
152 return;
153 }
154
155 emitResultProxy();
156 });
157 };
158
159 if (asn.isEmpty()) {
160#if HAVE_WAYLAND
161 if (KWindowSystem::isPlatformWayland()) {
162 auto window = qGuiApp->focusWindow();
163 if (!window && !qGuiApp->allWindows().isEmpty()) {
164 window = qGuiApp->allWindows().constFirst();
165 }
166 const int launchedSerial = KWaylandExtras::lastInputSerial(window);
167 QObject::connect(
168 sender: KWaylandExtras::self(),
169 signal: &KWaylandExtras::xdgActivationTokenArrived,
170 context: m_job,
171 slot: [launchedSerial, runWithToken](int serial, const QString &token) {
172 if (serial == launchedSerial) {
173 runWithToken(token.toUtf8());
174 }
175 },
176 type: Qt::SingleShotConnection);
177 KWaylandExtras::requestXdgActivationToken(win: window, serial: launchedSerial, app_id: {});
178 } else {
179 runWithToken({});
180 }
181#else
182 runWithToken({});
183#endif
184 } else {
185 runWithToken(asn);
186 }
187}
188#endif
189
190void OpenFileManagerWindowKRunStrategy::start(const QList<QUrl> &urls, const QByteArray &asn)
191{
192 KIO::OpenUrlJob *urlJob = new KIO::OpenUrlJob(urls.at(i: 0).adjusted(options: QUrl::RemoveFilename), QStringLiteral("inode/directory"));
193 urlJob->setUiDelegate(m_job->uiDelegate());
194 urlJob->setStartupId(asn);
195 QObject::connect(sender: urlJob, signal: &KJob::result, context: m_job, slot: [this](KJob *urlJob) {
196 if (urlJob->error()) {
197 emitResultProxy(error: OpenFileManagerWindowJob::LaunchFailedError);
198 } else {
199 emitResultProxy();
200 }
201 });
202 urlJob->start();
203}
204
205#if defined(Q_OS_WINDOWS)
206void OpenFileManagerWindowWindowsShellStrategy::start(const QList<QUrl> &urls, const QByteArray &asn)
207{
208 Q_UNUSED(asn);
209 LPITEMIDLIST dir = ILCreateFromPathW(QDir::toNativeSeparators(urls.at(0).adjusted(QUrl::RemoveFilename).toLocalFile()).toStdWString().data());
210
211 std::vector<LPCITEMIDLIST> items;
212 for (const auto &url : urls) {
213 LPITEMIDLIST item = ILCreateFromPathW(QDir::toNativeSeparators(url.toLocalFile()).toStdWString().data());
214 items.push_back(item);
215 }
216
217 auto result = SHOpenFolderAndSelectItems(dir, items.size(), items.data(), 0);
218 if (SUCCEEDED(result)) {
219 emitResultProxy();
220 } else {
221 emitResultProxy(OpenFileManagerWindowJob::LaunchFailedError);
222 }
223 ILFree(dir);
224 for (auto &item : items) {
225 ILFree(const_cast<LPITEMIDLIST>(item));
226 }
227}
228#endif
229} // namespace KIO
230
231#include "moc_openfilemanagerwindowjob.cpp"
232

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