summaryrefslogtreecommitdiffstats
path: root/dom/system/linux/GpsdLocationProvider.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/system/linux/GpsdLocationProvider.cpp')
-rw-r--r--dom/system/linux/GpsdLocationProvider.cpp446
1 files changed, 446 insertions, 0 deletions
diff --git a/dom/system/linux/GpsdLocationProvider.cpp b/dom/system/linux/GpsdLocationProvider.cpp
new file mode 100644
index 0000000000..34cd23c453
--- /dev/null
+++ b/dom/system/linux/GpsdLocationProvider.cpp
@@ -0,0 +1,446 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GpsdLocationProvider.h"
+#include <errno.h>
+#include <gps.h>
+#include "MLSFallback.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/LazyIdleThread.h"
+#include "mozilla/dom/GeolocationPositionErrorBinding.h"
+#include "GeolocationPosition.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+#include "prtime.h"
+
+namespace mozilla {
+namespace dom {
+
+//
+// MLSGeolocationUpdate
+//
+
+/**
+ * |MLSGeolocationUpdate| provides a fallback if gpsd is not supported.
+ */
+class GpsdLocationProvider::MLSGeolocationUpdate final
+ : public nsIGeolocationUpdate {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGEOLOCATIONUPDATE
+
+ explicit MLSGeolocationUpdate(nsIGeolocationUpdate* aCallback);
+
+ protected:
+ ~MLSGeolocationUpdate() = default;
+
+ private:
+ nsCOMPtr<nsIGeolocationUpdate> mCallback;
+};
+
+GpsdLocationProvider::MLSGeolocationUpdate::MLSGeolocationUpdate(
+ nsIGeolocationUpdate* aCallback)
+ : mCallback(aCallback) {
+ MOZ_ASSERT(mCallback);
+}
+
+// nsISupports
+//
+
+NS_IMPL_ISUPPORTS(GpsdLocationProvider::MLSGeolocationUpdate,
+ nsIGeolocationUpdate);
+
+// nsIGeolocationUpdate
+//
+
+NS_IMETHODIMP
+GpsdLocationProvider::MLSGeolocationUpdate::Update(
+ nsIDOMGeoPosition* aPosition) {
+ nsCOMPtr<nsIDOMGeoPositionCoords> coords;
+ aPosition->GetCoords(getter_AddRefs(coords));
+ if (!coords) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mCallback->Update(aPosition);
+}
+
+NS_IMETHODIMP
+GpsdLocationProvider::MLSGeolocationUpdate::NotifyError(uint16_t aError) {
+ return mCallback->NotifyError(aError);
+}
+
+//
+// UpdateRunnable
+//
+
+class GpsdLocationProvider::UpdateRunnable final : public Runnable {
+ public:
+ UpdateRunnable(
+ const nsMainThreadPtrHandle<GpsdLocationProvider>& aLocationProvider,
+ nsIDOMGeoPosition* aPosition)
+ : Runnable("GpsdU"),
+ mLocationProvider(aLocationProvider),
+ mPosition(aPosition) {
+ MOZ_ASSERT(mLocationProvider);
+ MOZ_ASSERT(mPosition);
+ }
+
+ // nsIRunnable
+ //
+
+ NS_IMETHOD Run() override {
+ mLocationProvider->Update(mPosition);
+ return NS_OK;
+ }
+
+ private:
+ nsMainThreadPtrHandle<GpsdLocationProvider> mLocationProvider;
+ RefPtr<nsIDOMGeoPosition> mPosition;
+};
+
+//
+// NotifyErrorRunnable
+//
+
+class GpsdLocationProvider::NotifyErrorRunnable final : public Runnable {
+ public:
+ NotifyErrorRunnable(
+ const nsMainThreadPtrHandle<GpsdLocationProvider>& aLocationProvider,
+ int aError)
+ : Runnable("GpsdNE"),
+ mLocationProvider(aLocationProvider),
+ mError(aError) {
+ MOZ_ASSERT(mLocationProvider);
+ }
+
+ // nsIRunnable
+ //
+
+ NS_IMETHOD Run() override {
+ mLocationProvider->NotifyError(mError);
+ return NS_OK;
+ }
+
+ private:
+ nsMainThreadPtrHandle<GpsdLocationProvider> mLocationProvider;
+ int mError;
+};
+
+//
+// PollRunnable
+//
+
+/**
+ * |PollRunnable| does the main work of processing GPS data received
+ * from gpsd. libgps blocks while polling, so this runnable has to be
+ * executed on it's own thread. To cancel the poll runnable, invoke
+ * |StopRunning| and |PollRunnable| will stop within a reasonable time
+ * frame.
+ */
+class GpsdLocationProvider::PollRunnable final : public Runnable {
+ public:
+ PollRunnable(
+ const nsMainThreadPtrHandle<GpsdLocationProvider>& aLocationProvider)
+ : Runnable("GpsdP"),
+ mLocationProvider(aLocationProvider),
+ mRunning(true) {
+ MOZ_ASSERT(mLocationProvider);
+ }
+
+ static bool IsSupported() {
+ return GPSD_API_MAJOR_VERSION >= 5 && GPSD_API_MAJOR_VERSION <= 12;
+ }
+
+ bool IsRunning() const { return mRunning; }
+
+ void StopRunning() { mRunning = false; }
+
+ // nsIRunnable
+ //
+
+ NS_IMETHOD Run() override {
+ int err;
+
+ switch (GPSD_API_MAJOR_VERSION) {
+ case 5 ... 12:
+ err = PollLoop5();
+ break;
+ default:
+ err = GeolocationPositionError_Binding::POSITION_UNAVAILABLE;
+ break;
+ }
+
+ if (err) {
+ NS_DispatchToMainThread(
+ MakeAndAddRef<NotifyErrorRunnable>(mLocationProvider, err));
+ }
+
+ mLocationProvider = nullptr;
+
+ return NS_OK;
+ }
+
+ protected:
+ int PollLoop5() {
+#if GPSD_API_MAJOR_VERSION >= 5 && GPSD_API_MAJOR_VERSION <= 12
+ static const int GPSD_WAIT_TIMEOUT_US =
+ 1000000; /* us to wait for GPS data */
+
+ struct gps_data_t gpsData;
+
+ auto res = gps_open(nullptr, nullptr, &gpsData);
+
+ if (res < 0) {
+ return ErrnoToError(errno);
+ }
+
+ gps_stream(&gpsData, WATCH_ENABLE | WATCH_JSON, NULL);
+
+ int err = 0;
+
+ // nsGeoPositionCoords will convert NaNs to null for optional properties of
+ // the JavaScript Coordinates object.
+ double lat = 0;
+ double lon = 0;
+ double alt = UnspecifiedNaN<double>();
+ double hError = 0;
+ double vError = UnspecifiedNaN<double>();
+ double heading = UnspecifiedNaN<double>();
+ double speed = UnspecifiedNaN<double>();
+
+ while (IsRunning()) {
+ errno = 0;
+ auto hasGpsData = gps_waiting(&gpsData, GPSD_WAIT_TIMEOUT_US);
+
+ if (errno) {
+ err = ErrnoToError(errno);
+ break;
+ }
+ if (!hasGpsData) {
+ continue; /* woke up from timeout */
+ }
+
+# if GPSD_API_MAJOR_VERSION >= 7
+ res = gps_read(&gpsData, nullptr, 0);
+# else
+
+ res = gps_read(&gpsData);
+# endif
+
+ if (res < 0) {
+ err = ErrnoToError(errno);
+ break;
+ } else if (!res) {
+ continue; /* no data available */
+ }
+
+# if GPSD_API_MAJOR_VERSION < 10
+ if (gpsData.status == STATUS_NO_FIX) {
+ continue;
+ }
+# endif
+
+ switch (gpsData.fix.mode) {
+ case MODE_3D:
+ double galt;
+
+# if GPSD_API_MAJOR_VERSION >= 9
+ galt = gpsData.fix.altMSL;
+# else
+ galt = gpsData.fix.altitude;
+# endif
+ if (!std::isnan(galt)) {
+ alt = galt;
+ }
+ [[fallthrough]];
+ case MODE_2D:
+ if (!std::isnan(gpsData.fix.latitude)) {
+ lat = gpsData.fix.latitude;
+ }
+ if (!std::isnan(gpsData.fix.longitude)) {
+ lon = gpsData.fix.longitude;
+ }
+ if (!std::isnan(gpsData.fix.epx) && !std::isnan(gpsData.fix.epy)) {
+ hError = std::max(gpsData.fix.epx, gpsData.fix.epy);
+ } else if (!std::isnan(gpsData.fix.epx)) {
+ hError = gpsData.fix.epx;
+ } else if (!std::isnan(gpsData.fix.epy)) {
+ hError = gpsData.fix.epy;
+ }
+ if (!std::isnan(gpsData.fix.epv)) {
+ vError = gpsData.fix.epv;
+ }
+ if (!std::isnan(gpsData.fix.track)) {
+ heading = gpsData.fix.track;
+ }
+ if (!std::isnan(gpsData.fix.speed)) {
+ speed = gpsData.fix.speed;
+ }
+ break;
+ default:
+ continue; // There's no useful data in this fix; continue.
+ }
+
+ NS_DispatchToMainThread(MakeAndAddRef<UpdateRunnable>(
+ mLocationProvider,
+ new nsGeoPosition(lat, lon, alt, hError, vError, heading, speed,
+ PR_Now() / PR_USEC_PER_MSEC)));
+ }
+
+ gps_stream(&gpsData, WATCH_DISABLE, NULL);
+ gps_close(&gpsData);
+
+ return err;
+#else
+ return GeolocationPositionError_Binding::POSITION_UNAVAILABLE;
+#endif // GPSD_MAJOR_API_VERSION
+ }
+
+ static int ErrnoToError(int aErrno) {
+ switch (aErrno) {
+ case EACCES:
+ [[fallthrough]];
+ case EPERM:
+ [[fallthrough]];
+ case EROFS:
+ return GeolocationPositionError_Binding::PERMISSION_DENIED;
+ case ETIME:
+ [[fallthrough]];
+ case ETIMEDOUT:
+ return GeolocationPositionError_Binding::TIMEOUT;
+ default:
+ return GeolocationPositionError_Binding::POSITION_UNAVAILABLE;
+ }
+ }
+
+ private:
+ nsMainThreadPtrHandle<GpsdLocationProvider> mLocationProvider;
+ Atomic<bool> mRunning;
+};
+
+//
+// GpsdLocationProvider
+//
+
+const uint32_t GpsdLocationProvider::GPSD_POLL_THREAD_TIMEOUT_MS = 5000;
+
+GpsdLocationProvider::GpsdLocationProvider() {}
+
+GpsdLocationProvider::~GpsdLocationProvider() {}
+
+void GpsdLocationProvider::Update(nsIDOMGeoPosition* aPosition) {
+ if (!mCallback || !mPollRunnable) {
+ return; // not initialized or already shut down
+ }
+
+ if (mMLSProvider) {
+ /* We got a location from gpsd, so let's cancel our MLS fallback. */
+ mMLSProvider->Shutdown();
+ mMLSProvider = nullptr;
+ }
+
+ mCallback->Update(aPosition);
+}
+
+void GpsdLocationProvider::NotifyError(int aError) {
+ if (!mCallback) {
+ return; // not initialized or already shut down
+ }
+
+ if (!mMLSProvider) {
+ /* With gpsd failed, we restart MLS. It will be canceled once we
+ * get another location from gpsd.
+ */
+ mMLSProvider = MakeAndAddRef<MLSFallback>();
+ mMLSProvider->Startup(new MLSGeolocationUpdate(mCallback));
+ }
+
+ mCallback->NotifyError(aError);
+}
+
+// nsISupports
+//
+
+NS_IMPL_ISUPPORTS(GpsdLocationProvider, nsIGeolocationProvider)
+
+// nsIGeolocationProvider
+//
+
+NS_IMETHODIMP
+GpsdLocationProvider::Startup() {
+ if (!PollRunnable::IsSupported()) {
+ return NS_OK; // We'll fall back to MLS.
+ }
+
+ if (mPollRunnable) {
+ return NS_OK; // already running
+ }
+
+ RefPtr<PollRunnable> pollRunnable =
+ MakeAndAddRef<PollRunnable>(nsMainThreadPtrHandle<GpsdLocationProvider>(
+ new nsMainThreadPtrHolder<GpsdLocationProvider>("GpsdLP", this)));
+
+ // Use existing poll thread...
+ RefPtr<LazyIdleThread> pollThread = mPollThread;
+
+ // ... or create a new one.
+ if (!pollThread) {
+ pollThread = MakeAndAddRef<LazyIdleThread>(GPSD_POLL_THREAD_TIMEOUT_MS,
+ "Gpsd poll thread",
+ LazyIdleThread::ManualShutdown);
+ }
+
+ auto rv = pollThread->Dispatch(pollRunnable, NS_DISPATCH_NORMAL);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mPollRunnable = pollRunnable.forget();
+ mPollThread = pollThread.forget();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GpsdLocationProvider::Watch(nsIGeolocationUpdate* aCallback) {
+ mCallback = aCallback;
+
+ /* The MLS fallback will kick in after a few seconds if gpsd
+ * doesn't provide location information within time. Once we
+ * see the first message from gpsd, the fallback will be
+ * disabled in |Update|.
+ */
+ mMLSProvider = MakeAndAddRef<MLSFallback>();
+ mMLSProvider->Startup(new MLSGeolocationUpdate(aCallback));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GpsdLocationProvider::Shutdown() {
+ if (mMLSProvider) {
+ mMLSProvider->Shutdown();
+ mMLSProvider = nullptr;
+ }
+
+ if (!mPollRunnable) {
+ return NS_OK; // not running
+ }
+
+ mPollRunnable->StopRunning();
+ mPollRunnable = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GpsdLocationProvider::SetHighAccuracy(bool aHigh) { return NS_OK; }
+
+} // namespace dom
+} // namespace mozilla