1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qdesktopservices.h"
5
6#ifndef QT_NO_DESKTOPSERVICES
7
8#include <qdebug.h>
9
10#include <qstandardpaths.h>
11#include <qhash.h>
12#include <qobject.h>
13#include <qcoreapplication.h>
14#include <private/qguiapplication_p.h>
15#include <qurl.h>
16#include <qmutex.h>
17#include <qpa/qplatformservices.h>
18#include <qpa/qplatformintegration.h>
19#include <qdir.h>
20
21#include <QtCore/private/qlocking_p.h>
22
23QT_BEGIN_NAMESPACE
24
25class QOpenUrlHandlerRegistry
26{
27public:
28 QOpenUrlHandlerRegistry() = default;
29
30 QRecursiveMutex mutex;
31
32 struct Handler
33 {
34 QObject *receiver;
35 QByteArray name;
36 };
37 typedef QHash<QString, Handler> HandlerHash;
38 HandlerHash handlers;
39
40#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
41 QObject context;
42
43 void handlerDestroyed(QObject *handler);
44#endif
45
46};
47
48Q_GLOBAL_STATIC(QOpenUrlHandlerRegistry, handlerRegistry)
49
50#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
51void QOpenUrlHandlerRegistry::handlerDestroyed(QObject *handler)
52{
53 const auto lock = qt_scoped_lock(mutex);
54 HandlerHash::Iterator it = handlers.begin();
55 while (it != handlers.end()) {
56 if (it->receiver == handler) {
57 it = handlers.erase(it);
58 qWarning("Please call QDesktopServices::unsetUrlHandler() before destroying a "
59 "registered URL handler object.\n"
60 "Support for destroying a registered URL handler object is deprecated, "
61 "and will be removed in Qt 6.6.");
62 } else {
63 ++it;
64 }
65 }
66}
67#endif
68
69/*!
70 \class QDesktopServices
71 \brief The QDesktopServices class provides methods for accessing common desktop services.
72 \since 4.2
73 \ingroup desktop
74 \inmodule QtGui
75
76 Many desktop environments provide services that can be used by applications to
77 perform common tasks, such as opening a web page, in a way that is both consistent
78 and takes into account the user's application preferences.
79
80 This class contains functions that provide simple interfaces to these services
81 that indicate whether they succeeded or failed.
82
83 The openUrl() function is used to open files located at arbitrary URLs in external
84 applications. For URLs that correspond to resources on the local filing system
85 (where the URL scheme is "file"), a suitable application will be used to open the
86 file; otherwise, a web browser will be used to fetch and display the file.
87
88 The user's desktop settings control whether certain executable file types are
89 opened for browsing, or if they are executed instead. Some desktop environments
90 are configured to prevent users from executing files obtained from non-local URLs,
91 or to ask the user's permission before doing so.
92
93 \section1 URL Handlers
94
95 The behavior of the openUrl() function can be customized for individual URL
96 schemes to allow applications to override the default handling behavior for
97 certain types of URLs.
98
99 The dispatch mechanism allows only one custom handler to be used for each URL
100 scheme; this is set using the setUrlHandler() function. Each handler is
101 implemented as a slot which accepts only a single QUrl argument.
102
103 The existing handlers for each scheme can be removed with the
104 unsetUrlHandler() function. This returns the handling behavior for the given
105 scheme to the default behavior.
106
107 This system makes it easy to implement a help system, for example. Help could be
108 provided in labels and text browsers using \uicontrol{help://myapplication/mytopic}
109 URLs, and by registering a handler it becomes possible to display the help text
110 inside the application:
111
112 \snippet code/src_gui_util_qdesktopservices.cpp 0
113 \snippet code/src_gui_util_qdesktopservices.cpp setUrlHandler
114
115 If inside the handler you decide that you can't open the requested
116 URL, you can just call QDesktopServices::openUrl() again with the
117 same argument, and it will try to open the URL using the
118 appropriate mechanism for the user's desktop environment.
119
120 Combined with platform specific settings, the schemes registered by the
121 openUrl() function can also be exposed to other applications, opening up
122 for application deep linking or a very basic URL-based IPC mechanism.
123
124 \sa QSystemTrayIcon, QProcess, QStandardPaths
125*/
126
127/*!
128 Opens the given \a url in the appropriate Web browser for the user's desktop
129 environment, and returns \c true if successful; otherwise returns \c false.
130
131 If the URL is a reference to a local file (i.e., the URL scheme is "file") then
132 it will be opened with a suitable application instead of a Web browser.
133
134 The following example opens a file on the Windows file system residing on a path
135 that contains spaces:
136
137 \snippet code/src_gui_util_qdesktopservices.cpp 2
138
139 If a \c mailto URL is specified, the user's e-mail client will be used to open a
140 composer window containing the options specified in the URL, similar to the way
141 \c mailto links are handled by a Web browser.
142
143 For example, the following URL contains a recipient (\c{user@foo.com}), a
144 subject (\c{Test}), and a message body (\c{Just a test}):
145
146 \snippet code/src_gui_util_qdesktopservices.cpp 1
147
148 \warning Although many e-mail clients can send attachments and are
149 Unicode-aware, the user may have configured their client without these features.
150 Also, certain e-mail clients (e.g., Lotus Notes) have problems with long URLs.
151
152 \warning A return value of \c true indicates that the application has successfully requested
153 the operating system to open the URL in an external application. The external application may
154 still fail to launch or fail to open the requested URL. This result will not be reported back
155 to the application.
156
157 \warning URLs passed to this function on iOS will not load unless their schemes are
158 listed in the \c LSApplicationQueriesSchemes key of the application's Info.plist file.
159 For more information, see the Apple Developer Documentation for
160 \l {iOS: canOpenURL:}{canOpenURL:}.
161 For example, the following lines enable URLs with the HTTPS scheme:
162
163 \snippet code/src_gui_util_qdesktopservices.cpp 3
164
165 \note For Android Nougat (SDK 24) and above, URLs with a \c file scheme
166 are opened using \l {Android: FileProvider}{FileProvider} which tries to obtain
167 a shareable \c content scheme URI first. For that reason, Qt for Android defines
168 a file provider with the authority \c ${applicationId}.qtprovider, with \c applicationId
169 being the app's package name to avoid name conflicts. For more information, also see
170 \l {Android: Setting up file sharing}{Setting up file sharing}.
171
172 \sa setUrlHandler()
173*/
174bool QDesktopServices::openUrl(const QUrl &url)
175{
176 QOpenUrlHandlerRegistry *registry = handlerRegistry();
177 QMutexLocker locker(&registry->mutex);
178 static bool insideOpenUrlHandler = false;
179
180 if (!insideOpenUrlHandler) {
181 QOpenUrlHandlerRegistry::HandlerHash::ConstIterator handler = registry->handlers.constFind(key: url.scheme());
182 if (handler != registry->handlers.constEnd()) {
183 insideOpenUrlHandler = true;
184 bool result = QMetaObject::invokeMethod(obj: handler->receiver, member: handler->name.constData(), c: Qt::DirectConnection, Q_ARG(QUrl, url));
185 insideOpenUrlHandler = false;
186 return result; // ### support bool slot return type
187 }
188 }
189 if (!url.isValid())
190 return false;
191
192 QPlatformIntegration *platformIntegration = QGuiApplicationPrivate::platformIntegration();
193 if (Q_UNLIKELY(!platformIntegration)) {
194 QCoreApplication *application = QCoreApplication::instance();
195 if (Q_UNLIKELY(!application))
196 qWarning(msg: "QDesktopServices::openUrl: Please instantiate the QGuiApplication object "
197 "first");
198 else if (Q_UNLIKELY(!qobject_cast<QGuiApplication *>(application)))
199 qWarning(msg: "QDesktopServices::openUrl: Application is not a GUI application");
200 return false;
201 }
202
203 QPlatformServices *platformServices = platformIntegration->services();
204 if (!platformServices) {
205 qWarning(msg: "The platform plugin does not support services.");
206 return false;
207 }
208 // We only use openDocument if there is no fragment for the URL to
209 // avoid it being lost when using openDocument
210 if (url.isLocalFile() && !url.hasFragment())
211 return platformServices->openDocument(url);
212 return platformServices->openUrl(url);
213}
214
215/*!
216 Sets the handler for the given \a scheme to be the handler \a method provided by
217 the \a receiver object.
218
219 This function provides a way to customize the behavior of openUrl(). If openUrl()
220 is called with a URL with the specified \a scheme then the given \a method on the
221 \a receiver object is called instead of QDesktopServices launching an external
222 application.
223
224 The provided method must be implemented as a slot that only accepts a single QUrl
225 argument.
226
227 \snippet code/src_gui_util_qdesktopservices.cpp 0
228
229 If setUrlHandler() is used to set a new handler for a scheme which already
230 has a handler, the existing handler is simply replaced with the new one.
231 Since QDesktopServices does not take ownership of handlers, no objects are
232 deleted when a handler is replaced.
233
234 Note that the handler will always be called from within the same thread that
235 calls QDesktopServices::openUrl().
236
237 You must call unsetUrlHandler() before destroying the handler object, so
238 the destruction of the handler object does not overlap with concurrent
239 invocations of openUrl() using it.
240
241 \section1 iOS
242
243 To use this function for receiving data from other apps on iOS you also need to
244 add the custom scheme to the \c CFBundleURLSchemes list in your Info.plist file:
245
246 \snippet code/src_gui_util_qdesktopservices.cpp 4
247
248 For more information, see the Apple Developer Documentation for
249 \l {iOS: Defining a Custom URL Scheme for Your App}{Defining a Custom URL Scheme for Your App}.
250 \warning It is not possible to claim support for some well known URL schemes, including http and
251 https. This is only allowed for Universal Links.
252
253 To claim support for http and https the above entry in the Info.plist file
254 is not allowed. This is only possible when you add your domain to the
255 Entitlements file:
256
257 \snippet code/src_gui_util_qdesktopservices.cpp 7
258
259 iOS will search for /.well-known/apple-app-site-association on your domain,
260 when the application is installed. If you want to listen to
261 \c{https://your.domain.com/help?topic=ABCDEF} you need to provide the following
262 content there:
263
264 \snippet code/src_gui_util_qdesktopservices.cpp 8
265
266 For more information, see the Apple Developer Documentation for
267 \l {iOS: Supporting Associated Domains}{Supporting Associated Domains}.
268
269 \section1 Android
270
271 To use this function for receiving data from other apps on Android, you
272 need to add one or more intent filter to the \c activity in your app manifest:
273
274 \snippet code/src_gui_util_qdesktopservices.cpp 9
275
276 For more information, see the Android Developer Documentation for
277 \l {Android: Create Deep Links to App Content}{Create Deep Links to App Content}.
278
279 To immediately open the corresponding content in your Android app, without
280 requiring the user to select the app, you need to verify your link. To
281 enable the verification, add an additional parameter to your intent filter:
282
283 \snippet code/src_gui_util_qdesktopservices.cpp 10
284
285 Android will look for \c{https://your.domain.com/.well-known/assetlinks.json},
286 when the application is installed. If you want to listen to
287 \c{https://your.domain.com:1337/help}, you need to provide the following
288 content there:
289
290 \snippet code/src_gui_util_qdesktopservices.cpp 11
291
292 For more information, see the Android Developer Documentation for
293 \l {Android: Verify Android App Links}{Verify Android App Links}.
294
295 \sa openUrl(), unsetUrlHandler()
296*/
297void QDesktopServices::setUrlHandler(const QString &scheme, QObject *receiver, const char *method)
298{
299 QOpenUrlHandlerRegistry *registry = handlerRegistry();
300 QMutexLocker locker(&registry->mutex);
301 if (!receiver) {
302 registry->handlers.remove(key: scheme.toLower());
303 return;
304 }
305 QOpenUrlHandlerRegistry::Handler h;
306 h.receiver = receiver;
307 h.name = method;
308 registry->handlers.insert(key: scheme.toLower(), value: h);
309#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
310 QObject::connect(receiver, &QObject::destroyed, &registry->context,
311 [registry](QObject *obj) { registry->handlerDestroyed(obj); },
312 Qt::DirectConnection);
313#endif
314}
315
316/*!
317 Removes a previously set URL handler for the specified \a scheme.
318
319 Call this function before the handler object that was registered for \a scheme
320 is destroyed, to prevent concurrent openUrl() calls from continuing to call
321 the destroyed handler object.
322
323 \sa setUrlHandler()
324*/
325void QDesktopServices::unsetUrlHandler(const QString &scheme)
326{
327 setUrlHandler(scheme, receiver: nullptr, method: nullptr);
328}
329
330QT_END_NAMESPACE
331
332#endif // QT_NO_DESKTOPSERVICES
333

source code of qtbase/src/gui/util/qdesktopservices.cpp