6#include <QtCore/QLoggingCategory>
7#include <QtCore/QSaveFile>
8#include <QtCore/QScopedPointer>
9#include <QtCore/QTimer>
10#include <QtDBus/QDBusPendingCallWatcher>
13#include <client_interface.h>
14#include "moc_client_interface.cpp"
15#include <location_interface.h>
16#include "moc_location_interface.cpp"
17#include "moc_manager_interface.cpp"
19Q_DECLARE_LOGGING_CATEGORY(lcPositioningGeoclue2)
23using namespace QtPositioningPrivate;
28enum GClueAccuracyLevel {
29 GCLUE_ACCURACY_LEVEL_NONE = 0,
30 GCLUE_ACCURACY_LEVEL_COUNTRY = 1,
31 GCLUE_ACCURACY_LEVEL_CITY = 4,
32 GCLUE_ACCURACY_LEVEL_NEIGHBORHOOD = 5,
33 GCLUE_ACCURACY_LEVEL_STREET = 6,
34 GCLUE_ACCURACY_LEVEL_EXACT = 8
37const char GEOCLUE2_SERVICE_NAME[] =
"org.freedesktop.GeoClue2";
38const int MINIMUM_UPDATE_INTERVAL = 1000;
39const int UPDATE_TIMEOUT_COLD_START = 120000;
40static const auto desktopIdParameter =
"desktopId";
42static QString lastPositionFilePath()
44 return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)
45 + QStringLiteral(
"/qtposition-geoclue2");
52 : QGeoPositionInfoSource(parent)
53 , m_requestTimer(
new QTimer(
this))
54 , m_manager(QLatin1String(GEOCLUE2_SERVICE_NAME),
55 QStringLiteral(
"/org/freedesktop/GeoClue2/Manager"),
56 QDBusConnection::systemBus(),
59 parseParameters(parameters);
61 qDBusRegisterMetaType<Timestamp>();
63 restoreLastPosition();
65 m_requestTimer->setSingleShot(
true);
66 connect(m_requestTimer, &QTimer::timeout,
67 this, &QGeoPositionInfoSourceGeoclue2::requestUpdateTimeout);
77 QGeoPositionInfoSource::setUpdateInterval(msec);
83 if (fromSatellitePositioningMethodsOnly && !m_lastPositionFromSatellite)
84 return QGeoPositionInfo();
85 return m_lastPosition;
91 const auto accuracy = m_manager.property(
"AvailableAccuracyLevel").toUInt(&ok);
93 const_cast<QGeoPositionInfoSourceGeoclue2 *>(
this)->setError(AccessError);
94 return NoPositioningMethods;
98 case GCLUE_ACCURACY_LEVEL_COUNTRY:
99 case GCLUE_ACCURACY_LEVEL_CITY:
100 case GCLUE_ACCURACY_LEVEL_NEIGHBORHOOD:
101 case GCLUE_ACCURACY_LEVEL_STREET:
102 return NonSatellitePositioningMethods;
103 case GCLUE_ACCURACY_LEVEL_EXACT:
104 return AllPositioningMethods;
105 case GCLUE_ACCURACY_LEVEL_NONE:
107 return NoPositioningMethods;
113 QGeoPositionInfoSource::setPreferredPositioningMethods(methods);
119 return MINIMUM_UPDATE_INTERVAL;
130 qCWarning(lcPositioningGeoclue2) <<
"Already running";
134 qCDebug(lcPositioningGeoclue2) <<
"Starting updates";
136 m_error = QGeoPositionInfoSource::NoError;
142 if (m_lastPosition.isValid()) {
143 QMetaObject::invokeMethod(
this,
"positionUpdated", Qt::QueuedConnection,
144 Q_ARG(QGeoPositionInfo, m_lastPosition));
151 qCWarning(lcPositioningGeoclue2) <<
"Already stopped";
155 qCDebug(lcPositioningGeoclue2) <<
"Stopping updates";
163 if (m_requestTimer->isActive()) {
164 qCDebug(lcPositioningGeoclue2) <<
"Request timer was active, ignoring startUpdates";
168 m_error = QGeoPositionInfoSource::NoError;
171 setError(QGeoPositionInfoSource::UpdateTimeoutError);
175 m_requestTimer->start(timeout ? timeout : UPDATE_TIMEOUT_COLD_START);
182 if (m_error != QGeoPositionInfoSource::NoError)
183 emit QGeoPositionInfoSource::errorOccurred(m_error);
188#if !defined(QT_NO_DATASTREAM)
189 const auto filePath = lastPositionFilePath();
190 QFile file(filePath);
191 if (file.open(QIODevice::ReadOnly)) {
192 QDataStream out(&file);
193 out >> m_lastPosition;
200#if !defined(QT_NO_DATASTREAM) && QT_CONFIG(temporaryfile)
201 if (!m_lastPosition.isValid())
204 const auto filePath = lastPositionFilePath();
205 QSaveFile file(filePath);
206 if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
207 QDataStream out(&file);
209 out << QGeoPositionInfo(m_lastPosition.coordinate(), m_lastPosition.timestamp());
217 const QDBusPendingReply<QDBusObjectPath> reply = m_manager.GetClient();
218 const auto watcher =
new QDBusPendingCallWatcher(reply,
this);
219 connect(watcher, &QDBusPendingCallWatcher::finished,
this,
220 [
this](QDBusPendingCallWatcher *watcher) {
221 watcher->deleteLater();
222 const QDBusPendingReply<QDBusObjectPath> reply = *watcher;
223 if (reply.isError()) {
224 const auto error = reply.error();
225 qCWarning(lcPositioningGeoclue2) <<
"Unable to obtain the client:"
226 << error.name() << error.message();
227 setError(AccessError);
229 const QString clientPath = reply.value().path();
230 qCDebug(lcPositioningGeoclue2) <<
"Client path is:"
233 m_client =
new OrgFreedesktopGeoClue2ClientInterface(
234 QLatin1String(GEOCLUE2_SERVICE_NAME),
236 QDBusConnection::systemBus(),
238 if (!m_client->isValid()) {
239 const auto error = m_client->lastError();
240 qCCritical(lcPositioningGeoclue2) <<
"Unable to create the client object:"
241 << error.name() << error.message();
243 setError(AccessError);
245 connect(m_client.data(), &OrgFreedesktopGeoClue2ClientInterface::LocationUpdated,
246 this, &QGeoPositionInfoSourceGeoclue2::handleNewLocation);
248 if (configureClient())
258 if (!m_running && !m_requestTimer->isActive())
266 const QDBusPendingReply<> reply = m_client->Start();
267 const auto watcher =
new QDBusPendingCallWatcher(reply,
this);
268 connect(watcher, &QDBusPendingCallWatcher::finished,
this,
269 [
this](QDBusPendingCallWatcher *watcher) {
270 watcher->deleteLater();
271 const QDBusPendingReply<> reply = *watcher;
272 if (reply.isError()) {
273 const auto error = reply.error();
274 qCCritical(lcPositioningGeoclue2) <<
"Unable to start the client:"
275 << error.name() << error.message();
279 setError(AccessError);
281 qCDebug(lcPositioningGeoclue2) <<
"Client successfully started";
283 const QDBusObjectPath location = m_client->location();
284 const QString path = location.path();
285 if (path.isEmpty() || path == QLatin1String(
"/"))
288 handleNewLocation({}, location);
296 if (m_requestTimer->isActive() || m_running || !m_client)
299 const QDBusPendingReply<> reply = m_client->Stop();
300 const auto watcher =
new QDBusPendingCallWatcher(reply,
this);
301 connect(watcher, &QDBusPendingCallWatcher::finished,
this,
302 [
this](QDBusPendingCallWatcher *watcher) {
303 watcher->deleteLater();
304 const QDBusPendingReply<> reply = *watcher;
305 if (reply.isError()) {
306 const auto error = reply.error();
307 qCCritical(lcPositioningGeoclue2) <<
"Unable to stop the client:"
308 << error.name() << error.message();
309 setError(AccessError);
311 qCDebug(lcPositioningGeoclue2) <<
"Client successfully stopped";
322 if (m_desktopId.isEmpty()) {
323 qCCritical(lcPositioningGeoclue2)
324 <<
"Unable to configure the client due to the desktop id is not set via"
325 << desktopIdParameter <<
"plugin parameter or QGuiApplication::desktopFileName";
326 setError(AccessError);
330 m_client->setDesktopId(m_desktopId);
332 const auto msecs = updateInterval();
333 const uint secs = qMax(uint(msecs), 0u) / 1000u;
334 m_client->setTimeThreshold(secs);
336 const auto methods = preferredPositioningMethods();
338 case SatellitePositioningMethods:
339 m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_EXACT);
341 case NonSatellitePositioningMethods:
342 m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_STREET);
344 case AllPositioningMethods:
345 m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_EXACT);
348 m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_NONE);
357 qCDebug(lcPositioningGeoclue2) <<
"Request update timeout occurred";
359 setError(QGeoPositionInfoSource::UpdateTimeoutError);
365 const QDBusObjectPath &newLocation)
367 if (m_requestTimer->isActive())
368 m_requestTimer->stop();
370 const auto oldPath = oldLocation.path();
371 const auto newPath = newLocation.path();
372 qCDebug(lcPositioningGeoclue2) <<
"Old location object path:" << oldPath;
373 qCDebug(lcPositioningGeoclue2) <<
"New location object path:" << newPath;
375 OrgFreedesktopGeoClue2LocationInterface location(
376 QLatin1String(GEOCLUE2_SERVICE_NAME),
378 QDBusConnection::systemBus(),
380 if (!location.isValid()) {
381 const auto error = location.lastError();
382 qCCritical(lcPositioningGeoclue2) <<
"Unable to create the location object:"
383 << error.name() << error.message();
385 QGeoCoordinate coordinate(location.latitude(),
386 location.longitude());
387 const auto altitude = location.altitude();
388 if (altitude > std::numeric_limits<
double>::lowest())
389 coordinate.setAltitude(altitude);
391 const Timestamp ts = location.timestamp();
392 if (ts.m_seconds == 0 && ts.m_microseconds == 0) {
393 const auto dt = QDateTime::currentDateTime();
394 m_lastPosition = QGeoPositionInfo(coordinate, dt);
396 auto dt = QDateTime::fromSecsSinceEpoch(qint64(ts.m_seconds));
397 dt = dt.addMSecs(ts.m_microseconds / 1000);
398 m_lastPosition = QGeoPositionInfo(coordinate, dt);
401 const auto accuracy = location.accuracy();
403 m_lastPositionFromSatellite = qFuzzyCompare(accuracy, 0.0);
405 m_lastPosition.setAttribute(QGeoPositionInfo::HorizontalAccuracy, accuracy);
406 const auto speed = location.speed();
408 m_lastPosition.setAttribute(QGeoPositionInfo::GroundSpeed, speed);
409 const auto heading = location.heading();
411 m_lastPosition.setAttribute(QGeoPositionInfo::Direction, heading);
413 emit positionUpdated(m_lastPosition);
414 qCDebug(lcPositioningGeoclue2) <<
"New position:" << m_lastPosition;
422 if (parameters.contains(desktopIdParameter))
423 m_desktopId = parameters.value(desktopIdParameter).toString();
425 if (m_desktopId.isEmpty() && qApp)
426 m_desktopId = qApp->property(
"desktopFileName").toString();
428#if QT_VERSION < QT_VERSION_CHECK(7
, 0
, 0
)
429 if (m_desktopId.isEmpty()) {
430 qCWarning(lcPositioningGeoclue2) <<
"Neither" << desktopIdParameter
431 <<
"plugin parameter nor QGuiApplication::desktopFileName"
432 <<
"has been set. Please consider setting one of the two.";
433 m_desktopId = QCoreApplication::applicationName();
440#include "moc_qgeopositioninfosource_geoclue2_p.cpp"
void startUpdates() override
Starts emitting updates at regular intervals as specified by setUpdateInterval().
~QGeoPositionInfoSourceGeoclue2()
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.
int minimumUpdateInterval() const override
void setPreferredPositioningMethods(PositioningMethods methods) override
void setUpdateInterval(int msec) override