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 | |
35 | namespace KIO |
36 | { |
37 | class OpenFileManagerWindowJobPrivate |
38 | { |
39 | public: |
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 | |
73 | OpenFileManagerWindowJob::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 | |
106 | OpenFileManagerWindowJob::~OpenFileManagerWindowJob() = default; |
107 | |
108 | QList<QUrl> OpenFileManagerWindowJob::highlightUrls() const |
109 | { |
110 | return d->highlightUrls; |
111 | } |
112 | |
113 | void OpenFileManagerWindowJob::setHighlightUrls(const QList<QUrl> &highlightUrls) |
114 | { |
115 | d->highlightUrls = highlightUrls; |
116 | } |
117 | |
118 | QByteArray OpenFileManagerWindowJob::startupId() const |
119 | { |
120 | return d->startupId; |
121 | } |
122 | |
123 | void OpenFileManagerWindowJob::setStartupId(const QByteArray &startupId) |
124 | { |
125 | d->startupId = startupId; |
126 | } |
127 | |
128 | void 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 | |
139 | OpenFileManagerWindowJob *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 |
150 | void 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 | |
203 | void 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) |
219 | void 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 | |