Bug 1250922: Add gpsd geolocation provider on Linux, r=jdm,glandium
authorThomas Zimmermann <tdz@users.sourceforge.net>
Fri, 22 Jul 2016 11:52:09 +0200
changeset 346380 76b04196acceb86e55d7a2cdc2fa62f53a756f14
parent 346379 13a135813767793c0ac1d185d4983b222b29d9c4
child 346381 a1a42e8d66e905fcd99042d878b2cb56f6b137ad
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm, glandium
bugs1250922
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1250922: Add gpsd geolocation provider on Linux, r=jdm,glandium Gpsd is the GPS daemon on Linux. It implements support for a wide range of GPS receivers. This patch adds support for gpsd to the Geolocation module. The build system can now test for libgps, which provides the public interface to gpsd's functionality. If found, |GpsdLocationProvider| is added to the build. MozReview-Commit-ID: 1kBgFdEZePI
config/system-headers
dom/system/linux/GpsdLocationProvider.cpp
dom/system/linux/GpsdLocationProvider.h
dom/system/linux/moz.build
dom/system/moz.build
toolkit/moz.configure
--- a/config/system-headers
+++ b/config/system-headers
@@ -493,16 +493,17 @@ gdk-pixbuf/gdk-pixbuf.h
 Gestalt.h
 getopt.h
 glibconfig.h
 glib.h
 glib-object.h
 gmodule.h
 gnome.h
 gnu/libc-version.h
+gps.h
 grp.h
 gssapi_generic.h
 gssapi/gssapi_generic.h
 gssapi/gssapi.h
 gssapi.h
 gtk/gtk.h
 gtk/gtkx.h
 gtk/gtkunixprint.h
new file mode 100644
--- /dev/null
+++ b/dom/system/linux/GpsdLocationProvider.cpp
@@ -0,0 +1,375 @@
+/* -*- 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 "mozilla/Atomics.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/LazyIdleThread.h"
+#include "nsGeoPosition.h"
+#include "nsIDOMGeoPositionError.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+//
+// UpdateRunnable
+//
+
+class GpsdLocationProvider::UpdateRunnable final : public Runnable
+{
+public:
+  UpdateRunnable(
+    const nsMainThreadPtrHandle<GpsdLocationProvider>& aLocationProvider,
+    nsIDOMGeoPosition* aPosition)
+    : 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)
+    : 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)
+    : mLocationProvider(aLocationProvider)
+    , mRunning(true)
+  {
+    MOZ_ASSERT(mLocationProvider);
+  }
+
+  bool IsRunning() const
+  {
+    return mRunning;
+  }
+
+  void StopRunning()
+  {
+    mRunning = false;
+  }
+
+  // nsIRunnable
+  //
+
+  NS_IMETHOD Run() override
+  {
+    int err;
+
+    switch (GPSD_API_MAJOR_VERSION) {
+      case 5:
+        err = PollLoop5();
+        break;
+      default:
+        err = nsIDOMGeoPositionError::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
+    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;
+
+    double lat = -1;
+    double lon = -1;
+    double alt = -1;
+    double hError = -1;
+    double vError = -1;
+    double heading = -1;
+    double speed = -1;
+    long long timestamp = 0;
+
+    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 */
+      }
+
+      res = gps_read(&gpsData);
+
+      if (res < 0) {
+        err = ErrnoToError(errno);
+        break;
+      } else if (!res) {
+        continue; /* no data available */
+      }
+
+      if (gpsData.status == STATUS_NO_FIX) {
+        continue;
+      }
+
+      switch (gpsData.fix.mode) {
+        case MODE_3D:
+          if (!IsNaN(gpsData.fix.altitude)) {
+            alt = gpsData.fix.altitude;
+          }
+          MOZ_FALLTHROUGH;
+        case MODE_2D:
+          if (!IsNaN(gpsData.fix.latitude)) {
+            lat = gpsData.fix.latitude;
+          }
+          if (!IsNaN(gpsData.fix.longitude)) {
+            lon = gpsData.fix.longitude;
+          }
+          if (!IsNaN(gpsData.fix.epx) && !IsNaN(gpsData.fix.epy)) {
+            hError = std::max(gpsData.fix.epx, gpsData.fix.epy);
+          } else if (!IsNaN(gpsData.fix.epx)) {
+            hError = gpsData.fix.epx;
+          } else if (!IsNaN(gpsData.fix.epy)) {
+            hError = gpsData.fix.epy;
+          }
+          if (!IsNaN(gpsData.fix.altitude)) {
+            alt = gpsData.fix.altitude;
+          }
+          if (!IsNaN(gpsData.fix.epv)) {
+            vError = gpsData.fix.epv;
+          }
+          if (!IsNaN(gpsData.fix.track)) {
+            heading = gpsData.fix.track;
+          }
+          if (!IsNaN(gpsData.fix.speed)) {
+            speed = gpsData.fix.speed;
+          }
+          break;
+        default:
+          continue; // There's no useful data in this fix; continue.
+      }
+
+      timestamp = PR_Now() / PR_USEC_PER_MSEC; // convert to milliseconds
+
+      NS_DispatchToMainThread(
+        MakeAndAddRef<UpdateRunnable>(mLocationProvider,
+                                      new nsGeoPosition(lat, lon, alt,
+                                                        hError, vError,
+                                                        heading, speed,
+                                                        timestamp)));
+    }
+
+    gps_stream(&gpsData, WATCH_DISABLE, NULL);
+    gps_close(&gpsData);
+
+    return err;
+#else
+    return nsIDOMGeoPositionError::POSITION_UNAVAILABLE;
+#endif // GPSD_MAJOR_API_VERSION
+  }
+
+  static int ErrnoToError(int aErrno)
+  {
+    switch (aErrno) {
+      case EACCES:
+          MOZ_FALLTHROUGH;
+      case EPERM:
+          MOZ_FALLTHROUGH;
+      case EROFS:
+        return nsIDOMGeoPositionError::PERMISSION_DENIED;
+      case ETIME:
+          MOZ_FALLTHROUGH;
+      case ETIMEDOUT:
+        return nsIDOMGeoPositionError::TIMEOUT;
+      default:
+        return nsIDOMGeoPositionError::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
+  }
+
+  mCallback->Update(aPosition);
+}
+
+void
+GpsdLocationProvider::NotifyError(int aError)
+{
+  if (!mCallback) {
+    return; // not initialized or already shut down
+  }
+
+  mCallback->NotifyError(aError);
+}
+
+// nsISupports
+//
+
+NS_IMPL_ISUPPORTS(GpsdLocationProvider, nsIGeolocationProvider)
+
+// nsIGeolocationProvider
+//
+
+NS_IMETHODIMP
+GpsdLocationProvider::Startup()
+{
+  if (mPollRunnable) {
+    return NS_OK; // already running
+  }
+
+  RefPtr<PollRunnable> pollRunnable =
+    MakeAndAddRef<PollRunnable>(
+      nsMainThreadPtrHandle<GpsdLocationProvider>(
+        new nsMainThreadPtrHolder<GpsdLocationProvider>(this)));
+
+  // Use existing poll thread...
+  RefPtr<LazyIdleThread> pollThread = mPollThread;
+
+  // ... or create a new one.
+  if (!pollThread) {
+    pollThread = MakeAndAddRef<LazyIdleThread>(
+      GPSD_POLL_THREAD_TIMEOUT_MS,
+      NS_LITERAL_CSTRING("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;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+GpsdLocationProvider::Shutdown()
+{
+  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
new file mode 100644
--- /dev/null
+++ b/dom/system/linux/GpsdLocationProvider.h
@@ -0,0 +1,48 @@
+/* -*- 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/. */
+
+#ifndef GpsdLocationProvider_h
+#define GpsdLocationProvider_h
+
+#include "nsCOMPtr.h"
+#include "nsGeolocation.h"
+#include "nsIGeolocationProvider.h"
+
+namespace mozilla {
+
+class LazyIdleThread;
+
+namespace dom {
+
+class GpsdLocationProvider final : public nsIGeolocationProvider
+{
+  class NotifyErrorRunnable;
+  class PollRunnable;
+  class UpdateRunnable;
+
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIGEOLOCATIONPROVIDER
+
+  GpsdLocationProvider();
+
+private:
+  ~GpsdLocationProvider();
+
+  void Update(nsIDOMGeoPosition* aPosition);
+  void NotifyError(int aError);
+
+  static const uint32_t GPSD_POLL_THREAD_TIMEOUT_MS;
+
+  nsCOMPtr<nsIGeolocationUpdate> mCallback;
+  RefPtr<LazyIdleThread> mPollThread;
+  RefPtr<PollRunnable> mPollRunnable;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* GpsLocationProvider_h */
new file mode 100644
--- /dev/null
+++ b/dom/system/linux/moz.build
@@ -0,0 +1,20 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+if CONFIG['MOZ_GPSD']:
+    SOURCES += [
+        'GpsdLocationProvider.cpp'
+    ]
+
+    CXXFLAGS += CONFIG['MOZ_GPSD_CFLAGS']
+
+    OS_LIBS += CONFIG['MOZ_GPSD_LIBS']
+
+    LOCAL_INCLUDES += [
+        '/dom/geolocation'
+    ]
+
+FINAL_LIBRARY = 'xul'
--- a/dom/system/moz.build
+++ b/dom/system/moz.build
@@ -9,16 +9,18 @@ toolkit = CONFIG['MOZ_WIDGET_TOOLKIT']
 if toolkit == 'windows':
     DIRS += ['windows']
 elif toolkit == 'cocoa':
     DIRS += ['mac']
 elif toolkit == 'android':
     DIRS += ['android']
 elif toolkit == 'gonk':
     DIRS += ['gonk']
+elif toolkit in ('gtk2', 'gtk3'):
+    DIRS += ['linux']
 
 XPIDL_SOURCES += [
     'nsIOSFileConstantsService.idl',
     'nsISystemUpdateProvider.idl',
 ]
 
 XPIDL_MODULE = 'dom_system'
 
--- a/toolkit/moz.configure
+++ b/toolkit/moz.configure
@@ -430,14 +430,27 @@ project_flag('MOZ_ANDROID_HISTORY',
              help='Enable Android History instead of Places',
              set_as_define=True)
 
 @depends('MOZ_PLACES', 'MOZ_ANDROID_HISTORY')
 def check_places_and_android_history(places, android_history):
     if places and android_history:
         die('Cannot use MOZ_ANDROID_HISTORY alongside MOZ_PLACES.')
 
+# gpsd support
+# ==============================================================
+option('--enable-gpsd', env='MOZ_GPSD',
+       help='Enable gpsd support')
+
+@depends('--enable-gpsd')
+def gpsd(value):
+    return bool(value)
+
+system_gpsd = pkg_check_modules('MOZ_GPSD', 'libgps >= 3.11', gpsd)
+
+set_config('MOZ_GPSD', system_gpsd)
+
 # Miscellaneous programs
 # ==============================================================
 
 check_prog('TAR', ('gnutar', 'gtar', 'tar'))
 check_prog('UNZIP', ('unzip',))
 check_prog('ZIP', ('zip',))