diff options
Diffstat (limited to 'src')
12 files changed, 1149 insertions, 0 deletions
diff --git a/src/plugins/position/geoclue2/geoclue2.pro b/src/plugins/position/geoclue2/geoclue2.pro new file mode 100644 index 00000000..1bd129d5 --- /dev/null +++ b/src/plugins/position/geoclue2/geoclue2.pro @@ -0,0 +1,30 @@ +TARGET = qtposition_geoclue2 + +QT = core positioning dbus + +HEADERS += \ + qgeopositioninfosource_geoclue2_p.h \ + qgeopositioninfosourcefactory_geoclue2.h \ + geocluetypes.h + +SOURCES += \ + qgeopositioninfosource_geoclue2.cpp \ + qgeopositioninfosourcefactory_geoclue2.cpp \ + geocluetypes.cpp + +QDBUSXML2CPP_INTERFACE_HEADER_FLAGS += \ + "-N -i geocluetypes.h" + +DBUS_INTERFACES += \ + org.freedesktop.GeoClue2.Manager.xml \ + org.freedesktop.GeoClue2.Client.xml \ + org.freedesktop.GeoClue2.Location.xml + +INCLUDEPATH += $$QT.location.includes $$OUT_PWD + +OTHER_FILES += \ + plugin.json + +PLUGIN_TYPE = position +PLUGIN_CLASS_NAME = QGeoPositionInfoSourceFactoryGeoclue2 +load(qt_plugin) diff --git a/src/plugins/position/geoclue2/geocluetypes.cpp b/src/plugins/position/geoclue2/geocluetypes.cpp new file mode 100644 index 00000000..dec55d3c --- /dev/null +++ b/src/plugins/position/geoclue2/geocluetypes.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Denis Shienkov <denis.shienkov@gmail.com> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "geocluetypes.h" + +QT_BEGIN_NAMESPACE + +QDBusArgument &operator<<(QDBusArgument &arg, const Timestamp &ts) +{ + arg.beginStructure(); + arg << ts.m_seconds; + arg << ts.m_microseconds; + arg.endStructure(); + return arg; +} + +const QDBusArgument &operator>>(const QDBusArgument &arg, Timestamp &ts) +{ + arg.beginStructure(); + arg >> ts.m_seconds; + arg >> ts.m_microseconds; + arg.endStructure(); + return arg; +} + +QT_END_NAMESPACE diff --git a/src/plugins/position/geoclue2/geocluetypes.h b/src/plugins/position/geoclue2/geocluetypes.h new file mode 100644 index 00000000..0f3eb522 --- /dev/null +++ b/src/plugins/position/geoclue2/geocluetypes.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Denis Shienkov <denis.shienkov@gmail.com> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef GEOCLUETYPES_H +#define GEOCLUETYPES_H + +#include <QtDBus/QDBusArgument> + +class Timestamp +{ +public: + quint64 m_seconds = 0; + quint64 m_microseconds = 0; +}; + +Q_DECLARE_METATYPE(Timestamp) + +QT_BEGIN_NAMESPACE + +Q_DECLARE_TYPEINFO(Timestamp, Q_MOVABLE_TYPE); + +QDBusArgument &operator<<(QDBusArgument &arg, const Timestamp &ts); +const QDBusArgument &operator>>(const QDBusArgument &arg, Timestamp &ts); + +QT_END_NAMESPACE + +#endif // GEOCLUETYPES_H diff --git a/src/plugins/position/geoclue2/org.freedesktop.GeoClue2.Client.xml b/src/plugins/position/geoclue2/org.freedesktop.GeoClue2.Client.xml new file mode 100644 index 00000000..4a9399b7 --- /dev/null +++ b/src/plugins/position/geoclue2/org.freedesktop.GeoClue2.Client.xml @@ -0,0 +1,122 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" +"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> + +<!-- + GeoClue 2.0 Interface Specification + + Copyright 2013 Red Hat, Inc. +--> + +<node> + + <!-- + org.freedesktop.GeoClue2.Client: + @short_description: The Application-specific client API + + This is the interface you use to retrieve location information and receive + location update signals from GeoClue service. You get the client object to + use this interface on from org.freedesktop.GeoClue2.Manager.GetClient() + method. + --> + <interface name="org.freedesktop.GeoClue2.Client"> + <!-- + Location: + + Current location as path to a #org.freedesktop.GeoClue2.Location object. + Please note that this property will be set to "/" (D-Bus equivalent of + null) initially, until Geoclue finds user's location. You want to delay + reading this property until your callback to + #org.freedesktop.GeoClue2.Client::LocationUpdated signal is called for + the first time after starting the client. + --> + <property name="Location" type="o" access="read"/> + + <!-- + DistanceThreshold: + + Contains the current distance threshold in meters. This value is used + by the service when it gets new location info. If the distance moved is + below the threshold, it won't emit the LocationUpdated signal. + The default value is 0. When TimeThreshold is zero, it always emits + the signal. + --> + <property name="DistanceThreshold" type="u" access="readwrite"> + <annotation name="org.freedesktop.Accounts.DefaultValue" value="0"/> + </property> + + <!-- + TimeThreshold: + + Contains the current time threshold in seconds. This value is used + by the service when it gets new location info. If the time since the + last update is below the threshold, it won't emit the LocationUpdated + signal. The default value is 0. When TimeThreshold is zero, it always + emits the signal. + --> + <property name="TimeThreshold" type="u" access="readwrite"> + <annotation name="org.freedesktop.Accounts.DefaultValue" value="0"/> + </property> + + <!-- + DesktopId: + + The desktop file id (the basename of the desktop file). This property + must be set by applications for authorization to work. + --> + <property name="DesktopId" type="s" access="readwrite"/> + + <!-- + RequestedAccuracyLevel: + + The level of accuracy requested by client, as + <link linkend="GClueAccuracyLevel">GClueAccuracyLevel</link>. + + Please keep in mind that the actual accuracy of location information is + dependent on available hardware on your machine, external resources + and/or how much accuracy user agrees to be confortable with. + --> + <property name="RequestedAccuracyLevel" type="u" access="readwrite"/> + + <!-- + Active: + + If client is active, i-e started successfully using + org.freedesktop.GeoClue2.Client.Start() and receiving location updates. + + Please keep in mind that geoclue can at any time stop and start the + client on user (agent) request. Applications that are interested in + in these changes, should watch for changes in this property. + --> + <property name="Active" type="b" access="read"/> + + <!-- + Start: + + Start receiving events about the current location. Applications should + hook-up to #org.freedesktop.GeoClue2.Client::LocationUpdated signal + before calling this method. + --> + <method name="Start"/> + + <!-- + Stop: + + Stop receiving events about the current location. + --> + <method name="Stop"/> + + <!-- + LocationUpdated: + @old: old location as path to a #org.freedesktop.GeoClue2.Location object + @new: new location as path to a #org.freedesktop.GeoClue2.Location object + + The signal is emitted every time the location changes. + The client should set the DistanceThreshold property to control how + often this signal is emitted. + --> + <signal name="LocationUpdated"> + <arg name="oldLocation" type="o"/> + <arg name="newLocation" type="o"/> + </signal> + </interface> +</node> diff --git a/src/plugins/position/geoclue2/org.freedesktop.GeoClue2.Location.xml b/src/plugins/position/geoclue2/org.freedesktop.GeoClue2.Location.xml new file mode 100644 index 00000000..ebf2ea6b --- /dev/null +++ b/src/plugins/position/geoclue2/org.freedesktop.GeoClue2.Location.xml @@ -0,0 +1,96 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" +"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> + +<!-- + GeoClue 2.0 Interface Specification + + Copyright 2013 Red Hat, Inc. +--> + +<node> + + <!-- + org.freedesktop.GeoClue2.Location: + @short_description: The Location interface + + This is the interface you use on location objects. + --> + <interface name="org.freedesktop.GeoClue2.Location"> + + <!-- + Latitude: + + The latitude of the location, in degrees. + --> + <property name="Latitude" type="d" access="read"/> + + <!-- + Longitude: + + The longitude of the location, in degrees. + --> + <property name="Longitude" type="d" access="read"/> + + <!-- + Accuracy: + + The accuracy of the location fix, in meters. + --> + <property name="Accuracy" type="d" access="read"/> + + <!-- + Altitude: + + The altitude of the location fix, in meters. When unknown, its set to + minimum double value, -1.7976931348623157e+308. + --> + <property name="Altitude" type="d" access="read"/> + + <!-- + Speed: + + The speed in meters per second. When unknown, it's set to -1.0. + --> + <property name="Speed" type="d" access="read"/> + + <!-- + Heading: + + The heading direction in degrees with respect to North direction, in + clockwise order. That means North becomes 0 degree, East: 90 degrees, + South: 180 degrees, West: 270 degrees and so on. When unknown, + it's set to -1.0. + --> + <property name="Heading" type="d" access="read"/> + + <!-- + Description: + + A human-readable description of the location, if available. + + WARNING: Applications should not rely on this property since not all + sources provide a description. If you really need a description (or + more details) about current location, use a reverse-geocoding API, e.g + geocode-glib. + --> + <property name="Description" type="s" access="read"/> + + + <!-- + Timestamp: + + The timestamp when the location was determined, in seconds and + microseconds since the Epoch. This is the time of measurement if the + backend provided that information, otherwise the time when GeoClue + received the new location. + + Note that GeoClue can't guarantee that the timestamp will always + monotonically increase, as a backend may not respect that. + Also note that a timestamp can be very old, e.g. because of a cached + location. + --> + <property name="Timestamp" type="(tt)" access="read"> + <annotation name="org.qtproject.QtDBus.QtTypeName" value="Timestamp"/> + </property> + </interface> +</node> diff --git a/src/plugins/position/geoclue2/org.freedesktop.GeoClue2.Manager.xml b/src/plugins/position/geoclue2/org.freedesktop.GeoClue2.Manager.xml new file mode 100644 index 00000000..cf9590f6 --- /dev/null +++ b/src/plugins/position/geoclue2/org.freedesktop.GeoClue2.Manager.xml @@ -0,0 +1,60 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" +"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> + +<!-- + GeoClue 2.0 Interface Specification + + Copyright 2013 Red Hat, Inc. +--> + +<node> + + <!-- + org.freedesktop.GeoClue2.Manager: + @short_description: The GeoClue service manager + + This is the interface you use to talk to main GeoClue2 manager object at + path "/org/freedesktop/GeoClue2/Manager". The only thing you do with this + interface is to call org.freedesktop.GeoClue2.Manager.GetClient() on it + to get your application specific client object. + --> + <interface name="org.freedesktop.GeoClue2.Manager"> + <!-- + InUse: + + Whether service is currently is use by any application. + --> + <property name="InUse" type="b" access="read"/> + + <!-- + AvailableAccuracyLevel: + + The level of available accuracy, as + <link linkend="GClueAccuracyLevel">GClueAccuracyLevel</link>. + --> + <property name="AvailableAccuracyLevel" type="u" access="read"/> + + <!-- + GetClient: + @client: The path for newly created client object + + Retrieves a client object which can only be used by the calling + application only. + --> + <method name="GetClient"> + <arg name="client" type="o" direction="out"/> + </method> + + <!-- + AddAgent: + @id: The Desktop ID (excluding .desktop) of the agent + + An API for user authorization agents to register themselves. Each agent + is responsible for the user it is running as. Application developers + can and should simply ignore this API. + --> + <method name="AddAgent"> + <arg name="id" type="s" direction="in"/> + </method> + </interface> +</node> diff --git a/src/plugins/position/geoclue2/plugin.json b/src/plugins/position/geoclue2/plugin.json new file mode 100644 index 00000000..c23d40e0 --- /dev/null +++ b/src/plugins/position/geoclue2/plugin.json @@ -0,0 +1,9 @@ +{ + "Keys": ["geoclue2"], + "Provider": "geoclue2", + "Position": true, + "Satellite": false, + "Monitor": false, + "Priority": 1000, + "Testable": false +} diff --git a/src/plugins/position/geoclue2/qgeopositioninfosource_geoclue2.cpp b/src/plugins/position/geoclue2/qgeopositioninfosource_geoclue2.cpp new file mode 100644 index 00000000..1e457437 --- /dev/null +++ b/src/plugins/position/geoclue2/qgeopositioninfosource_geoclue2.cpp @@ -0,0 +1,476 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Denis Shienkov <denis.shienkov@gmail.com> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeopositioninfosource_geoclue2_p.h" + +#include <QtCore/QLoggingCategory> +#include <QtCore/QSaveFile> +#include <QtCore/QScopedPointer> +#include <QtCore/QTimer> +#include <QtDBus/QDBusPendingCallWatcher> + +// Auto-generated D-Bus files. +#include <client_interface.h> +#include <location_interface.h> +#include <manager_interface.h> + +Q_DECLARE_LOGGING_CATEGORY(lcPositioningGeoclue2) + +QT_BEGIN_NAMESPACE + +namespace { + +// NOTE: Copied from the /usr/include/libgeoclue-2.0/gclue-client.h +enum GClueAccuracyLevel { + GCLUE_ACCURACY_LEVEL_NONE = 0, + GCLUE_ACCURACY_LEVEL_COUNTRY = 1, + GCLUE_ACCURACY_LEVEL_CITY = 4, + GCLUE_ACCURACY_LEVEL_NEIGHBORHOOD = 5, + GCLUE_ACCURACY_LEVEL_STREET = 6, + GCLUE_ACCURACY_LEVEL_EXACT = 8 +}; + +const char GEOCLUE2_SERVICE_NAME[] = "org.freedesktop.GeoClue2"; +const int MINIMUM_UPDATE_INTERVAL = 1000; +const int UPDATE_TIMEOUT_COLD_START = 120000; + +static QString lastPositionFilePath() +{ + return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + + QStringLiteral("/qtposition-geoclue2"); +} + +} // namespace + +QGeoPositionInfoSourceGeoclue2::QGeoPositionInfoSourceGeoclue2(QObject *parent) + : QGeoPositionInfoSource(parent) + , m_requestTimer(new QTimer(this)) +{ + qDBusRegisterMetaType<Timestamp>(); + + restoreLastPosition(); + + m_requestTimer->setSingleShot(true); + connect(m_requestTimer, &QTimer::timeout, + this, &QGeoPositionInfoSourceGeoclue2::requestUpdateTimeout); + + const auto flags = QDBusServiceWatcher::WatchForRegistration; + const auto serviceWatcher = new QDBusServiceWatcher( + QLatin1String(GEOCLUE2_SERVICE_NAME), + QDBusConnection::systemBus(), + flags, + this); + connect(serviceWatcher, &QDBusServiceWatcher::serviceRegistered, + this, &QGeoPositionInfoSourceGeoclue2::createClient); + + if (const auto iface = QDBusConnection::systemBus().interface()) { + if (iface->isServiceRegistered(QLatin1String(GEOCLUE2_SERVICE_NAME))) + createClient(QLatin1String(GEOCLUE2_SERVICE_NAME)); + else + iface->startService(QLatin1String(GEOCLUE2_SERVICE_NAME)); + } else { + qCCritical(lcPositioningGeoclue2) << "D-Bus connection interface is not exists"; + setError(AccessError); + } +} + +QGeoPositionInfoSourceGeoclue2::~QGeoPositionInfoSourceGeoclue2() +{ + saveLastPosition(); +} + +void QGeoPositionInfoSourceGeoclue2::setUpdateInterval(int msec) +{ + QGeoPositionInfoSource::setUpdateInterval(msec); + configureClient(); +} + +QGeoPositionInfo QGeoPositionInfoSourceGeoclue2::lastKnownPosition(bool fromSatellitePositioningMethodsOnly) const +{ + if (fromSatellitePositioningMethodsOnly && !m_lastPositionFromSatellite) + return QGeoPositionInfo(); + return m_lastPosition; +} + +QGeoPositionInfoSourceGeoclue2::PositioningMethods QGeoPositionInfoSourceGeoclue2::supportedPositioningMethods() const +{ + if (m_manager) { + return m_manager->availableAccuracyLevel() == GCLUE_ACCURACY_LEVEL_EXACT + ? AllPositioningMethods : NonSatellitePositioningMethods; + } + return NoPositioningMethods; +} + +void QGeoPositionInfoSourceGeoclue2::setPreferredPositioningMethods(PositioningMethods methods) +{ + QGeoPositionInfoSource::setPreferredPositioningMethods(methods); + configureClient(); +} + +int QGeoPositionInfoSourceGeoclue2::minimumUpdateInterval() const +{ + return MINIMUM_UPDATE_INTERVAL; +} + +QGeoPositionInfoSource::Error QGeoPositionInfoSourceGeoclue2::error() const +{ + return m_error; +} + +void QGeoPositionInfoSourceGeoclue2::startUpdates() +{ + if (m_running) { + qCWarning(lcPositioningGeoclue2) << "Already running"; + return; + } + + qCDebug(lcPositioningGeoclue2) << "Starting updates"; + m_running = true; + + if (m_lastPosition.isValid()) { + QMetaObject::invokeMethod(this, "positionUpdated", Qt::QueuedConnection, + Q_ARG(QGeoPositionInfo, m_lastPosition)); + } +} + +void QGeoPositionInfoSourceGeoclue2::stopUpdates() +{ + if (!m_running) { + qCWarning(lcPositioningGeoclue2) << "Already stopped"; + return; + } + + qCDebug(lcPositioningGeoclue2) << "Stopping updates"; + m_running = false; + + // Only stop positioning if single update not requested. + if (!m_requestTimer->isActive() && m_client) + stopClient(); +} + +void QGeoPositionInfoSourceGeoclue2::requestUpdate(int timeout) +{ + if (timeout < minimumUpdateInterval() && timeout != 0) { + emit updateTimeout(); + return; + } + + if (m_requestTimer->isActive()) { + qCDebug(lcPositioningGeoclue2) << "Request timer was active, ignoring startUpdates"; + return; + } + + m_requestTimer->start(timeout ? timeout : UPDATE_TIMEOUT_COLD_START); + if (!m_running) + startClient(); +} + +void QGeoPositionInfoSourceGeoclue2::setError(QGeoPositionInfoSource::Error error) +{ + m_error = error; + emit QGeoPositionInfoSource::error(m_error); +} + +void QGeoPositionInfoSourceGeoclue2::restoreLastPosition() +{ +#if !defined(QT_NO_DATASTREAM) + const auto filePath = lastPositionFilePath(); + QFile file(filePath); + if (file.open(QIODevice::ReadOnly)) { + QDataStream out(&file); + out >> m_lastPosition; + } +#endif +} + +void QGeoPositionInfoSourceGeoclue2::saveLastPosition() +{ +#if !defined(QT_NO_DATASTREAM) && QT_CONFIG(temporaryfile) + if (!m_lastPosition.isValid()) + return; + + const auto filePath = lastPositionFilePath(); + QSaveFile file(filePath); + if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + QDataStream out(&file); + // Only save position and timestamp. + out << QGeoPositionInfo(m_lastPosition.coordinate(), m_lastPosition.timestamp()); + file.commit(); + } +#endif +} + +void QGeoPositionInfoSourceGeoclue2::createClient(const QString &service) +{ + if (service != QLatin1String(GEOCLUE2_SERVICE_NAME)) { + qCCritical(lcPositioningGeoclue2) << "Registered unexpected service:" + << service << ", expected:" + << GEOCLUE2_SERVICE_NAME; + setError(UnknownSourceError); + return; + } + + if (!m_manager) { + m_manager = new OrgFreedesktopGeoClue2ManagerInterface( + QLatin1String(GEOCLUE2_SERVICE_NAME), + QStringLiteral("/org/freedesktop/GeoClue2/Manager"), + QDBusConnection::systemBus(), + this); + } + + if (!m_manager->isValid()) { + const auto error = m_manager->lastError(); + qCCritical(lcPositioningGeoclue2) << "Unable to create the manager object:" + << error.name() << error.message(); + setError(AccessError); + delete m_manager; + m_manager = nullptr; + return; + } + + const QDBusPendingReply<QDBusObjectPath> reply = m_manager->GetClient(); + const auto watcher = new QDBusPendingCallWatcher(reply, this); + connect(watcher, &QDBusPendingCallWatcher::finished, + [this](QDBusPendingCallWatcher *watcher) { + const QScopedPointer<QDBusPendingCallWatcher, QScopedPointerDeleteLater> + scopedWatcher(watcher); + const QDBusPendingReply<QDBusObjectPath> reply = *scopedWatcher; + if (reply.isError()) { + const auto error = reply.error(); + qCWarning(lcPositioningGeoclue2) << "Unable to obtain the client patch:" + << error.name() + error.message(); + setError(AccessError); + } else { + const QString clientPath = reply.value().path(); + qCDebug(lcPositioningGeoclue2) << "Client path is:" + << clientPath; + delete m_client; + m_client = new OrgFreedesktopGeoClue2ClientInterface( + QLatin1String(GEOCLUE2_SERVICE_NAME), + clientPath, + QDBusConnection::systemBus(), + this); + if (!m_client->isValid()) { + const auto error = m_client->lastError(); + qCCritical(lcPositioningGeoclue2) << "Unable to create the client object:" + << error.name() << error.message(); + setError(AccessError); + delete m_client; + m_client = nullptr; + } else { + connect(m_client, &OrgFreedesktopGeoClue2ClientInterface::LocationUpdated, + this, &QGeoPositionInfoSourceGeoclue2::handleNewLocation); + + // only start the client if someone asked for it already + if (configureClient() && (m_running || m_requestTimer->isActive())) + startClient(); + } + } + }); +} + +void QGeoPositionInfoSourceGeoclue2::startClient() +{ + if (!m_client) { + qCWarning(lcPositioningGeoclue2) << "Unable to start the client " + "due to it is not created yet"; + return; + } + + const QDBusPendingReply<> reply = m_client->Start(); + const auto watcher = new QDBusPendingCallWatcher(reply, this); + connect(watcher, &QDBusPendingCallWatcher::finished, + [this](QDBusPendingCallWatcher *watcher) { + const QScopedPointer<QDBusPendingCallWatcher, QScopedPointerDeleteLater> + scopedWatcher(watcher); + const QDBusPendingReply<> reply = *scopedWatcher; + if (reply.isError()) { + const auto error = reply.error(); + qCCritical(lcPositioningGeoclue2) << "Unable to start the client:" + << error.name() << error.message(); + setError(AccessError); + } else { + qCDebug(lcPositioningGeoclue2) << "Client successfully started"; + + const QDBusObjectPath location = m_client->location(); + const QString path = location.path(); + if (path.isEmpty() || path == QLatin1String("/")) + return; + + handleNewLocation({}, location); + } + }); +} + +void QGeoPositionInfoSourceGeoclue2::stopClient() +{ + if (!m_client) { + qCWarning(lcPositioningGeoclue2) << "Unable to stop the client " + "due to it is not created yet"; + return; + } + + const QDBusPendingReply<> reply = m_client->Stop(); + const auto watcher = new QDBusPendingCallWatcher(reply, this); + connect(watcher, &QDBusPendingCallWatcher::finished, + [this](QDBusPendingCallWatcher *watcher) { + const QScopedPointer<QDBusPendingCallWatcher, QScopedPointerDeleteLater> + scopedWatcher(watcher); + const QDBusPendingReply<> reply = *scopedWatcher; + if (reply.isError()) { + const auto error = reply.error(); + qCCritical(lcPositioningGeoclue2) << "Unable to stop the client:" + << error.name() << error.message(); + setError(AccessError); + } else { + qCDebug(lcPositioningGeoclue2) << "Client successfully stopped"; + } + }); +} + +bool QGeoPositionInfoSourceGeoclue2::configureClient() +{ + if (!m_client) { + qCWarning(lcPositioningGeoclue2) << "Unable to configure the client " + "due to it is not created yet"; + return false; + } + + auto desktopId = QString::fromUtf8(qgetenv("QT_GEOCLUE_APP_DESKTOP_ID")); + if (desktopId.isEmpty()) + desktopId = QCoreApplication::applicationName(); + if (desktopId.isEmpty()) { + qCCritical(lcPositioningGeoclue2) << "Unable to configure the client " + "due to the application desktop id " + "is not set via QT_GEOCLUE_APP_DESKTOP_ID " + "envirorment variable or QCoreApplication::applicationName"; + setError(AccessError); + return false; + } + + m_client->setDesktopId(desktopId); + + const auto msecs = updateInterval(); + const auto secs = msecs / 1000; + m_client->setTimeThreshold(secs); + + const auto methods = preferredPositioningMethods(); + switch (methods) { + case SatellitePositioningMethods: + m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_EXACT); + break; + case NonSatellitePositioningMethods: + m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_STREET); + break; + case AllPositioningMethods: + m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_EXACT); + break; + default: + m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_NONE); + break; + } + + return true; +} + +void QGeoPositionInfoSourceGeoclue2::requestUpdateTimeout() +{ + qCDebug(lcPositioningGeoclue2) << "Request update timeout occurred"; + + emit updateTimeout(); + + if (!m_running) + stopClient(); +} + +void QGeoPositionInfoSourceGeoclue2::handleNewLocation(const QDBusObjectPath &oldLocation, + const QDBusObjectPath &newLocation) +{ + if (m_requestTimer->isActive()) + m_requestTimer->stop(); + + const auto oldPath = oldLocation.path(); + const auto newPath = newLocation.path(); + qCDebug(lcPositioningGeoclue2) << "Old location object path:" << oldPath; + qCDebug(lcPositioningGeoclue2) << "New location object path:" << newPath; + + OrgFreedesktopGeoClue2LocationInterface location( + QLatin1String(GEOCLUE2_SERVICE_NAME), + newPath, + QDBusConnection::systemBus(), + this); + if (!location.isValid()) { + const auto error = location.lastError(); + qCCritical(lcPositioningGeoclue2) << "Unable to create the location object:" + << error.name() << error.message(); + } else { + QGeoCoordinate coordinate(location.latitude(), + location.longitude()); + if (const auto altitude = location.altitude() > std::numeric_limits<double>::min()) + coordinate.setAltitude(altitude); + + const Timestamp ts = location.timestamp(); + if (ts.m_seconds == 0 && ts.m_microseconds == 0) { + const auto dt = QDateTime::currentDateTime(); + m_lastPosition = QGeoPositionInfo(coordinate, dt); + } else { + auto dt = QDateTime::fromSecsSinceEpoch(ts.m_seconds); + dt = dt.addMSecs(ts.m_microseconds / 1000); + m_lastPosition = QGeoPositionInfo(coordinate, dt); + } + + const auto accuracy = location.accuracy(); + // We assume that an accuracy as 0.0 means that it comes from a sattelite. + m_lastPositionFromSatellite = qFuzzyCompare(accuracy, 0.0); + + m_lastPosition.setAttribute(QGeoPositionInfo::HorizontalAccuracy, accuracy); + if (const auto speed = location.speed() >= 0.0) + m_lastPosition.setAttribute(QGeoPositionInfo::GroundSpeed, speed); + if (const auto heading = location.heading() >= 0.0) + m_lastPosition.setAttribute(QGeoPositionInfo::Direction, heading); + + emit positionUpdated(m_lastPosition); + qCDebug(lcPositioningGeoclue2) << "New position:" << m_lastPosition; + } + + if (!m_running) + stopClient(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/position/geoclue2/qgeopositioninfosource_geoclue2_p.h b/src/plugins/position/geoclue2/qgeopositioninfosource_geoclue2_p.h new file mode 100644 index 00000000..1ccd7290 --- /dev/null +++ b/src/plugins/position/geoclue2/qgeopositioninfosource_geoclue2_p.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Denis Shienkov <denis.shienkov@gmail.com> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOPOSITIONINFOSOURCE_GEOCLUE2_P_H +#define QGEOPOSITIONINFOSOURCE_GEOCLUE2_P_H + +#include <QtPositioning/QGeoPositionInfoSource> + +class OrgFreedesktopGeoClue2ClientInterface; +class OrgFreedesktopGeoClue2ManagerInterface; + +QT_BEGIN_NAMESPACE +class QDBusObjectPath; +class QTimer; + +class QGeoPositionInfoSourceGeoclue2 : public QGeoPositionInfoSource +{ + Q_OBJECT + +public: + explicit QGeoPositionInfoSourceGeoclue2(QObject *parent = nullptr); + ~QGeoPositionInfoSourceGeoclue2(); + + // From QGeoPositionInfoSource + void setUpdateInterval(int msec) override; + QGeoPositionInfo lastKnownPosition(bool fromSatellitePositioningMethodsOnly = false) const override; + PositioningMethods supportedPositioningMethods() const override; + void setPreferredPositioningMethods(PositioningMethods methods) override; + int minimumUpdateInterval() const override; + + Error error() const override; + + void startUpdates() override; + void stopUpdates() override; + void requestUpdate(int timeout = 5000) override; + +private: + void setError(QGeoPositionInfoSource::Error error); + void restoreLastPosition(); + void saveLastPosition(); + void createClient(const QString &service); + bool configureClient(); + void startClient(); + void stopClient(); + void requestUpdateTimeout(); + void handleNewLocation(const QDBusObjectPath &oldLocation, + const QDBusObjectPath &newLocation); + + QTimer *m_requestTimer = nullptr; + OrgFreedesktopGeoClue2ManagerInterface *m_manager = nullptr; + OrgFreedesktopGeoClue2ClientInterface *m_client = nullptr; + bool m_running = false; + bool m_lastPositionFromSatellite = false; + QGeoPositionInfoSource::Error m_error = NoError; + QGeoPositionInfo m_lastPosition; +}; + +QT_END_NAMESPACE + +#endif // QGEOPOSITIONINFOSOURCE_GEOCLUE2_P_H diff --git a/src/plugins/position/geoclue2/qgeopositioninfosourcefactory_geoclue2.cpp b/src/plugins/position/geoclue2/qgeopositioninfosourcefactory_geoclue2.cpp new file mode 100644 index 00000000..a713c31f --- /dev/null +++ b/src/plugins/position/geoclue2/qgeopositioninfosourcefactory_geoclue2.cpp @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Denis Shienkov <denis.shienkov@gmail.com> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeopositioninfosource_geoclue2_p.h" +#include "qgeopositioninfosourcefactory_geoclue2.h" + +#include <QtCore/QLoggingCategory> + +Q_LOGGING_CATEGORY(lcPositioningGeoclue2, "qt.positioning.geoclue2") + +QT_BEGIN_NAMESPACE + +QGeoPositionInfoSource *QGeoPositionInfoSourceFactoryGeoclue2::positionInfoSource(QObject *parent) +{ + return new QGeoPositionInfoSourceGeoclue2(parent); +} + +QGeoSatelliteInfoSource *QGeoPositionInfoSourceFactoryGeoclue2::satelliteInfoSource(QObject *parent) +{ + Q_UNUSED(parent) + return nullptr; +} + +QGeoAreaMonitorSource *QGeoPositionInfoSourceFactoryGeoclue2::areaMonitor(QObject *parent) +{ + Q_UNUSED(parent) + return nullptr; +} + +QT_END_NAMESPACE diff --git a/src/plugins/position/geoclue2/qgeopositioninfosourcefactory_geoclue2.h b/src/plugins/position/geoclue2/qgeopositioninfosourcefactory_geoclue2.h new file mode 100644 index 00000000..bfacd848 --- /dev/null +++ b/src/plugins/position/geoclue2/qgeopositioninfosourcefactory_geoclue2.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Denis Shienkov <denis.shienkov@gmail.com> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOPOSITIONINFOSOURCEFACTORY_GEOCLUE2_H +#define QGEOPOSITIONINFOSOURCEFACTORY_GEOCLUE2_H + +#include <QtCore/QObject> +#include <QtPositioning/QGeoPositionInfoSourceFactory> + +QT_BEGIN_NAMESPACE + +/* + Qt Positioning plugin for Geoclue. This plugin supports Geoclue version 2.x. +*/ +class QGeoPositionInfoSourceFactoryGeoclue2 : public QObject, public QGeoPositionInfoSourceFactory +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "org.qt-project.qt.position.sourcefactory/5.0" + FILE "plugin.json") + + Q_INTERFACES(QGeoPositionInfoSourceFactory) + +public: + QGeoPositionInfoSource *positionInfoSource(QObject *parent) override; + QGeoSatelliteInfoSource *satelliteInfoSource(QObject *parent) override; + QGeoAreaMonitorSource *areaMonitor(QObject *parent) override; +}; + +QT_END_NAMESPACE + +#endif // QGEOPOSITIONINFOSOURCEFACTORY_GEOCLUE2_H diff --git a/src/plugins/position/position.pro b/src/plugins/position/position.pro index b9832ff4..4453b8a3 100644 --- a/src/plugins/position/position.pro +++ b/src/plugins/position/position.pro @@ -3,6 +3,7 @@ TEMPLATE = subdirs QT_FOR_CONFIG += positioning-private qtHaveModule(dbus):SUBDIRS += geoclue +qtHaveModule(dbus):SUBDIRS += geoclue2 qtConfig(gypsy):SUBDIRS += gypsy qtConfig(winrt_geolocation):SUBDIRS += winrt qtHaveModule(simulator):SUBDIRS += simulator |