| 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 |  | 
| 59 | QT_BEGIN_NAMESPACE | 
| 60 |  | 
| 61 | class QOpenUrlHandlerRegistry : public QObject | 
| 62 | { | 
| 63 |     Q_OBJECT | 
| 64 | public: | 
| 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) | 
| 78 | public Q_SLOTS: | 
| 79 |     void handlerDestroyed(QObject *handler); | 
| 80 | #endif | 
| 81 |  | 
| 82 | }; | 
| 83 |  | 
| 84 | Q_GLOBAL_STATIC(QOpenUrlHandlerRegistry, handlerRegistry) | 
| 85 |  | 
| 86 | #if QT_VERSION < QT_VERSION_CHECK(6, 6, 0) | 
| 87 | void 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 | */ | 
| 205 | bool QDesktopServices::openUrl(const QUrl &url) | 
| 206 | { | 
| 207 |     QOpenUrlHandlerRegistry *registry = handlerRegistry(); | 
| 208 |     QMutexLocker locker(®istry->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 | */ | 
| 299 | void QDesktopServices::setUrlHandler(const QString &scheme, QObject *receiver, const char *method) | 
| 300 | { | 
| 301 |     QOpenUrlHandlerRegistry *registry = handlerRegistry(); | 
| 302 |     QMutexLocker locker(®istry->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 | */ | 
| 327 | void 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 |  | 
| 389 | extern Q_CORE_EXPORT QString qt_applicationName_noFallback(); | 
| 390 |  | 
| 391 | QString 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 |  | 
| 415 | QT_END_NAMESPACE | 
| 416 |  | 
| 417 | #include "qdesktopservices.moc" | 
| 418 |  | 
| 419 | #endif // QT_NO_DESKTOPSERVICES | 
| 420 |  |