Qt 6.x
The Qt SDK
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
33};
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
41{
43 + QStringLiteral("/qtposition-geoclue2");
44}
45
46} // namespace
47
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
69{
70 saveLastPosition();
71}
72
74{
76 configureClient();
77}
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
110{
112 configureClient();
113}
114
116{
118}
119
121{
122 return m_error;
123}
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);
220 scopedWatcher(watcher);
221 const QDBusPendingReply<QDBusObjectPath> reply = *scopedWatcher;
222 if (reply.isError()) {
223 const auto error = reply.error();
224 qCWarning(lcPositioningGeoclue2) << "Unable to obtain the client patch:"
225 << error.name() + error.message();
226 setError(AccessError);
227 } else {
228 const QString clientPath = reply.value().path();
229 qCDebug(lcPositioningGeoclue2) << "Client path is:"
230 << clientPath;
231 delete m_client;
232 m_client = new OrgFreedesktopGeoClue2ClientInterface(
233 QLatin1String(GEOCLUE2_SERVICE_NAME),
234 clientPath,
236 this);
237 if (!m_client->isValid()) {
238 const auto error = m_client->lastError();
239 qCCritical(lcPositioningGeoclue2) << "Unable to create the client object:"
240 << error.name() << error.message();
241 setError(AccessError);
242 delete m_client;
243 } else {
244 connect(m_client.data(), &OrgFreedesktopGeoClue2ClientInterface::LocationUpdated,
245 this, &QGeoPositionInfoSourceGeoclue2::handleNewLocation);
246
247 if (configureClient())
248 startClient();
249 }
250 }
251 });
252}
253
254void QGeoPositionInfoSourceGeoclue2::startClient()
255{
256 // only start the client if someone asked for it already
257 if (!m_running && !m_requestTimer->isActive())
258 return;
259
260 if (!m_client) {
261 createClient();
262 return;
263 }
264
265 const QDBusPendingReply<> reply = m_client->Start();
266 const auto watcher = new QDBusPendingCallWatcher(reply, this);
270 scopedWatcher(watcher);
271 const QDBusPendingReply<> reply = *scopedWatcher;
272 if (reply.isError()) {
273 const auto error = reply.error();
274 qCCritical(lcPositioningGeoclue2) << "Unable to start the client:"
275 << error.name() << error.message();
276 setError(AccessError);
277 delete m_client;
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);
302 scopedWatcher(watcher);
303 const QDBusPendingReply<> reply = *scopedWatcher;
304 if (reply.isError()) {
305 const auto error = reply.error();
306 qCCritical(lcPositioningGeoclue2) << "Unable to stop the client:"
307 << error.name() << error.message();
308 setError(AccessError);
309 } else {
310 qCDebug(lcPositioningGeoclue2) << "Client successfully stopped";
311 }
312 delete m_client;
313 });
314}
315
316bool QGeoPositionInfoSourceGeoclue2::configureClient()
317{
318 if (!m_client)
319 return false;
320
321 if (m_desktopId.isEmpty()) {
322 qCCritical(lcPositioningGeoclue2)
323 << "Unable to configure the client due to the desktop id is not set via"
324 << desktopIdParameter << "plugin parameter or QCoreApplication::applicationName";
325 setError(AccessError);
326 return false;
327 }
328
329 m_client->setDesktopId(m_desktopId);
330
331 const auto msecs = updateInterval();
332 const uint secs = qMax(uint(msecs), 0u) / 1000u;
333 m_client->setTimeThreshold(secs);
334
336 switch (methods) {
338 m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_EXACT);
339 break;
341 m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_STREET);
342 break;
344 m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_EXACT);
345 break;
346 default:
347 m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_NONE);
348 break;
349 }
350
351 return true;
352}
353
354void QGeoPositionInfoSourceGeoclue2::requestUpdateTimeout()
355{
356 qCDebug(lcPositioningGeoclue2) << "Request update timeout occurred";
357
359
360 stopClient();
361}
362
363void QGeoPositionInfoSourceGeoclue2::handleNewLocation(const QDBusObjectPath &oldLocation,
364 const QDBusObjectPath &newLocation)
365{
366 if (m_requestTimer->isActive())
367 m_requestTimer->stop();
368
369 const auto oldPath = oldLocation.path();
370 const auto newPath = newLocation.path();
371 qCDebug(lcPositioningGeoclue2) << "Old location object path:" << oldPath;
372 qCDebug(lcPositioningGeoclue2) << "New location object path:" << newPath;
373
374 OrgFreedesktopGeoClue2LocationInterface location(
375 QLatin1String(GEOCLUE2_SERVICE_NAME),
376 newPath,
378 this);
379 if (!location.isValid()) {
380 const auto error = location.lastError();
381 qCCritical(lcPositioningGeoclue2) << "Unable to create the location object:"
382 << error.name() << error.message();
383 } else {
384 QGeoCoordinate coordinate(location.latitude(),
385 location.longitude());
386 const auto altitude = location.altitude();
387 if (altitude > std::numeric_limits<double>::lowest())
388 coordinate.setAltitude(altitude);
389
390 const Timestamp ts = location.timestamp();
391 if (ts.m_seconds == 0 && ts.m_microseconds == 0) {
392 const auto dt = QDateTime::currentDateTime();
393 m_lastPosition = QGeoPositionInfo(coordinate, dt);
394 } else {
396 dt = dt.addMSecs(ts.m_microseconds / 1000);
397 m_lastPosition = QGeoPositionInfo(coordinate, dt);
398 }
399
400 const auto accuracy = location.accuracy();
401 // We assume that an accuracy as 0.0 means that it comes from a sattelite.
402 m_lastPositionFromSatellite = qFuzzyCompare(accuracy, 0.0);
403
404 m_lastPosition.setAttribute(QGeoPositionInfo::HorizontalAccuracy, accuracy);
405 const auto speed = location.speed();
406 if (speed >= 0.0)
407 m_lastPosition.setAttribute(QGeoPositionInfo::GroundSpeed, speed);
408 const auto heading = location.heading();
409 if (heading >= 0.0)
410 m_lastPosition.setAttribute(QGeoPositionInfo::Direction, heading);
411
412 emit positionUpdated(m_lastPosition);
413 qCDebug(lcPositioningGeoclue2) << "New position:" << m_lastPosition;
414 }
415
416 stopClient();
417}
418
419void QGeoPositionInfoSourceGeoclue2::parseParameters(const QVariantMap &parameters)
420{
421 if (parameters.contains(desktopIdParameter))
422 m_desktopId = parameters.value(desktopIdParameter).toString();
423
424 if (m_desktopId.isEmpty())
425 m_desktopId = QCoreApplication::applicationName();
426}
427
429
430#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
QString path() const
Returns this object path.
void finished(QDBusPendingCallWatcher *self=nullptr)
This signal is emitted when the pending call has finished and its reply is available.
\inmodule QtDBus
\inmodule QtCore\reentrant
Definition qdatastream.h:30
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
bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:881
\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.
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
virtual void requestUpdate(int timeout=0)=0
Attempts to get the current position and emit positionUpdated() with this information.
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:356
bool contains(const Key &key) const
Definition qmap.h:340
NetworkError error() const
Returns the error that was found during the processing of this request.
\inmodule QtCore
Definition qobject.h:90
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
T * data() const
Definition qpointer.h:56
\inmodule QtCore
Definition qsavefile.h:24
\inmodule QtCore
static QString writableLocation(StandardLocation type)
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
\inmodule QtCore
Definition qtimer.h:20
void setSingleShot(bool singleShot)
Definition qtimer.cpp:580
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:208
bool isActive() const
Returns true if the timer is running (pending); otherwise returns false.
Definition qtimer.cpp:156
void stop()
Stops the timer.
Definition qtimer.cpp:226
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:287
#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:62
GLint location
GLbitfield GLuint64 timeout
[4]
GLsizei const GLchar *const * path
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define QStringLiteral(str)
#define emit
unsigned int uint
Definition qtypes.h:29
long long qint64
Definition qtypes.h:55
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...
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent