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