6#include <QtCore/QLoggingCategory>
7#include <QtCore/QSaveFile>
8#include <QtCore/QTimer>
9#include <QtDBus/QDBusPendingCallWatcher>
12#include <client_interface.h>
13#include "moc_client_interface.cpp"
14#include <location_interface.h>
15#include "moc_location_interface.cpp"
16#include "moc_manager_interface.cpp"
18Q_DECLARE_LOGGING_CATEGORY(lcPositioningGeoclue2)
22using namespace QtPositioningPrivate;
27enum GClueAccuracyLevel {
28 GCLUE_ACCURACY_LEVEL_NONE = 0,
29 GCLUE_ACCURACY_LEVEL_COUNTRY = 1,
30 GCLUE_ACCURACY_LEVEL_CITY = 4,
31 GCLUE_ACCURACY_LEVEL_NEIGHBORHOOD = 5,
32 GCLUE_ACCURACY_LEVEL_STREET = 6,
33 GCLUE_ACCURACY_LEVEL_EXACT = 8
36const char GEOCLUE2_SERVICE_NAME[] =
"org.freedesktop.GeoClue2";
37const int MINIMUM_UPDATE_INTERVAL = 1000;
38const int UPDATE_TIMEOUT_COLD_START = 120000;
39static const auto desktopIdParameter =
"desktopId";
41static QString lastPositionFilePath()
43 return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)
44 + QStringLiteral(
"/qtposition-geoclue2");
51 : QGeoPositionInfoSource(parent)
52 , m_requestTimer(
new QTimer(
this))
53 , m_manager(QLatin1String(GEOCLUE2_SERVICE_NAME),
54 QStringLiteral(
"/org/freedesktop/GeoClue2/Manager"),
55 QDBusConnection::systemBus(),
58 parseParameters(parameters);
60 qDBusRegisterMetaType<Timestamp>();
62 restoreLastPosition();
64 m_requestTimer->setSingleShot(
true);
65 connect(m_requestTimer, &QTimer::timeout,
76 QGeoPositionInfoSource::setUpdateInterval(msec);
82 if (fromSatellitePositioningMethodsOnly && !m_lastPositionFromSatellite)
83 return QGeoPositionInfo();
84 return m_lastPosition;
90 const auto accuracy = m_manager.property(
"AvailableAccuracyLevel").toUInt(&ok);
93 return NoPositioningMethods;
97 case GCLUE_ACCURACY_LEVEL_COUNTRY:
98 case GCLUE_ACCURACY_LEVEL_CITY:
99 case GCLUE_ACCURACY_LEVEL_NEIGHBORHOOD:
100 case GCLUE_ACCURACY_LEVEL_STREET:
101 return NonSatellitePositioningMethods;
102 case GCLUE_ACCURACY_LEVEL_EXACT:
103 return AllPositioningMethods;
104 case GCLUE_ACCURACY_LEVEL_NONE:
106 return NoPositioningMethods;
112 QGeoPositionInfoSource::setPreferredPositioningMethods(methods);
118 return MINIMUM_UPDATE_INTERVAL;
129 qCWarning(lcPositioningGeoclue2) <<
"Already running";
133 qCDebug(lcPositioningGeoclue2) <<
"Starting updates";
135 m_error = QGeoPositionInfoSource::NoError;
141 if (m_lastPosition.isValid()) {
142 QMetaObject::invokeMethod(
this,
"positionUpdated", Qt::QueuedConnection,
143 Q_ARG(QGeoPositionInfo, m_lastPosition));
150 qCWarning(lcPositioningGeoclue2) <<
"Already stopped";
154 qCDebug(lcPositioningGeoclue2) <<
"Stopping updates";
162 if (m_requestTimer->isActive()) {
163 qCDebug(lcPositioningGeoclue2) <<
"Request timer was active, ignoring startUpdates";
167 m_error = QGeoPositionInfoSource::NoError;
170 setError(QGeoPositionInfoSource::UpdateTimeoutError);
174 m_requestTimer->start(timeout ? timeout : UPDATE_TIMEOUT_COLD_START);
181 if (m_error != QGeoPositionInfoSource::NoError)
182 emit QGeoPositionInfoSource::errorOccurred(m_error);
187#if !defined(QT_NO_DATASTREAM)
188 const auto filePath = lastPositionFilePath();
189 QFile file(filePath);
190 if (file.open(QIODevice::ReadOnly)) {
191 QDataStream out(&file);
192 out >> m_lastPosition;
199#if !defined(QT_NO_DATASTREAM) && QT_CONFIG(temporaryfile)
200 if (!m_lastPosition.isValid())
203 const auto filePath = lastPositionFilePath();
204 QSaveFile file(filePath);
205 if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
206 QDataStream out(&file);
208 out << QGeoPositionInfo(m_lastPosition.coordinate(), m_lastPosition.timestamp());
216 const QDBusPendingReply<QDBusObjectPath> reply = m_manager.GetClient();
217 const auto watcher =
new QDBusPendingCallWatcher(reply,
this);
218 connect(watcher, &QDBusPendingCallWatcher::finished,
this,
219 [
this](QDBusPendingCallWatcher *watcher) {
220 watcher->deleteLater();
221 const QDBusPendingReply<QDBusObjectPath> reply = *watcher;
222 if (reply.isError()) {
223 const auto error = reply.error();
224 qCWarning(lcPositioningGeoclue2) <<
"Unable to obtain the client:"
225 << error.name() << error.message();
226 setError(AccessError);
228 const QString clientPath = reply.value().path();
229 qCDebug(lcPositioningGeoclue2) <<
"Client path is:"
232 m_client =
new OrgFreedesktopGeoClue2ClientInterface(
233 QLatin1String(GEOCLUE2_SERVICE_NAME),
235 QDBusConnection::systemBus(),
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();
242 setError(AccessError);
244 connect(m_client.data(), &OrgFreedesktopGeoClue2ClientInterface::LocationUpdated,
247 if (configureClient())
257 if (!m_running && !m_requestTimer->isActive())
265 const QDBusPendingReply<> reply = m_client->Start();
266 const auto watcher =
new QDBusPendingCallWatcher(reply,
this);
267 connect(watcher, &QDBusPendingCallWatcher::finished,
this,
268 [
this](QDBusPendingCallWatcher *watcher) {
269 watcher->deleteLater();
270 const QDBusPendingReply<> reply = *watcher;
271 if (reply.isError()) {
272 const auto error = reply.error();
273 qCCritical(lcPositioningGeoclue2) <<
"Unable to start the client:"
274 << error.name() << error.message();
278 setError(AccessError);
280 qCDebug(lcPositioningGeoclue2) <<
"Client successfully started";
282 const QDBusObjectPath location = m_client->location();
283 const QString path = location.path();
284 if (path.isEmpty() || path == QLatin1String(
"/"))
287 handleNewLocation({}, location);
295 if (m_requestTimer->isActive() || m_running || !m_client)
298 const QDBusPendingReply<> reply = m_client->Stop();
299 const auto watcher =
new QDBusPendingCallWatcher(reply,
this);
300 connect(watcher, &QDBusPendingCallWatcher::finished,
this,
301 [
this](QDBusPendingCallWatcher *watcher) {
302 watcher->deleteLater();
303 const QDBusPendingReply<> reply = *watcher;
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);
310 qCDebug(lcPositioningGeoclue2) <<
"Client successfully stopped";
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 QGuiApplication::desktopFileName";
325 setError(AccessError);
329 m_client->setDesktopId(m_desktopId);
331 const auto msecs = updateInterval();
332 const uint secs = qMax(uint(msecs), 0u) / 1000u;
333 m_client->setTimeThreshold(secs);
335 const auto methods = preferredPositioningMethods();
337 case SatellitePositioningMethods:
338 m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_EXACT);
340 case NonSatellitePositioningMethods:
341 m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_STREET);
343 case AllPositioningMethods:
344 m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_EXACT);
347 m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_NONE);
356 qCDebug(lcPositioningGeoclue2) <<
"Request update timeout occurred";
358 setError(QGeoPositionInfoSource::UpdateTimeoutError);
364 const QDBusObjectPath &newLocation)
366 if (m_requestTimer->isActive())
367 m_requestTimer->stop();
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;
374 OrgFreedesktopGeoClue2LocationInterface location(
375 QLatin1String(GEOCLUE2_SERVICE_NAME),
377 QDBusConnection::systemBus(),
379 if (!location.isValid()) {
380 const auto error = location.lastError();
381 qCCritical(lcPositioningGeoclue2) <<
"Unable to create the location object:"
382 << error.name() << error.message();
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);
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);
395 auto dt = QDateTime::fromSecsSinceEpoch(qint64(ts.m_seconds));
396 dt = dt.addMSecs(ts.m_microseconds / 1000);
397 m_lastPosition = QGeoPositionInfo(coordinate, dt);
400 const auto accuracy = location.accuracy();
402 m_lastPositionFromSatellite = qFuzzyCompare(accuracy, 0.0);
404 m_lastPosition.setAttribute(QGeoPositionInfo::HorizontalAccuracy, accuracy);
405 const auto speed = location.speed();
407 m_lastPosition.setAttribute(QGeoPositionInfo::GroundSpeed, speed);
408 const auto heading = location.heading();
410 m_lastPosition.setAttribute(QGeoPositionInfo::Direction, heading);
412 emit positionUpdated(m_lastPosition);
413 qCDebug(lcPositioningGeoclue2) <<
"New position:" << m_lastPosition;
421 if (parameters.contains(desktopIdParameter))
422 m_desktopId = parameters.value(desktopIdParameter).toString();
424 if (m_desktopId.isEmpty() && qApp)
425 m_desktopId = qApp->property(
"desktopFileName").toString();
427#if QT_VERSION < QT_VERSION_CHECK(7
, 0
, 0
)
428 if (m_desktopId.isEmpty()) {
429 qCWarning(lcPositioningGeoclue2) <<
"Neither" << desktopIdParameter
430 <<
"plugin parameter nor QGuiApplication::desktopFileName"
431 <<
"has been set. Please consider setting one of the two.";
432 m_desktopId = QCoreApplication::applicationName();
439#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