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)
26enum GClueAccuracyLevel {
27 GCLUE_ACCURACY_LEVEL_NONE = 0,
28 GCLUE_ACCURACY_LEVEL_COUNTRY = 1,
29 GCLUE_ACCURACY_LEVEL_CITY = 4,
30 GCLUE_ACCURACY_LEVEL_NEIGHBORHOOD = 5,
31 GCLUE_ACCURACY_LEVEL_STREET = 6,
32 GCLUE_ACCURACY_LEVEL_EXACT = 8
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";
40static QString lastPositionFilePath()
42 return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)
43 + QStringLiteral(
"/qtposition-geoclue2");
48QGeoPositionInfoSourceGeoclue2::QGeoPositionInfoSourceGeoclue2(
const QVariantMap ¶meters,
50 : QGeoPositionInfoSource(parent)
51 , m_requestTimer(
new QTimer(
this))
52 , m_manager(QLatin1String(GEOCLUE2_SERVICE_NAME),
53 QStringLiteral(
"/org/freedesktop/GeoClue2/Manager"),
54 QDBusConnection::systemBus(),
57 parseParameters(parameters);
59 qDBusRegisterMetaType<Timestamp>();
61 restoreLastPosition();
63 m_requestTimer->setSingleShot(
true);
64 connect(m_requestTimer, &QTimer::timeout,
65 this, &QGeoPositionInfoSourceGeoclue2::requestUpdateTimeout);
68QGeoPositionInfoSourceGeoclue2::~QGeoPositionInfoSourceGeoclue2()
73void QGeoPositionInfoSourceGeoclue2::setUpdateInterval(
int msec)
75 QGeoPositionInfoSource::setUpdateInterval(msec);
79QGeoPositionInfo QGeoPositionInfoSourceGeoclue2::lastKnownPosition(
bool fromSatellitePositioningMethodsOnly)
const
81 if (fromSatellitePositioningMethodsOnly && !m_lastPositionFromSatellite)
82 return QGeoPositionInfo();
83 return m_lastPosition;
86QGeoPositionInfoSourceGeoclue2::PositioningMethods QGeoPositionInfoSourceGeoclue2::supportedPositioningMethods()
const
89 const auto accuracy = m_manager.property(
"AvailableAccuracyLevel").toUInt(&ok);
91 const_cast<QGeoPositionInfoSourceGeoclue2 *>(
this)->setError(AccessError);
92 return NoPositioningMethods;
96 case GCLUE_ACCURACY_LEVEL_COUNTRY:
97 case GCLUE_ACCURACY_LEVEL_CITY:
98 case GCLUE_ACCURACY_LEVEL_NEIGHBORHOOD:
99 case GCLUE_ACCURACY_LEVEL_STREET:
100 return NonSatellitePositioningMethods;
101 case GCLUE_ACCURACY_LEVEL_EXACT:
102 return AllPositioningMethods;
103 case GCLUE_ACCURACY_LEVEL_NONE:
105 return NoPositioningMethods;
109void QGeoPositionInfoSourceGeoclue2::setPreferredPositioningMethods(PositioningMethods methods)
111 QGeoPositionInfoSource::setPreferredPositioningMethods(methods);
115int QGeoPositionInfoSourceGeoclue2::minimumUpdateInterval()
const
117 return MINIMUM_UPDATE_INTERVAL;
120QGeoPositionInfoSource::Error QGeoPositionInfoSourceGeoclue2::error()
const
125void QGeoPositionInfoSourceGeoclue2::startUpdates()
128 qCWarning(lcPositioningGeoclue2) <<
"Already running";
132 qCDebug(lcPositioningGeoclue2) <<
"Starting updates";
134 m_error = QGeoPositionInfoSource::NoError;
140 if (m_lastPosition.isValid()) {
141 QMetaObject::invokeMethod(
this,
"positionUpdated", Qt::QueuedConnection,
142 Q_ARG(QGeoPositionInfo, m_lastPosition));
146void QGeoPositionInfoSourceGeoclue2::stopUpdates()
149 qCWarning(lcPositioningGeoclue2) <<
"Already stopped";
153 qCDebug(lcPositioningGeoclue2) <<
"Stopping updates";
159void QGeoPositionInfoSourceGeoclue2::requestUpdate(
int timeout)
161 if (m_requestTimer->isActive()) {
162 qCDebug(lcPositioningGeoclue2) <<
"Request timer was active, ignoring startUpdates";
166 m_error = QGeoPositionInfoSource::NoError;
168 if (timeout < minimumUpdateInterval() && timeout != 0) {
169 setError(QGeoPositionInfoSource::UpdateTimeoutError);
173 m_requestTimer->start(timeout ? timeout : UPDATE_TIMEOUT_COLD_START);
177void QGeoPositionInfoSourceGeoclue2::setError(QGeoPositionInfoSource::Error error)
180 if (m_error != QGeoPositionInfoSource::NoError)
181 emit QGeoPositionInfoSource::errorOccurred(m_error);
184void QGeoPositionInfoSourceGeoclue2::restoreLastPosition()
186#if !defined(QT_NO_DATASTREAM)
187 const auto filePath = lastPositionFilePath();
188 QFile file(filePath);
189 if (file.open(QIODevice::ReadOnly)) {
190 QDataStream out(&file);
191 out >> m_lastPosition;
196void QGeoPositionInfoSourceGeoclue2::saveLastPosition()
198#if !defined(QT_NO_DATASTREAM) && QT_CONFIG(temporaryfile)
199 if (!m_lastPosition.isValid())
202 const auto filePath = lastPositionFilePath();
203 QSaveFile file(filePath);
204 if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
205 QDataStream out(&file);
207 out << QGeoPositionInfo(m_lastPosition.coordinate(), m_lastPosition.timestamp());
213void QGeoPositionInfoSourceGeoclue2::createClient()
215 const QDBusPendingReply<QDBusObjectPath> reply = m_manager.GetClient();
216 const auto watcher =
new QDBusPendingCallWatcher(reply,
this);
217 connect(watcher, &QDBusPendingCallWatcher::finished,
this,
218 [
this](QDBusPendingCallWatcher *watcher) {
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);
227 const QString clientPath = reply.value().path();
228 qCDebug(lcPositioningGeoclue2) <<
"Client path is:"
231 m_client =
new OrgFreedesktopGeoClue2ClientInterface(
232 QLatin1String(GEOCLUE2_SERVICE_NAME),
234 QDBusConnection::systemBus(),
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();
241 setError(AccessError);
243 connect(m_client.data(), &OrgFreedesktopGeoClue2ClientInterface::LocationUpdated,
244 this, &QGeoPositionInfoSourceGeoclue2::handleNewLocation);
246 if (configureClient())
253void QGeoPositionInfoSourceGeoclue2::startClient()
256 if (!m_running && !m_requestTimer->isActive())
264 const QDBusPendingReply<> reply = m_client->Start();
265 const auto watcher =
new QDBusPendingCallWatcher(reply,
this);
266 connect(watcher, &QDBusPendingCallWatcher::finished,
this,
267 [
this](QDBusPendingCallWatcher *watcher) {
268 watcher->deleteLater();
269 const QDBusPendingReply<> reply = *watcher;
270 if (reply.isError()) {
271 const auto error = reply.error();
272 qCCritical(lcPositioningGeoclue2) <<
"Unable to start the client:"
273 << error.name() << error.message();
277 setError(AccessError);
279 qCDebug(lcPositioningGeoclue2) <<
"Client successfully started";
281 const QDBusObjectPath location = m_client->location();
282 const QString path = location.path();
283 if (path.isEmpty() || path == QLatin1String(
"/"))
286 handleNewLocation({}, location);
291void QGeoPositionInfoSourceGeoclue2::stopClient()
294 if (m_requestTimer->isActive() || m_running || !m_client)
297 const QDBusPendingReply<> reply = m_client->Stop();
298 const auto watcher =
new QDBusPendingCallWatcher(reply,
this);
299 connect(watcher, &QDBusPendingCallWatcher::finished,
this,
300 [
this](QDBusPendingCallWatcher *watcher) {
301 watcher->deleteLater();
302 const QDBusPendingReply<> reply = *watcher;
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);
309 qCDebug(lcPositioningGeoclue2) <<
"Client successfully stopped";
315bool QGeoPositionInfoSourceGeoclue2::configureClient()
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);
328 m_client->setDesktopId(m_desktopId);
330 const auto msecs = updateInterval();
331 const uint secs = qMax(uint(msecs), 0u) / 1000u;
332 m_client->setTimeThreshold(secs);
334 const auto methods = preferredPositioningMethods();
336 case SatellitePositioningMethods:
337 m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_EXACT);
339 case NonSatellitePositioningMethods:
340 m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_STREET);
342 case AllPositioningMethods:
343 m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_EXACT);
346 m_client->setRequestedAccuracyLevel(GCLUE_ACCURACY_LEVEL_NONE);
353void QGeoPositionInfoSourceGeoclue2::requestUpdateTimeout()
355 qCDebug(lcPositioningGeoclue2) <<
"Request update timeout occurred";
357 setError(QGeoPositionInfoSource::UpdateTimeoutError);
362void QGeoPositionInfoSourceGeoclue2::handleNewLocation(
const QDBusObjectPath &oldLocation,
363 const QDBusObjectPath &newLocation)
365 if (m_requestTimer->isActive())
366 m_requestTimer->stop();
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;
373 OrgFreedesktopGeoClue2LocationInterface location(
374 QLatin1String(GEOCLUE2_SERVICE_NAME),
376 QDBusConnection::systemBus(),
378 if (!location.isValid()) {
379 const auto error = location.lastError();
380 qCCritical(lcPositioningGeoclue2) <<
"Unable to create the location object:"
381 << error.name() << error.message();
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);
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);
394 auto dt = QDateTime::fromSecsSinceEpoch(qint64(ts.m_seconds));
395 dt = dt.addMSecs(ts.m_microseconds / 1000);
396 m_lastPosition = QGeoPositionInfo(coordinate, dt);
399 const auto accuracy = location.accuracy();
401 m_lastPositionFromSatellite = qFuzzyCompare(accuracy, 0.0);
403 m_lastPosition.setAttribute(QGeoPositionInfo::HorizontalAccuracy, accuracy);
404 const auto speed = location.speed();
406 m_lastPosition.setAttribute(QGeoPositionInfo::GroundSpeed, speed);
407 const auto heading = location.heading();
409 m_lastPosition.setAttribute(QGeoPositionInfo::Direction, heading);
411 emit positionUpdated(m_lastPosition);
412 qCDebug(lcPositioningGeoclue2) <<
"New position:" << m_lastPosition;
418void QGeoPositionInfoSourceGeoclue2::parseParameters(
const QVariantMap ¶meters)
420 if (parameters.contains(desktopIdParameter))
421 m_desktopId = parameters.value(desktopIdParameter).toString();
423 if (m_desktopId.isEmpty())
424 m_desktopId = QCoreApplication::applicationName();
429#include "moc_qgeopositioninfosource_geoclue2_p.cpp"