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
57#include <QtCore/private/qlocking_p.h>
58
59QT_BEGIN_NAMESPACE
60
61class QOpenUrlHandlerRegistry : public QObject
62{
63 Q_OBJECT
64public:
65 QOpenUrlHandlerRegistry() = default;
66
67 QRecursiveMutex mutex;
68
69 struct Handler
70 {
71 QObject *receiver;
72 QByteArray name;
73 };
74 typedef QHash<QString, Handler> HandlerHash;
75 HandlerHash handlers;
76
77#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
78public Q_SLOTS:
79 void handlerDestroyed(QObject *handler);
80#endif
81
82};
83
84Q_GLOBAL_STATIC(QOpenUrlHandlerRegistry, handlerRegistry)
85
86#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
87void QOpenUrlHandlerRegistry::handlerDestroyed(QObject *handler)
88{
89 const auto lock = qt_scoped_lock(mutex);
90 HandlerHash::Iterator it = handlers.begin();
91 while (it != handlers.end()) {
92 if (it->receiver == handler) {
93 it = handlers.erase(it);
94 qWarning(msg: "Please call QDesktopServices::unsetUrlHandler() before destroying a "
95 "registered URL handler object.\n"
96 "Support for destroying a registered URL handler object is deprecated, "
97 "and will be removed in Qt 6.6.");
98 } else {
99 ++it;
100 }
101 }
102}
103#endif
104
105/*!
106 \class QDesktopServices
107 \brief The QDesktopServices class provides methods for accessing common desktop services.
108 \since 4.2
109 \ingroup desktop
110 \inmodule QtGui
111
112 Many desktop environments provide services that can be used by applications to
113 perform common tasks, such as opening a web page, in a way that is both consistent
114 and takes into account the user's application preferences.
115
116 This class contains functions that provide simple interfaces to these services
117 that indicate whether they succeeded or failed.
118
119 The openUrl() function is used to open files located at arbitrary URLs in external
120 applications. For URLs that correspond to resources on the local filing system
121 (where the URL scheme is "file"), a suitable application will be used to open the
122 file; otherwise, a web browser will be used to fetch and display the file.
123
124 The user's desktop settings control whether certain executable file types are
125 opened for browsing, or if they are executed instead. Some desktop environments
126 are configured to prevent users from executing files obtained from non-local URLs,
127 or to ask the user's permission before doing so.
128
129 \section1 URL Handlers
130
131 The behavior of the openUrl() function can be customized for individual URL
132 schemes to allow applications to override the default handling behavior for
133 certain types of URLs.
134
135 The dispatch mechanism allows only one custom handler to be used for each URL
136 scheme; this is set using the setUrlHandler() function. Each handler is
137 implemented as a slot which accepts only a single QUrl argument.
138
139 The existing handlers for each scheme can be removed with the
140 unsetUrlHandler() function. This returns the handling behavior for the given
141 scheme to the default behavior.
142
143 This system makes it easy to implement a help system, for example. Help could be
144 provided in labels and text browsers using \uicontrol{help://myapplication/mytopic}
145 URLs, and by registering a handler it becomes possible to display the help text
146 inside the application:
147
148 \snippet code/src_gui_util_qdesktopservices.cpp 0
149
150 If inside the handler you decide that you can't open the requested
151 URL, you can just call QDesktopServices::openUrl() again with the
152 same argument, and it will try to open the URL using the
153 appropriate mechanism for the user's desktop environment.
154
155 Combined with platform specific settings, the schemes registered by the
156 openUrl() function can also be exposed to other applications, opening up
157 for application deep linking or a very basic URL-based IPC mechanism.
158
159 \note Since Qt 5, storageLocation() and displayName() are replaced by functionality
160 provided by the QStandardPaths class.
161
162 \sa QSystemTrayIcon, QProcess, QStandardPaths
163*/
164
165/*!
166 Opens the given \a url in the appropriate Web browser for the user's desktop
167 environment, and returns \c true if successful; otherwise returns \c false.
168
169 If the URL is a reference to a local file (i.e., the URL scheme is "file") then
170 it will be opened with a suitable application instead of a Web browser.
171
172 The following example opens a file on the Windows file system residing on a path
173 that contains spaces:
174
175 \snippet code/src_gui_util_qdesktopservices.cpp 2
176
177 If a \c mailto URL is specified, the user's e-mail client will be used to open a
178 composer window containing the options specified in the URL, similar to the way
179 \c mailto links are handled by a Web browser.
180
181 For example, the following URL contains a recipient (\c{user@foo.com}), a
182 subject (\c{Test}), and a message body (\c{Just a test}):
183
184 \snippet code/src_gui_util_qdesktopservices.cpp 1
185
186 \warning Although many e-mail clients can send attachments and are
187 Unicode-aware, the user may have configured their client without these features.
188 Also, certain e-mail clients (e.g., Lotus Notes) have problems with long URLs.
189
190 \warning A return value of \c true indicates that the application has successfully requested
191 the operating system to open the URL in an external application. The external application may
192 still fail to launch or fail to open the requested URL. This result will not be reported back
193 to the application.
194
195 \warning URLs passed to this function on iOS will not load unless their schemes are
196 listed in the \c LSApplicationQueriesSchemes key of the application's Info.plist file.
197 For more information, see the Apple Developer Documentation for
198 \l{https://developer.apple.com/documentation/uikit/uiapplication/1622952-canopenurl}{canOpenURL(_:)}.
199 For example, the following lines enable URLs with the HTTPS scheme:
200
201 \snippet code/src_gui_util_qdesktopservices.cpp 3
202
203 \sa setUrlHandler()
204*/
205bool QDesktopServices::openUrl(const QUrl &url)
206{
207 QOpenUrlHandlerRegistry *registry = handlerRegistry();
208 QMutexLocker locker(&registry->mutex);
209 static bool insideOpenUrlHandler = false;
210
211 if (!insideOpenUrlHandler) {
212 QOpenUrlHandlerRegistry::HandlerHash::ConstIterator handler = registry->handlers.constFind(akey: url.scheme());
213 if (handler != registry->handlers.constEnd()) {
214 insideOpenUrlHandler = true;
215 bool result = QMetaObject::invokeMethod(obj: handler->receiver, member: handler->name.constData(), type: Qt::DirectConnection, Q_ARG(QUrl, url));
216 insideOpenUrlHandler = false;
217 return result; // ### support bool slot return type
218 }
219 }
220 if (!url.isValid())
221 return false;
222
223 QPlatformIntegration *platformIntegration = QGuiApplicationPrivate::platformIntegration();
224 if (Q_UNLIKELY(!platformIntegration)) {
225 QCoreApplication *application = QCoreApplication::instance();
226 if (Q_UNLIKELY(!application))
227 qWarning(msg: "QDesktopServices::openUrl: Please instantiate the QGuiApplication object "
228 "first");
229 else if (Q_UNLIKELY(!qobject_cast<QGuiApplication *>(application)))
230 qWarning(msg: "QDesktopServices::openUrl: Application is not a GUI application");
231 return false;
232 }
233
234 QPlatformServices *platformServices = platformIntegration->services();
235 if (!platformServices) {
236 qWarning(msg: "The platform plugin does not support services.");
237 return false;
238 }
239 // We only use openDocument if there is no fragment for the URL to
240 // avoid it being lost when using openDocument
241 if (url.isLocalFile() && !url.hasFragment())
242 return platformServices->openDocument(url);
243 return platformServices->openUrl(url);
244}
245
246/*!
247 Sets the handler for the given \a scheme to be the handler \a method provided by
248 the \a receiver object.
249
250 This function provides a way to customize the behavior of openUrl(). If openUrl()
251 is called with a URL with the specified \a scheme then the given \a method on the
252 \a receiver object is called instead of QDesktopServices launching an external
253 application.
254
255 The provided method must be implemented as a slot that only accepts a single QUrl
256 argument.
257
258 \snippet code/src_gui_util_qdesktopservices.cpp 0
259
260 To use this function for receiving data from other apps on iOS you also need to
261 add the custom scheme to the \c CFBundleURLSchemes list in your Info.plist file:
262
263 \snippet code/src_gui_util_qdesktopservices.cpp 4
264
265 For more information, see the Apple Developer Documentation for
266 \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}.
267 \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.
268
269 To claim support for http and https the above entry in the Info.plist file
270 is not allowed. This is only possible when you add your domain to the
271 Entitlements file:
272
273 \snippet code/src_gui_util_qdesktopservices.cpp 7
274
275 iOS will search for /.well-known/apple-app-site-association on your domain,
276 when the application is installed. If you want to listen to
277 https://your.domain.com/help?topic=ABCDEF you need to provide the following
278 content there:
279
280 \snippet code/src_gui_util_qdesktopservices.cpp 8
281
282 For more information, see the Apple Developer Documentation for
283 \l{https://developer.apple.com/documentation/safariservices/supporting_associated_domains_in_your_app}[Supporting Associated Domains}.
284
285 If setUrlHandler() is used to set a new handler for a scheme which already
286 has a handler, the existing handler is simply replaced with the new one.
287 Since QDesktopServices does not take ownership of handlers, no objects are
288 deleted when a handler is replaced.
289
290 Note that the handler will always be called from within the same thread that
291 calls QDesktopServices::openUrl().
292
293 You must call unsetUrlHandler() before destroying the handler object, so
294 the destruction of the handler object does not overlap with concurrent
295 invocations of openUrl() using it.
296
297 \sa openUrl(), unsetUrlHandler()
298*/
299void QDesktopServices::setUrlHandler(const QString &scheme, QObject *receiver, const char *method)
300{
301 QOpenUrlHandlerRegistry *registry = handlerRegistry();
302 QMutexLocker locker(&registry->mutex);
303 if (!receiver) {
304 registry->handlers.remove(akey: scheme.toLower());
305 return;
306 }
307 QOpenUrlHandlerRegistry::Handler h;
308 h.receiver = receiver;
309 h.name = method;
310 registry->handlers.insert(akey: scheme.toLower(), avalue: h);
311#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
312 QObject::connect(sender: receiver, SIGNAL(destroyed(QObject*)),
313 receiver: registry, SLOT(handlerDestroyed(QObject*)),
314 Qt::DirectConnection);
315#endif
316}
317
318/*!
319 Removes a previously set URL handler for the specified \a scheme.
320
321 Call this function before the handler object that was registered for \a scheme
322 is destroyed, to prevent concurrent openUrl() calls from continuing to call
323 the destroyed handler object.
324
325 \sa setUrlHandler()
326*/
327void QDesktopServices::unsetUrlHandler(const QString &scheme)
328{
329 setUrlHandler(scheme, receiver: nullptr, method: nullptr);
330}
331
332#if QT_DEPRECATED_SINCE(5, 0)
333/*!
334 \enum QDesktopServices::StandardLocation
335 \since 4.4
336 \obsolete
337 Use QStandardPaths::StandardLocation (see storageLocation() for porting notes)
338
339 This enum describes the different locations that can be queried by
340 QDesktopServices::storageLocation and QDesktopServices::displayName.
341
342 \value DesktopLocation Returns the user's desktop directory.
343 \value DocumentsLocation Returns the user's document.
344 \value FontsLocation Returns the user's fonts.
345 \value ApplicationsLocation Returns the user's applications.
346 \value MusicLocation Returns the users music.
347 \value MoviesLocation Returns the user's movies.
348 \value PicturesLocation Returns the user's pictures.
349 \value TempLocation Returns the system's temporary directory.
350 \value HomeLocation Returns the user's home directory.
351 \value DataLocation Returns a directory location where persistent
352 application data can be stored. QCoreApplication::applicationName
353 and QCoreApplication::organizationName should work on all
354 platforms.
355 \value CacheLocation Returns a directory location where user-specific
356 non-essential (cached) data should be written.
357
358 \sa storageLocation(), displayName()
359*/
360
361/*!
362 \fn QString QDesktopServices::storageLocation(StandardLocation type)
363 \obsolete
364 Use QStandardPaths::writableLocation()
365
366 \note when porting QDesktopServices::DataLocation to QStandardPaths::DataLocation,
367 a different path will be returned.
368
369 \c{QDesktopServices::DataLocation} was \c{GenericDataLocation + "/data/organization/application"},
370 while QStandardPaths::DataLocation is \c{GenericDataLocation + "/organization/application"}.
371
372 Also note that \c{application} could be empty in Qt 4, if QCoreApplication::setApplicationName()
373 wasn't called, while in Qt 5 it defaults to the name of the executable.
374
375 Therefore, if you still need to access the Qt 4 path (for example for data migration to Qt 5), replace
376 \snippet code/src_gui_util_qdesktopservices.cpp 5
377 with
378 \snippet code/src_gui_util_qdesktopservices.cpp 6
379 (assuming an organization name and an application name were set).
380*/
381
382/*!
383 \fn QString QDesktopServices::displayName(StandardLocation type)
384 \obsolete
385 Use QStandardPaths::displayName()
386*/
387#endif
388
389extern Q_CORE_EXPORT QString qt_applicationName_noFallback();
390
391QString QDesktopServices::storageLocationImpl(QStandardPaths::StandardLocation type)
392{
393 if (type == QStandardPaths::AppLocalDataLocation) {
394 // Preserve Qt 4 compatibility:
395 // * QCoreApplication::applicationName() must default to empty
396 // * Unix data location is under the "data/" subdirectory
397 const QString compatAppName = qt_applicationName_noFallback();
398 const QString baseDir = QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation);
399 const QString organizationName = QCoreApplication::organizationName();
400#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
401 QString result = baseDir;
402 if (!organizationName.isEmpty())
403 result += QLatin1Char('/') + organizationName;
404 if (!compatAppName.isEmpty())
405 result += QLatin1Char('/') + compatAppName;
406 return result;
407#elif defined(Q_OS_UNIX)
408 return baseDir + QLatin1String("/data/")
409 + organizationName + QLatin1Char('/') + compatAppName;
410#endif
411 }
412 return QStandardPaths::writableLocation(type);
413}
414
415QT_END_NAMESPACE
416
417#include "qdesktopservices.moc"
418
419#endif // QT_NO_DESKTOPSERVICES
420

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