Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qgeopositioninfosource_geoclue2.cpp
Go to the documentation of this file.
1// Copyright (C) 2018 Denis Shienkov <denis.shienkov@gmail.com>
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QtCore/QLoggingCategory>
7#include <QtCore/QSaveFile>
8#include <QtCore/QScopedPointer>
9#include <QtCore/QTimer>
10#include <QtDBus/QDBusPendingCallWatcher>
11
12// Auto-generated D-Bus files.
13#include <client_interface.h>
14#include "moc_client_interface.cpp" // includemocs
15#include <location_interface.h>
16#include "moc_location_interface.cpp" // includemocs
17#include "moc_manager_interface.cpp" // includemocs
18
19Q_DECLARE_LOGGING_CATEGORY(lcPositioningGeoclue2)
20
22
23namespace {
24
25// NOTE: Copied from the /usr/include/libgeoclue-2.0/gclue-client.h
34
35const char GEOCLUE2_SERVICE_NAME[] = "org.freedesktop.GeoClue2";
36const int MINIMUM_UPDATE_INTERVAL = 1000;
37const int UPDATE_TIMEOUT_COLD_START = 120000;
38static const auto desktopIdParameter = "desktopId";
39
45
46} // namespace
47
49 QObject *parent)
51 , m_requestTimer(new QTimer(this))
52 , m_manager(QLatin1String(GEOCLUE2_SERVICE_NAME),
53 QStringLiteral("/org/freedesktop/GeoClue2/Manager"),
54 QDBusConnection::systemBus(),
55 this)
56{
57 parseParameters(parameters);
58
59 qDBusRegisterMetaType<Timestamp>();
60
61 restoreLastPosition();
62
63 m_requestTimer->setSingleShot(true);
64 connect(m_requestTimer, &QTimer::timeout,
65 this, &QGeoPositionInfoSourceGeoclue2::requestUpdateTimeout);
66}
67
72
78
79QGeoPositionInfo QGeoPositionInfoSourceGeoclue2::lastKnownPosition(bool fromSatellitePositioningMethodsOnly) const
80{
81 if (fromSatellitePositioningMethodsOnly && !m_lastPositionFromSatellite)
82 return QGeoPositionInfo();
83 return m_lastPosition;
84}
85
86QGeoPositionInfoSourceGeoclue2::PositioningMethods QGeoPositionInfoSourceGeoclue2::supportedPositioningMethods() const
87{
88 bool ok;
89 const auto accuracy = m_manager.property("AvailableAccuracyLevel").toUInt(&ok);
90 if (!ok) {
91 const_cast<QGeoPositionInfoSourceGeoclue2 *>(this)->setError(AccessError);
93 }
94
95 switch (accuracy) {
96 case GCLUE_ACCURACY_LEVEL_COUNTRY:
97 case GCLUE_ACCURACY_LEVEL_CITY:
98 case GCLUE_ACCURACY_LEVEL_NEIGHBORHOOD:
99 case GCLUE_ACCURACY_LEVEL_STREET:
101 case GCLUE_ACCURACY_LEVEL_EXACT:
103 case GCLUE_ACCURACY_LEVEL_NONE:
104 default:
106 }
107}
108
114
119
124
126{
127 if (m_running) {
128 qCWarning(lcPositioningGeoclue2) << "Already running";
129 return;
130 }
131
132 qCDebug(lcPositioningGeoclue2) << "Starting updates";
133
135
136 m_running = true;
137
138 startClient();
139
140 if (m_lastPosition.isValid()) {
141 QMetaObject::invokeMethod(this, "positionUpdated", Qt::QueuedConnection,
142 Q_ARG(QGeoPositionInfo, m_lastPosition));
143 }
144}
145
147{
148 if (!m_running) {
149 qCWarning(lcPositioningGeoclue2) << "Already stopped";
150 return;
151 }
152
153 qCDebug(lcPositioningGeoclue2) << "Stopping updates";
154 m_running = false;
155
156 stopClient();
157}
158
160{
161 if (m_requestTimer->isActive()) {
162 qCDebug(lcPositioningGeoclue2) << "Request timer was active, ignoring startUpdates";
163 return;
164 }
165
167
168 if (timeout < minimumUpdateInterval() && timeout != 0) {
170 return;
171 }
172
173 m_requestTimer->start(timeout ? timeout : UPDATE_TIMEOUT_COLD_START);
174 startClient();
175}
176
177void QGeoPositionInfoSourceGeoclue2::setError(QGeoPositionInfoSource::Error error)
178{
179 m_error = error;
180 if (m_error != QGeoPositionInfoSource::NoError)
182}
183
184void QGeoPositionInfoSourceGeoclue2::restoreLastPosition()
185{
186#if !defined(QT_NO_DATASTREAM)
187 const auto filePath = lastPositionFilePath();
188 QFile file(filePath);
191 out >> m_lastPosition;
192 }
193#endif
194}
195
196void QGeoPositionInfoSourceGeoclue2::saveLastPosition()
197{
198#if !defined(QT_NO_DATASTREAM) && QT_CONFIG(temporaryfile)
199 if (!m_lastPosition.isValid())
200 return;
201
202 const auto filePath = lastPositionFilePath();
203 QSaveFile file(filePath);
206 // Only save position and timestamp.
207 out << QGeoPositionInfo(m_lastPosition.coordinate(), m_lastPosition.timestamp());
208 file.commit();
209 }
210#endif
211}
212
213void QGeoPositionInfoSourceGeoclue2::createClient()
214{
215 const QDBusPendingReply<QDBusObjectPath> reply = m_manager.GetClient();
216 const auto watcher = new QDBusPendingCallWatcher(reply, this);
219 watcher->deleteLater();
220 const QDBusPendingReply<QDBusObjectPath> reply = *watcher;
221 if (reply.isError()) {
222 const auto error = reply.error();
223 qCWarning(lcPositioningGeoclue2) << "Unable to obtain the client:"
224 << error.name() << error.message();
225 setError(AccessError);
226 } else {
227 const QString clientPath = reply.value().path();
228 qCDebug(lcPositioningGeoclue2) << "Client path is:"
229 << clientPath;
230 delete m_client;
231 m_client = new OrgFreedesktopGeoClue2ClientInterface(
232 QLatin1String(GEOCLUE2_SERVICE_NAME),
233 clientPath,
234 QDBusConnection::systemBus(),
235 this);
236 if (!m_client->isValid()) {
237 const auto error = m_client->lastError();
238 qCCritical(lcPositioningGeoclue2) << "Unable to create the client object:"
239 << error.name() << error.message();
240 delete m_client;
241 setError(AccessError);
242 } else {
243 connect(m_client.data(), &OrgFreedesktopGeoClue2ClientInterface::LocationUpdated,
244 this, &QGeoPositionInfoSourceGeoclue2::handleNewLocation);
245
246 if (configureClient())
247 startClient();
248 }
249 }
250 });
251}
252
253void QGeoPositionInfoSourceGeoclue2::startClient()
254{
255 // only start the client if someone asked for it already
256 if (!m_running && !m_requestTimer->isActive())
257 return;
258
259 if (!m_client) {
260 createClient();
261 return;
262 }
263
264 const QDBusPendingReply<> reply = m_client->Start();
265 const auto watcher = new QDBusPendingCallWatcher(reply, this);
268 watcher->deleteLater();
270 if (reply.isError()) {
271 const auto error = reply.error();
272 qCCritical(lcPositioningGeoclue2) << "Unable to start the client:"
273 << error.name() << error.message();
274 delete m_client;
275 // This can potentially lead to calling ~QGeoPositionInfoSourceGeoclue2(),
276 // so do all the cleanup before.
277 setError(AccessError);
278 } else {
279 qCDebug(lcPositioningGeoclue2) << "Client successfully started";
280
281 const QDBusObjectPath location = m_client->location();
282 const QString path = location.path();
283 if (path.isEmpty() || path == QLatin1String("/"))
284 return;
285
286 handleNewLocation({}, location);
287 }
288 });
289}
290
291void QGeoPositionInfoSourceGeoclue2::stopClient()
292{
293 // Only stop client if updates are no longer wanted.
294 if (m_requestTimer->isActive() || m_running || !m_client)
295 return;
296
297 const QDBusPendingReply<> reply = m_client->Stop();
298 const auto watcher = new QDBusPendingCallWatcher(reply, this);
301 watcher->deleteLater();
303 if (reply.isError()) {
304 const auto error = reply.error();
305 qCCritical(lcPositioningGeoclue2) << "Unable to stop the client:"
306 << error.name() << error.message();
307 setError(AccessError);
308 } else {
309 qCDebug(lcPositioningGeoclue2) << "Client successfully stopped";
310 }
311 delete m_client;
312 });
313}
314
315bool QGeoPositionInfoSourceGeoclue2::configureClient()
316{
317 if (!m_client)
318 return false;
319
320 if (m_desktopId.isEmpty()) {
321 qCCritical(lcPositioningGeoclue2)
322 << "Unable to configure the client due to the desktop id is not set via"
323 << desktopIdParameter << "plugin parameter or QCoreApplication::applicationName";
324 setError(AccessError);
325 return false;
326 }
327
328 m_client->setDesktopId(m_desktopId);
329
330 const auto msecs = updateInterval();
331 const uint secs = qMax(uint(msecs), 0u) / 1000u;
332 m_client->setTimeThreshold(secs);
333
335 switch (methods) {
337 m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_EXACT);
338 break;
340 m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_STREET);
341 break;
343 m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_EXACT);
344 break;
345 default:
346 m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_NONE);
347 break;
348 }
349
350 return true;
351}
352
353void QGeoPositionInfoSourceGeoclue2::requestUpdateTimeout()
354{
355 qCDebug(lcPositioningGeoclue2) << "Request update timeout occurred";
356
358
359 stopClient();
360}
361
362void QGeoPositionInfoSourceGeoclue2::handleNewLocation(const QDBusObjectPath &oldLocation,
363 const QDBusObjectPath &newLocation)
364{
365 if (m_requestTimer->isActive())
366 m_requestTimer->stop();
367
368 const auto oldPath = oldLocation.path();
369 const auto newPath = newLocation.path();
370 qCDebug(lcPositioningGeoclue2) << "Old location object path:" << oldPath;
371 qCDebug(lcPositioningGeoclue2) << "New location object path:" << newPath;
372
373 OrgFreedesktopGeoClue2LocationInterface location(
374 QLatin1String(GEOCLUE2_SERVICE_NAME),
375 newPath,
377 this);
378 if (!location.isValid()) {
379 const auto error = location.lastError();
380 qCCritical(lcPositioningGeoclue2) << "Unable to create the location object:"
381 << error.name() << error.message();
382 } else {
383 QGeoCoordinate coordinate(location.latitude(),
384 location.longitude());
385 const auto altitude = location.altitude();
386 if (altitude > std::numeric_limits<double>::lowest())
387 coordinate.setAltitude(altitude);
388
389 const Timestamp ts = location.timestamp();
390 if (ts.m_seconds == 0 && ts.m_microseconds == 0) {
391 const auto dt = QDateTime::currentDateTime();
392 m_lastPosition = QGeoPositionInfo(coordinate, dt);
393 } else {
395 dt = dt.addMSecs(ts.m_microseconds / 1000);
396 m_lastPosition = QGeoPositionInfo(coordinate, dt);
397 }
398
399 const auto accuracy = location.accuracy();
400 // We assume that an accuracy as 0.0 means that it comes from a sattelite.
401 m_lastPositionFromSatellite = qFuzzyCompare(accuracy, 0.0);
402
403 m_lastPosition.setAttribute(QGeoPositionInfo::HorizontalAccuracy, accuracy);
404 const auto speed = location.speed();
405 if (speed >= 0.0)
406 m_lastPosition.setAttribute(QGeoPositionInfo::GroundSpeed, speed);
407 const auto heading = location.heading();
408 if (heading >= 0.0)
409 m_lastPosition.setAttribute(QGeoPositionInfo::Direction, heading);
410
411 emit positionUpdated(m_lastPosition);
412 qCDebug(lcPositioningGeoclue2) << "New position:" << m_lastPosition;
413 }
414
415 stopClient();
416}
417
418void QGeoPositionInfoSourceGeoclue2::parseParameters(const QVariantMap &parameters)
419{
420 if (parameters.contains(desktopIdParameter))
421 m_desktopId = parameters.value(desktopIdParameter).toString();
422
423 if (m_desktopId.isEmpty())
424 m_desktopId = QCoreApplication::applicationName();
425}
426
428
429#include "moc_qgeopositioninfosource_geoclue2_p.cpp"
static JNINativeMethod methods[]
QString applicationName
the name of this application
\inmodule QtDBus
static QDBusConnection systemBus()
Returns a QDBusConnection object opened with the system bus.
\inmodule QtDBus
void finished(QDBusPendingCallWatcher *self=nullptr)
This signal is emitted when the pending call has finished and its reply is available.
\inmodule QtCore\reentrant
Definition qdatastream.h:46
static QDateTime currentDateTime()
This is an overloaded member function, provided for convenience. It differs from the above function o...
static QDateTime fromSecsSinceEpoch(qint64 secs, const QTimeZone &timeZone)
\inmodule QtCore
Definition qfile.h:93
QFILE_MAYBE_NODISCARD bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:904
\inmodule QtPositioning
void startUpdates() override
Starts emitting updates at regular intervals as specified by setUpdateInterval().
PositioningMethods supportedPositioningMethods() const override
Returns the positioning methods available to this source.
Error error() const override
Returns the type of error that last occurred.
void requestUpdate(int timeout=5000) override
Attempts to get the current position and emit positionUpdated() with this information.
QGeoPositionInfo lastKnownPosition(bool fromSatellitePositioningMethodsOnly=false) const override
Returns an update containing the last known position, or a null update if none is available.
void stopUpdates() override
Stops emitting updates at regular intervals.
void setPreferredPositioningMethods(PositioningMethods methods) override
QGeoPositionInfoSourceGeoclue2(const QVariantMap &parameters, QObject *parent=nullptr)
\inmodule QtPositioning
int updateInterval
This property holds the requested interval in milliseconds between each update.
void positionUpdated(const QGeoPositionInfo &update)
If startUpdates() or requestUpdate() is called, this signal is emitted when an update becomes availab...
void errorOccurred(QGeoPositionInfoSource::Error)
This signal is emitted after an error occurred.
virtual void setPreferredPositioningMethods(PositioningMethods methods)
Error
The Error enumeration represents the errors which can occur.
virtual void setUpdateInterval(int msec)
PositioningMethods preferredPositioningMethods
Sets the preferred positioning methods for this source.
\inmodule QtPositioning
bool isValid() const
Returns true if the timestamp() and coordinate() values are both valid.
QGeoCoordinate coordinate() const
Returns the coordinate for this position.
void setAttribute(Attribute attribute, qreal value)
Sets the value for attribute to value.
QDateTime timestamp() const
Returns the date and time at which this position was reported, in UTC time.
T value(const Key &key, const T &defaultValue=T()) const
Definition qmap.h:357
bool contains(const Key &key) const
Definition qmap.h:341
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
static QString writableLocation(StandardLocation type)
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
\inmodule QtCore
Definition qtimer.h:20
void setSingleShot(bool singleShot)
Definition qtimer.cpp:552
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:241
bool isActive() const
Returns true if the timer is running (pending); otherwise returns false.
Definition qtimer.cpp:167
void stop()
Stops the timer.
Definition qtimer.cpp:267
void timeout(QPrivateSignal)
This signal is emitted when the timer times out.
QString toString() const
Returns the variant as a QString if the variant has a userType() including, but not limited to:
quint64 m_microseconds
quint64 m_seconds
#define this
Definition dialogs.cpp:9
Combined button and popup list for selecting options.
@ QueuedConnection
DBusConnection const char DBusError * error
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:333
#define MINIMUM_UPDATE_INTERVAL
#define UPDATE_TIMEOUT_COLD_START
#define qCCritical(category,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
#define Q_DECLARE_LOGGING_CATEGORY(name)
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
#define Q_ARG(Type, data)
Definition qobjectdefs.h:63
GLint location
GLbitfield GLuint64 timeout
[4]
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define QStringLiteral(str)
#define emit
unsigned int uint
Definition qtypes.h:34
long long qint64
Definition qtypes.h:60
QFutureWatcher< int > watcher
QFile file
[0]
QTextStream out(stdout)
[7]
QNetworkReply * reply
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...