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 | |
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 | #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 | |
73 | OpenFileManagerWindowJob::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 | |
86 | OpenFileManagerWindowJob::~OpenFileManagerWindowJob() = default; |
87 | |
88 | QList<QUrl> OpenFileManagerWindowJob::highlightUrls() const |
89 | { |
90 | return d->highlightUrls; |
91 | } |
92 | |
93 | void OpenFileManagerWindowJob::setHighlightUrls(const QList<QUrl> &highlightUrls) |
94 | { |
95 | d->highlightUrls = highlightUrls; |
96 | } |
97 | |
98 | QByteArray OpenFileManagerWindowJob::startupId() const |
99 | { |
100 | return d->startupId; |
101 | } |
102 | |
103 | void OpenFileManagerWindowJob::setStartupId(const QByteArray &startupId) |
104 | { |
105 | d->startupId = startupId; |
106 | } |
107 | |
108 | void 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 | |
119 | OpenFileManagerWindowJob *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 |
130 | void 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 | |
190 | void 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) |
206 | void 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 | |