diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 070da72b3e5e9e8500118456bdd4d5c66c6d5c83..bea3b6931acfdecd4bc93e8ec3debd848a618b57 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,23 +1,13 @@
 before_script:
     -  sed -i '/^#\sdeb-src /s/^#//' '/etc/apt/sources.list'
     - apt-get --allow-unauthenticated update && apt-get build-dep --yes geoclue-2.0
-    - apt-get install --yes git gobject-introspection libmm-glib-dev wget
+    - apt-get install --yes git gobject-introspection libmm-glib-dev wget valac
     - apt-get install --yes libnotify-dev xsltproc gtk-doc-tools python3-pip
     - apt-get install --yes ninja-build gettext modemmanager-dev
     - pip3 install meson==0.53.2
 
 # Ubuntu 14.04 is not supported, see README for details
 #
-# FIXME: Also disable gtk-doc on Ubuntu 16.04 cause it fails install. Would be nice to get it fixed.
-
-ubuntu-16.04:
-    image: ubuntu:xenial
-    artifacts:
-        when: always
-        name: "xenial-${CI_COMMIT_REF_NAME}"
-        paths:
-            - "${CI_PROJECT_DIR}/build"
-    script: meson -Dgtk-doc=false build && ninja -C build && ninja -C build test && ninja -C build install
 
 ubuntu-18.04:
     image: ubuntu:bionic
diff --git a/NEWS b/NEWS
index 6b2856da22b3c098e00bccd85c220e4da0b7ce0d..9a6d731dabebfd1f6093d8e0a67d643949fd1e6f 100644
--- a/NEWS
+++ b/NEWS
@@ -1,8 +1,43 @@
+2.6.0
+=====
+
+- Stop the client for system applications when accuracy is set to NONE
+- NMEA: add a unix socket file option
+- Client info: support cgroup v2
+- Don't compute movements for low accuracy sources
+- Add an option to generate vapi
+- Send the 3G tower type as part of the Mozilla location service requests
+- Add phosh & lipstick as allowed agents
+- Use GeoIP when no WiFi device is available
+- Modem manager: add polkit rule to allow GPS access
+- Allow disabling compass at build and at runtime
+- Fix heading computation for identical locations
+- Be strict with time and distance threshold 
+- Fix the XDG location portal integration
+- Replace agent wait timeout with a queue 
+- Other bugs fixes
+
+Contributors:
+
+- Laurent Bigonville
+- Angus Ainslie
+- Dan Nicholson
+- Guido Günther 
+- Jan Alexander Steffens
+- Abderrahim Kitouni 
+- clayton craft 
+- Ian Douglas Scott
+- Chupligin Sergey
+- Dor Askayo
+- Teemu Ikonen
+- Maciej S. Szmigiero
+- Álvaro Peña
+- Bilal Elmoussaoui
+
 2.5.7
 =====
 
-Many fixes. Can't bother to list them. You can get the list of changes from git.
-
+- A bug fix release, many fixing a bunch of memory leaks.
 
 2.5.6
 =====
@@ -12,10 +47,10 @@ Many fixes. Can't bother to list them. You can get the list of changes from git.
 
 Contributors:
 
-Amaury Pouly <amaury.pouly@gmail.com>
-Jan Alexander Steffens (heftig) <jan.steffens@gmail.com>
-Will Thompson <will@willthompson.co.uk>
-Zeeshan Ali <zeeshanak@gnome.org>
+- Amaury Pouly amaury.pouly@gmail.com
+- Jan Alexander Steffens (heftig) jan.steffens@gmail.com
+- Will Thompson will@willthompson.co.uk
+- Zeeshan Ali zeeshanak@gnome.org
 
 2.5.5
 =====
diff --git a/README.md b/README.md
index 0ea9657b230df4f32d279951487037190cd5488b..6242120dc4deab9fb0233edfdbc1b0bfcd962e3c 100644
--- a/README.md
+++ b/README.md
@@ -31,9 +31,7 @@ been dropped in favour of the
 # History
 Geoclue was started during the GNOME Summit 2006 in Boston. At least 
 Keith Preston and Tuomas Kuosmanen can be blamed. There was a total rewrite 
-after version 0.9. 
-
-Use tag "0.9" (as in git checkout 0.9) if you need the old code.
+after version 0.9.
 
 There was yet another rewrite that we call geoclue2. The first version to 
 introduce the re-write was version 1.99.
diff --git a/data/geoclue.conf.in b/data/geoclue.conf.in
index fb111de59a05ff7f50a40055844db8edbae22744..650470d72bed4c1123e792b7444fbb065ece8f2a 100644
--- a/data/geoclue.conf.in
+++ b/data/geoclue.conf.in
@@ -10,7 +10,7 @@
 
 # Whitelist of desktop IDs (without .desktop part) of all agents we recognise,
 # separated by a ';'.
-whitelist=@demo_agent@gnome-shell;io.elementary.desktop.agent-geoclue2
+whitelist=@demo_agent@gnome-shell;io.elementary.desktop.agent-geoclue2;sm.puri.Phosh;lipstick
 
 # Network NMEA source configuration options
 [network-nmea]
@@ -18,6 +18,9 @@ whitelist=@demo_agent@gnome-shell;io.elementary.desktop.agent-geoclue2
 # Fetch location from NMEA sources on local network?
 enable=true
 
+# use aa nmea unix socket as the data source
+# nmea-socket=/var/run/gps-share.sock
+
 # 3G source configuration options
 [3g]
 
@@ -42,13 +45,13 @@ enable=true
 # Enable WiFi source
 enable=true
 
-# URL to the wifi geolocation service. The key can currenty be anything, just
-# needs to be present but that is likely going to change in future.
-url=https://location.services.mozilla.com/v1/geolocate?key=geoclue
+# URL to the WiFi geolocation service. If not set, defaults to Mozilla's
+# Location Service with a hardcoded key. To use a custom key, uncomment this URL
+# while changing YOUR_KEY to your MLS API key.
+#url=https://location.services.mozilla.com/v1/geolocate?key=YOUR_KEY
 
-# To use the Google geolocation service instead of mozilla's, simply uncomment
-# this url while changing API_KEY to your Google API key and comment out or
-# remove the url above.
+# To use the Google geolocation service instead of Mozilla's, uncomment this URL
+# while changing YOUR_KEY to your Google API key.
 #
 # WARNING: Please make sure that you are complying with the Google's ToS and
 #          policies if you uncomment this:
@@ -63,12 +66,20 @@ url=https://location.services.mozilla.com/v1/geolocate?key=geoclue
 #
 submit-data=false
 
-# URL to submission API of Mozilla Location Service
-submission-url=https://location.services.mozilla.com/v1/submit?key=geoclue
+# URL to submission API of Mozilla Location Service. If not set, defaults to
+# Mozilla's API with a hardcoded key. To use a custom key, uncomment this URL
+# while changing YOUR_KEY to your MLS API key.
+#submission-url=https://location.services.mozilla.com/v1/submit?key=YOUR_KEY
 
 # A nickname to submit network data with. A nickname must be 2-32 characters long.
 submission-nick=geoclue
 
+# Compass configuration options
+[compass]
+
+# Enable Compass
+enable=true
+
 # Application configuration options
 #
 # NOTE: Having an entry here for an application with allowed=true means that
@@ -117,6 +128,11 @@ allowed=true
 system=true
 users=
 
+[sm.puri.Phosh]
+allowed=true
+system=true
+users=
+
 [epiphany]
 allowed=true
 system=false
@@ -126,3 +142,8 @@ users=
 allowed=true
 system=false
 users=
+
+[lipstick]
+allowed=true
+system=true
+users=
diff --git a/data/meson.build b/data/meson.build
index c189753a60171c0cc5d45013f95a92409553a6b1..a1fc61f18723837d0b25e008e93038c66767ebdf 100644
--- a/data/meson.build
+++ b/data/meson.build
@@ -58,4 +58,12 @@ if get_option('enable-backend')
                        configuration: conf,
                        install_dir: systemd_unit_dir)
     endif
+
+    if get_option('modem-gps-source')
+        polkit_dir = join_paths(get_option('datadir'), 'polkit-1', 'rules.d')
+        configure_file(output: 'org.freedesktop.GeoClue2.rules',
+                       input: 'org.freedesktop.GeoClue2.rules.in',
+                       configuration: conf,
+                       install_dir: polkit_dir)
+    endif
 endif
diff --git a/data/org.freedesktop.GeoClue2.rules.in b/data/org.freedesktop.GeoClue2.rules.in
new file mode 100644
index 0000000000000000000000000000000000000000..511e46f486b889ecb50499f572e7d141b36bb5d2
--- /dev/null
+++ b/data/org.freedesktop.GeoClue2.rules.in
@@ -0,0 +1,7 @@
+polkit.addRule(function(action, subject) {
+        if ((action.id == "org.freedesktop.ModemManager1.Device.Control" ||
+             action.id == "org.freedesktop.ModemManager1.Location") &&
+            subject.user == "@dbus_srv_user@") {
+                return polkit.Result.YES;
+        }
+});
diff --git a/debian/changelog b/debian/changelog
index 8cf8a3558e3bbfadd4c34e51c2c418a62bc0dda4..d6c313f77b6afe10b353977af5fb3f7f0a396673 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,28 @@
+geoclue-2.0 (2.6.0-2) unstable; urgency=medium
+
+  [ Laurent Bigonville ]
+  * debian/geoclue-2.0.lintian-overrides: Fix dbus-policy-excessively-broad
+    override
+  * debian/geoclue-2.0.install: Install polkit rules shipped by upstream
+  * debian/geoclue-2.0.install: Rename the .pkla file to match the name
+    of the upstream rules file
+
+  [ Jeremy Bicha ]
+  * Cherry-pick proposed patch to build with libsoup3
+  * Build-Depend on libsoup-3.0-dev
+
+ -- Jeremy Bicha <jbicha@ubuntu.com>  Wed, 14 Sep 2022 11:46:47 -0400
+
+geoclue-2.0 (2.6.0-1) unstable; urgency=medium
+
+  * New upstream version 2.6.0
+    - debian/patches/: Drop all patches, they (or similar patches) have been
+      merged upstream
+    - debian/libgeoclue-2-0.symbols: Add newly exported symbols
+  * debian/control: Bump Standards-Version to 4.6.0 (no further changes)
+
+ -- Laurent Bigonville <bigon@debian.org>  Tue, 05 Apr 2022 14:47:54 +0200
+
 geoclue-2.0 (2.5.7-3) unstable; urgency=medium
 
   * Properly detect that applications are running in flatpak when using cgroup
diff --git a/debian/control b/debian/control
index 5b702b7ad50bbf2c0d8bbec24a74d074aa33702f..c9d72a3ad8b587cc6ba193c00ec8bc668c943d62 100644
--- a/debian/control
+++ b/debian/control
@@ -12,13 +12,13 @@ Build-Depends: debhelper-compat (= 12),
                libjson-glib-dev (>= 0.14),
                libmm-glib-dev (>= 1.6) [linux-any],
                libnotify-dev,
-               libsoup2.4-dev (>= 2.42),
+               libsoup-3.0-dev,
                meson (>= 0.47.2),
                pkg-config,
                valac
 Build-Depends-Indep: gtk-doc-tools <!nodoc>, libglib2.0-doc <!nodoc>
 Homepage: https://gitlab.freedesktop.org/geoclue/geoclue/wikis/home
-Standards-Version: 4.5.1
+Standards-Version: 4.6.0
 Vcs-Git: https://salsa.debian.org/freedesktop-team/geoclue-2.0.git
 Vcs-Browser: https://salsa.debian.org/freedesktop-team/geoclue-2.0
 Rules-Requires-Root: no
diff --git a/debian/geoclue-2.0.install b/debian/geoclue-2.0.install
index a0b40fec254defd6dcc1e48f978938c2d0c6dff1..7b7032e243b69cbc55ad389abe28892c8c287e7e 100644
--- a/debian/geoclue-2.0.install
+++ b/debian/geoclue-2.0.install
@@ -1,5 +1,4 @@
-debian/local/geoclue-2.0.pkla var/lib/polkit-1/localauthority/10-vendor.d/
-debian/local/geoclue-2.0.rules usr/share/polkit-1/rules.d/
+debian/local/org.freedesktop.GeoClue2.pkla var/lib/polkit-1/localauthority/10-vendor.d/
 etc/dbus-1/system.d/
 etc/geoclue/
 etc/xdg/autostart/geoclue-demo-agent.desktop
@@ -11,3 +10,4 @@ usr/share/applications/geoclue-demo-agent.desktop
 usr/share/dbus-1/interfaces/
 usr/share/dbus-1/system-services/
 usr/share/man/man5/geoclue.5
+usr/share/polkit-1/rules.d/
diff --git a/debian/geoclue-2.0.lintian-overrides b/debian/geoclue-2.0.lintian-overrides
index f08d829d678f7785a18e65620e883909796cbb5c..499171055e0ed1bae73fe9f402b5bc1cb6273380 100644
--- a/debian/geoclue-2.0.lintian-overrides
+++ b/debian/geoclue-2.0.lintian-overrides
@@ -1,5 +1,5 @@
 # This is not completely fixed but we have mitigated the issue by only allowing
 # the geoclue user to call these methods
-geoclue-2.0: dbus-policy-excessively-broad etc/dbus-1/system.d/org.freedesktop.GeoClue2.Agent.conf <policy user="geoclue"><allow send_interface="org.freedesktop.DBus.Properties" send_path="/org/freedesktop/GeoClue2/Agent"/>
+geoclue-2.0: dbus-policy-excessively-broad etc/dbus-1/system.d/org.freedesktop.GeoClue2.Agent.conf (rule 2) <policy user="geoclue"><allow send_interface="org.freedesktop.DBus.Properties" send_path="/org/freedesktop/GeoClue2/Agent"/>
 # The daemon is D-Bus activated, not started at boot
 geoclue-2.0: package-supports-alternative-init-but-no-init.d-script lib/systemd/system/geoclue.service
diff --git a/debian/libgeoclue-2-0.symbols b/debian/libgeoclue-2-0.symbols
index 5a4b69bb015ddf03fdd079fe7e95a57a7282bddb..d6c5d1c032fa77321d51d4f86ca5c32eb95edb57 100644
--- a/debian/libgeoclue-2-0.symbols
+++ b/debian/libgeoclue-2-0.symbols
@@ -112,3 +112,29 @@ libgeoclue-2.so.0 libgeoclue-2-0 #MINVER#
  gclue_simple_new@Base 2.4.0
  gclue_simple_new_finish@Base 2.4.0
  gclue_simple_new_sync@Base 2.4.0
+ gclue_simple_new_with_thresholds@Base 2.6.0
+ gclue_simple_new_with_thresholds_finish@Base 2.6.0
+ gclue_simple_new_with_thresholds_sync@Base 2.6.0
+ xdp_location_call_create_session@Base 2.6.0
+ xdp_location_call_create_session_finish@Base 2.6.0
+ xdp_location_call_create_session_sync@Base 2.6.0
+ xdp_location_call_start@Base 2.6.0
+ xdp_location_call_start_finish@Base 2.6.0
+ xdp_location_call_start_sync@Base 2.6.0
+ xdp_location_complete_create_session@Base 2.6.0
+ xdp_location_complete_start@Base 2.6.0
+ xdp_location_emit_location_updated@Base 2.6.0
+ xdp_location_get_type@Base 2.6.0
+ xdp_location_get_version@Base 2.6.0
+ xdp_location_interface_info@Base 2.6.0
+ xdp_location_override_properties@Base 2.6.0
+ xdp_location_proxy_get_type@Base 2.6.0
+ xdp_location_proxy_new@Base 2.6.0
+ xdp_location_proxy_new_finish@Base 2.6.0
+ xdp_location_proxy_new_for_bus@Base 2.6.0
+ xdp_location_proxy_new_for_bus_finish@Base 2.6.0
+ xdp_location_proxy_new_for_bus_sync@Base 2.6.0
+ xdp_location_proxy_new_sync@Base 2.6.0
+ xdp_location_set_version@Base 2.6.0
+ xdp_location_skeleton_get_type@Base 2.6.0
+ xdp_location_skeleton_new@Base 2.6.0
diff --git a/debian/local/geoclue-2.0.rules b/debian/local/geoclue-2.0.rules
deleted file mode 100644
index aeb42345098ad8f1c891ce4352b94356c9ec1fa2..0000000000000000000000000000000000000000
--- a/debian/local/geoclue-2.0.rules
+++ /dev/null
@@ -1,7 +0,0 @@
-polkit.addRule(function(action, subject) {
-    if ((action.id == "org.freedesktop.ModemManager1.Device.Control" ||
-         action.id == "org.freedesktop.ModemManager1.Location") &&
-        subject.user == "geoclue") {
-        return polkit.Result.YES;
-    }
-});
diff --git a/debian/local/geoclue-2.0.pkla b/debian/local/org.freedesktop.GeoClue2.pkla
similarity index 100%
rename from debian/local/geoclue-2.0.pkla
rename to debian/local/org.freedesktop.GeoClue2.pkla
diff --git a/debian/patches/0002-config-Make-the-Mozilla-API-key-configurable.patch b/debian/patches/0002-config-Make-the-Mozilla-API-key-configurable.patch
deleted file mode 100644
index 8e7cf32ff1067a8a19ac4730fd052a1ef03ed920..0000000000000000000000000000000000000000
--- a/debian/patches/0002-config-Make-the-Mozilla-API-key-configurable.patch
+++ /dev/null
@@ -1,87 +0,0 @@
-From: "Jan Alexander Steffens (heftig)" <jan.steffens@gmail.com>
-Date: Tue, 1 Oct 2019 13:27:41 +0000
-Subject: config: Make the Mozilla API key configurable
-
-And do not expose it in the configuration file.
----
- data/geoclue.conf.in | 18 ++++++++++--------
- meson.build          |  1 +
- meson_options.txt    |  3 +++
- src/gclue-config.c   |  4 ++--
- 4 files changed, 16 insertions(+), 10 deletions(-)
-
-diff --git a/data/geoclue.conf.in b/data/geoclue.conf.in
-index fb111de..bebe471 100644
---- a/data/geoclue.conf.in
-+++ b/data/geoclue.conf.in
-@@ -42,13 +42,13 @@ enable=true
- # Enable WiFi source
- enable=true
- 
--# URL to the wifi geolocation service. The key can currenty be anything, just
--# needs to be present but that is likely going to change in future.
--url=https://location.services.mozilla.com/v1/geolocate?key=geoclue
-+# URL to the WiFi geolocation service. If not set, defaults to Mozilla's
-+# Location Service with a hardcoded key. To use a custom key, uncomment this URL
-+# while changing YOUR_KEY to your MLS API key.
-+#url=https://location.services.mozilla.com/v1/geolocate?key=YOUR_KEY
- 
--# To use the Google geolocation service instead of mozilla's, simply uncomment
--# this url while changing API_KEY to your Google API key and comment out or
--# remove the url above.
-+# To use the Google geolocation service instead of Mozilla's, uncomment this URL
-+# while changing YOUR_KEY to your Google API key.
- #
- # WARNING: Please make sure that you are complying with the Google's ToS and
- #          policies if you uncomment this:
-@@ -63,8 +63,10 @@ url=https://location.services.mozilla.com/v1/geolocate?key=geoclue
- #
- submit-data=false
- 
--# URL to submission API of Mozilla Location Service
--submission-url=https://location.services.mozilla.com/v1/submit?key=geoclue
-+# URL to submission API of Mozilla Location Service. If not set, defaults to
-+# Mozilla's API with a hardcoded key. To use a custom key, uncomment this URL
-+# while changing YOUR_KEY to your MLS API key.
-+#submission-url=https://location.services.mozilla.com/v1/submit?key=YOUR_KEY
- 
- # A nickname to submit network data with. A nickname must be 2-32 characters long.
- submission-nick=geoclue
-diff --git a/meson.build b/meson.build
-index 088b651..fc41751 100644
---- a/meson.build
-+++ b/meson.build
-@@ -30,6 +30,7 @@ conf.set_quoted('PACKAGE_BUGREPORT', 'https://gitlab.freedesktop.org/geoclue/geo
- conf.set_quoted('TEST_SRCDIR', meson.source_root() + '/data/')
- conf.set_quoted('LOCALEDIR', localedir)
- conf.set_quoted('SYSCONFDIR', sysconfdir)
-+conf.set_quoted('MOZILLA_API_KEY', get_option('mozilla-api-key'))
- conf.set10('GCLUE_USE_3G_SOURCE', get_option('3g-source'))
- conf.set10('GCLUE_USE_CDMA_SOURCE', get_option('cdma-source'))
- conf.set10('GCLUE_USE_MODEM_GPS_SOURCE', get_option('modem-gps-source'))
-diff --git a/meson_options.txt b/meson_options.txt
-index 83bc60e..f5d42e3 100644
---- a/meson_options.txt
-+++ b/meson_options.txt
-@@ -34,3 +34,6 @@ option('systemd-system-unit-dir',
- option('dbus-srv-user',
-        type: 'string', value: 'root',
-        description: 'The user (existing) as which the service will run')
-+option('mozilla-api-key',
-+       type: 'string', value: 'geoclue',
-+       description: 'Your API key for Mozilla Location Service')
-diff --git a/src/gclue-config.c b/src/gclue-config.c
-index 0714d01..76222ff 100644
---- a/src/gclue-config.c
-+++ b/src/gclue-config.c
-@@ -216,8 +216,8 @@ load_enable_source_config (GClueConfig *config,
-         return enable;
- }
- 
--#define DEFAULT_WIFI_URL "https://location.services.mozilla.com/v1/geolocate?key=geoclue"
--#define DEFAULT_WIFI_SUBMIT_URL "https://location.services.mozilla.com/v1/submit?key=geoclue"
-+#define DEFAULT_WIFI_URL "https://location.services.mozilla.com/v1/geolocate?key=" MOZILLA_API_KEY
-+#define DEFAULT_WIFI_SUBMIT_URL "https://location.services.mozilla.com/v1/submit?key=" MOZILLA_API_KEY
- 
- static void
- load_wifi_config (GClueConfig *config)
diff --git a/debian/patches/0003-Revert-Fixed-hang-on-startup-when-client-app-is-a-sy.patch b/debian/patches/0003-Revert-Fixed-hang-on-startup-when-client-app-is-a-sy.patch
deleted file mode 100644
index b1fb101172a36a45bc8c6990c5b5f77b4eb73637..0000000000000000000000000000000000000000
--- a/debian/patches/0003-Revert-Fixed-hang-on-startup-when-client-app-is-a-sy.patch
+++ /dev/null
@@ -1,39 +0,0 @@
-From: Laurent Bigonville <bigon@bigon.be>
-Date: Fri, 25 Dec 2020 13:55:44 +0100
-Subject: Revert "Fixed hang on startup when client app is a system app: avoid
- wasting 20s waiting on agent."
-
-Now that we are requesting the allowed accuracy from the agent again for
-the system apps, we need to be sure the agent is started and connected
-before denying the request.
-
-This reverts commit 57efed15b09c5c78891f6fa1bfe5a7aee64a8fb8.
-
-Fixes: #139
----
- src/gclue-service-manager.c | 4 ----
- 1 file changed, 4 deletions(-)
-
-diff --git a/src/gclue-service-manager.c b/src/gclue-service-manager.c
-index 849debd..020c2af 100644
---- a/src/gclue-service-manager.c
-+++ b/src/gclue-service-manager.c
-@@ -271,7 +271,6 @@ on_client_info_new_ready (GObject      *source_object,
-         GError *error = NULL;
-         guint32 user_id;
-         gint64 now;
--        gboolean system_app;
- 
-         info = gclue_client_info_new_finish (res, &error);
-         if (info == NULL) {
-@@ -291,10 +290,7 @@ on_client_info_new_ready (GObject      *source_object,
-         agent_proxy = g_hash_table_lookup (priv->agents,
-                                            GINT_TO_POINTER (user_id));
-         now = g_get_monotonic_time ();
--
--        system_app = (gclue_client_info_get_xdg_id (info) == NULL);
-         if (agent_proxy == NULL &&
--            !system_app &&
-             now < (priv->init_time + AGENT_WAIT_TIMEOUT_USEC)) {
-                 /* Its possible that geoclue was just launched on GetClient
-                  * call, in which case agents need some time to register
diff --git a/debian/patches/0003-service-Sync-in_use-property-when-apps-get-connected.patch b/debian/patches/0003-service-Sync-in_use-property-when-apps-get-connected.patch
deleted file mode 100644
index 1f5fcb6ee91cea72a15e6aa2b30e7f32d7fde086..0000000000000000000000000000000000000000
--- a/debian/patches/0003-service-Sync-in_use-property-when-apps-get-connected.patch
+++ /dev/null
@@ -1,43 +0,0 @@
-From: Sujanan Bhathiya <sujananbhathiya@gmail.com>
-Date: Mon, 16 Mar 2020 17:19:30 +0530
-Subject: service: Sync in_use property when apps get connected
-
-Earlier we only synced in_use property when apps
-get deleted from the service. Here we set the in_use
-property when apps get connected to the service as well.
-This fixes the issue #112
----
- src/gclue-service-manager.c | 13 +++++++++++++
- 1 file changed, 13 insertions(+)
-
-diff --git a/src/gclue-service-manager.c b/src/gclue-service-manager.c
-index d7f5e55..849debd 100644
---- a/src/gclue-service-manager.c
-+++ b/src/gclue-service-manager.c
-@@ -140,6 +140,14 @@ delete_client (GClueServiceManager *manager,
-         sync_in_use_property (manager);
- }
- 
-+static void
-+on_client_notify_active (GObject    *gobject,
-+                         GParamSpec *pspec,
-+                         gpointer    user_data)
-+{
-+        sync_in_use_property (GCLUE_SERVICE_MANAGER (user_data));
-+}
-+
- static void
- on_peer_vanished (GClueClientInfo *info,
-                   gpointer         user_data)
-@@ -216,6 +224,11 @@ complete_get_client (OnClientInfoNewReadyData *data)
-         }
-         g_debug ("Number of connected clients: %u", priv->num_clients);
- 
-+        g_signal_connect (client,
-+                          "notify::active",
-+                          G_CALLBACK (on_client_notify_active),
-+                          data->manager);
-+
-         g_signal_connect (info,
-                           "peer-vanished",
-                           G_CALLBACK (on_peer_vanished),
diff --git a/debian/patches/0004-service-Stop-the-client-for-system-applications-when.patch b/debian/patches/0004-service-Stop-the-client-for-system-applications-when.patch
deleted file mode 100644
index d79ab475d12eb7b1a05b3ffd9ee8677ceeb9fef5..0000000000000000000000000000000000000000
--- a/debian/patches/0004-service-Stop-the-client-for-system-applications-when.patch
+++ /dev/null
@@ -1,43 +0,0 @@
-From: Laurent Bigonville <bigon@bigon.be>
-Date: Fri, 25 Dec 2020 14:13:05 +0100
-Subject: service: Stop the client for system applications when accuracy is
- set to 0
-
-Now that the accuracy set by the agent is used again for system
-applications, we should again stop the client when the accuracy is set
-to 0
----
- src/gclue-service-client.c | 6 +-----
- 1 file changed, 1 insertion(+), 5 deletions(-)
-
-diff --git a/src/gclue-service-client.c b/src/gclue-service-client.c
-index 62ea932..3e68f55 100644
---- a/src/gclue-service-client.c
-+++ b/src/gclue-service-client.c
-@@ -330,7 +330,6 @@ on_agent_props_changed (GDBusProxy *agent_proxy,
-         while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) {
-                 GClueAccuracyLevel max_accuracy;
-                 const char *id;
--                gboolean system_app;
- 
-                 if (strcmp (key, "MaxAccuracyLevel") != 0)
-                         continue;
-@@ -338,8 +337,6 @@ on_agent_props_changed (GDBusProxy *agent_proxy,
-                 gdbus_client = GCLUE_DBUS_CLIENT (client);
-                 id = gclue_dbus_client_get_desktop_id (gdbus_client);
-                 max_accuracy = g_variant_get_uint32 (value);
--                system_app = (gclue_client_info_get_xdg_id
--                              (client->priv->client_info) == NULL);
-                 /* FIXME: We should be handling all values of max accuracy
-                  *        level here, not just 0 and non-0.
-                  */
-@@ -354,8 +351,7 @@ on_agent_props_changed (GDBusProxy *agent_proxy,
-                         start_client (client, accuracy);
-                         g_debug ("Re-started '%s'.", id);
-                 } else if (max_accuracy == 0 &&
--                           gclue_dbus_client_get_active (gdbus_client) &&
--                           !system_app) {
-+                           gclue_dbus_client_get_active (gdbus_client)) {
-                         stop_client (client);
-                         client->priv->agent_stopped = TRUE;
-                         g_debug ("Stopped '%s'.", id);
diff --git a/debian/patches/0006-client-info-Support-cgroup-v2.patch b/debian/patches/0006-client-info-Support-cgroup-v2.patch
deleted file mode 100644
index 2f0760ba528e20650d589976c2bdf5d787cb348f..0000000000000000000000000000000000000000
--- a/debian/patches/0006-client-info-Support-cgroup-v2.patch
+++ /dev/null
@@ -1,93 +0,0 @@
-From: =?utf-8?q?Guido_G=C3=BCnther?= <agx@sigxcpu.org>
-Date: Tue, 16 Mar 2021 12:22:30 +0100
-Subject: client-info: Support cgroup v2
-MIME-Version: 1.0
-Content-Type: text/plain; charset="utf-8"
-Content-Transfer-Encoding: 8bit
-
-For v2 cgroups the /proc/<pid>/cgroup format changed to a single line¹.
-Support this too to not misdetect flatpaks as system apps.
-
-1) See https://www.kernel.org/doc/html/v4.18/admin-guide/cgroup-v2.html#processes
-
-Signed-off-by: Guido Günther <agx@sigxcpu.org>
----
- src/gclue-client-info.c | 44 +++++++++++++++++++++++++++++++++++++++++---
- 1 file changed, 41 insertions(+), 3 deletions(-)
-
-diff --git a/src/gclue-client-info.c b/src/gclue-client-info.c
-index d609b34..dd403a6 100644
---- a/src/gclue-client-info.c
-+++ b/src/gclue-client-info.c
-@@ -181,6 +181,42 @@ on_name_vanished (GDBusConnection *connection,
-                        0);
- }
- 
-+
-+static gchar *
-+parse_cgroup_v2 (GStrv lines)
-+{
-+        const char *unit, *name;
-+        char *dash, *xdg_id;
-+        g_autofree char *scope = NULL;
-+
-+        /* Cgroup v2 is always a single line:
-+         * 0::/user.slice/user-1000.slice/user@1000.service/app.slice/app-flatpak-org.gnome.Maps-3358.scope
-+         */
-+        if (g_strv_length (lines) != 2)
-+                return NULL;
-+
-+        if (!g_str_has_prefix (lines[0], "0::"))
-+                 return NULL;
-+
-+        unit = lines[0] + strlen ("0::");
-+        scope = g_path_get_basename (unit);
-+        if (!g_str_has_prefix (scope, "app-flatpak-") ||
-+            !g_str_has_suffix (scope, ".scope"))
-+                return NULL;
-+
-+        name = scope + strlen("app-flatpak-");
-+        dash = strchr (name, '-');
-+        if (dash == NULL)
-+                return NULL;
-+        *dash = 0;
-+
-+        xdg_id = g_strdup (name);
-+        g_debug ("Found xdg_id %s", xdg_id);
-+
-+        return xdg_id;
-+}
-+
-+
- /* Based on got_credentials_cb() from xdg-app source code */
- static char *
- get_xdg_id (guint32 pid)
-@@ -188,7 +224,7 @@ get_xdg_id (guint32 pid)
-         char *xdg_id = NULL;
-         g_autofree char *path = NULL;
-         g_autofree char *content = NULL;
--        gchar **lines;
-+        g_auto(GStrv) lines = NULL;
-         int i;
- 
-         path = g_strdup_printf ("/proc/%u/cgroup", pid);
-@@ -197,6 +233,10 @@ get_xdg_id (guint32 pid)
-                 return NULL;
-         lines =  g_strsplit (content, "\n", -1);
- 
-+	xdg_id = parse_cgroup_v2 (lines);
-+	if (xdg_id != NULL)
-+		return xdg_id;
-+
-         for (i = 0; lines[i] != NULL; i++) {
-                 const char *unit = lines[i] + strlen ("1:name=systemd:");
-                 g_autofree char *scope = NULL;
-@@ -224,8 +264,6 @@ get_xdg_id (guint32 pid)
-                 xdg_id = g_strdup (name);
-         }
- 
--        g_strfreev (lines);
--
-         return xdg_id;
- }
- 
diff --git a/debian/patches/Add-support-for-building-with-libsoup3.patch b/debian/patches/Add-support-for-building-with-libsoup3.patch
new file mode 100644
index 0000000000000000000000000000000000000000..d2c88049c2d9dee9e01e9cae1203b55da3b542af
--- /dev/null
+++ b/debian/patches/Add-support-for-building-with-libsoup3.patch
@@ -0,0 +1,294 @@
+From: Carlos Garcia Campos <cgarcia@igalia.com>
+Date: Tue, 23 Mar 2021 13:00:00 +0100
+Subject: Add support for building with libsoup3
+
+Remove soup2 build option, based on
+https://gitlab.freedesktop.org/geoclue/geoclue/-/merge_requests/83
+
+Original commit message:
+
+Add soup2 build option, enabled by default. When disabled, libsoup3 will
+be used instead.
+
+https://gitlab.freedesktop.org/geoclue/geoclue/-/merge_requests/129
+---
+ src/gclue-mozilla.c    |  20 ++++-----
+ src/gclue-web-source.c | 120 ++++++++++++++++++++++++++-----------------------
+ src/meson.build        |   2 +-
+ 3 files changed, 74 insertions(+), 68 deletions(-)
+
+diff --git a/src/gclue-mozilla.c b/src/gclue-mozilla.c
+index 8e602c1..3ee02c8 100644
+--- a/src/gclue-mozilla.c
++++ b/src/gclue-mozilla.c
+@@ -151,6 +151,7 @@ gclue_mozilla_create_query (GList        *bss_list, /* As in Access Points */
+         guint n_non_ignored_bsss;
+         GList *iter;
+         gint64 mcc, mnc;
++        g_autoptr(GBytes) body = NULL;
+ 
+         builder = json_builder_new ();
+         json_builder_begin_object (builder);
+@@ -238,11 +239,8 @@ gclue_mozilla_create_query (GList        *bss_list, /* As in Access Points */
+ 
+         uri = get_url ();
+         ret = soup_message_new ("POST", uri);
+-        soup_message_set_request (ret,
+-                                  "application/json",
+-                                  SOUP_MEMORY_TAKE,
+-                                  data,
+-                                  data_len);
++        body = g_bytes_new_take (data, data_len);
++        soup_message_set_request_body_from_bytes (ret, "application/json", body);
+         g_debug ("Sending following request to '%s':\n%s", uri, data);
+ 
+         return ret;
+@@ -322,6 +320,7 @@ gclue_mozilla_create_submit_query (GClueLocation   *location,
+                                    GError         **error)
+ {
+         SoupMessage *ret = NULL;
++        SoupMessageHeaders *request_headers;
+         JsonBuilder *builder;
+         JsonGenerator *generator;
+         JsonNode *root_node;
+@@ -332,6 +331,7 @@ gclue_mozilla_create_submit_query (GClueLocation   *location,
+         gdouble lat, lon, accuracy, altitude;
+         GDateTime *datetime;
+         gint64 mcc, mnc;
++        g_autoptr(GBytes) body = NULL;
+ 
+         url = get_submit_config (&nick);
+         if (url == NULL)
+@@ -448,15 +448,13 @@ gclue_mozilla_create_submit_query (GClueLocation   *location,
+         g_object_unref (generator);
+ 
+         ret = soup_message_new ("POST", url);
++        request_headers = soup_message_get_request_headers (ret);
+         if (nick != NULL && nick[0] != '\0')
+-                soup_message_headers_append (ret->request_headers,
++                soup_message_headers_append (request_headers,
+                                              "X-Nickname",
+                                              nick);
+-        soup_message_set_request (ret,
+-                                  "application/json",
+-                                  SOUP_MEMORY_TAKE,
+-                                  data,
+-                                  data_len);
++        body = g_bytes_new_take (data, data_len);
++        soup_message_set_request_body_from_bytes (ret, "application/json", body);
+         g_debug ("Sending following request to '%s':\n%s", url, data);
+ 
+ out:
+diff --git a/src/gclue-web-source.c b/src/gclue-web-source.c
+index bfd70f7..2c98f99 100644
+--- a/src/gclue-web-source.c
++++ b/src/gclue-web-source.c
+@@ -59,9 +59,9 @@ G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GClueWebSource,
+                                   GCLUE_TYPE_LOCATION_SOURCE,
+                                   G_ADD_PRIVATE (GClueWebSource))
+ 
+-static void refresh_callback (SoupSession *session,
+-                              SoupMessage *query,
+-                              gpointer     user_data);
++static void refresh_callback (SoupSession  *session,
++                              GAsyncResult *result,
++                              gpointer      user_data);
+ 
+ static void
+ gclue_web_source_real_refresh_async (GClueWebSource      *source,
+@@ -103,44 +103,48 @@ gclue_web_source_real_refresh_async (GClueWebSource      *source,
+                 return;
+         }
+ 
+-        /* TODO handle cancellation */
+-        soup_session_queue_message (source->priv->soup_session,
+-                                    source->priv->query,
+-                                    refresh_callback,
+-                                    g_steal_pointer (&task));
++        soup_session_send_and_read_async (source->priv->soup_session,
++                                          source->priv->query,
++                                          G_PRIORITY_DEFAULT,
++                                          cancellable,
++                                          (GAsyncReadyCallback)refresh_callback,
++                                          g_steal_pointer (&task));
+ }
+ 
+ static void
+-refresh_callback (SoupSession *session,
+-                  SoupMessage *query,
+-                  gpointer     user_data)
++refresh_callback (SoupSession  *session,
++                  GAsyncResult *result,
++                  gpointer      user_data)
+ {
+         g_autoptr(GTask) task = g_steal_pointer (&user_data);
+         GClueWebSource *web;
++        g_autoptr(SoupMessage) query = NULL;
++        g_autoptr(GBytes) body = NULL;
+         g_autoptr(GError) local_error = NULL;
+         g_autofree char *contents = NULL;
+         g_autofree char *str = NULL;
+         g_autoptr(GClueLocation) location = NULL;
+-        SoupURI *uri;
++        GUri *uri;
+ 
+-        if (query->status_code == SOUP_STATUS_CANCELLED) {
+-                g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+-                                         "Operation cancelled");
++        web = GCLUE_WEB_SOURCE (g_task_get_source_object (task));
++        query = g_steal_pointer (&web->priv->query);
++
++        body = soup_session_send_and_read_finish (session, result, &local_error);
++        if (!body) {
++                g_task_return_error (task, g_steal_pointer (&local_error));
+                 return;
+         }
+ 
+-        web = GCLUE_WEB_SOURCE (g_task_get_source_object (task));
+-        web->priv->query = NULL;
+-
+-        if (query->status_code != SOUP_STATUS_OK) {
++        if (soup_message_get_status (query) != SOUP_STATUS_OK) {
+                 g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
+-                                         "Failed to query location: %s", query->reason_phrase);
++                                         "Failed to query location: %s",
++                                         soup_message_get_reason_phrase (query));
+                 return;
+         }
+ 
+-        contents = g_strndup (query->response_body->data, query->response_body->length);
++        contents = g_strndup (g_bytes_get_data (body, NULL), g_bytes_get_size (body));
+         uri = soup_message_get_uri (query);
+-        str = soup_uri_to_string (uri, FALSE);
++        str = g_uri_to_string (uri);
+         g_debug ("Got following response from '%s':\n%s", str, contents);
+         location = GCLUE_WEB_SOURCE_GET_CLASS (web)->parse_response (web, contents, &local_error);
+         if (local_error != NULL) {
+@@ -253,15 +257,12 @@ gclue_web_source_finalize (GObject *gsource)
+                 priv->connectivity_changed_id = 0;
+         }
+ 
+-        if (priv->query != NULL) {
+-                g_debug ("Cancelling query");
+-                soup_session_cancel_message (priv->soup_session,
+-                                             priv->query,
+-                                             SOUP_STATUS_CANCELLED);
+-                priv->query = NULL;
+-        }
++        g_clear_object (&priv->query);
+ 
+-        g_clear_object (&priv->soup_session);
++        if (priv->soup_session) {
++                soup_session_abort (priv->soup_session);
++                g_object_unref (priv->soup_session);
++        }
+ 
+         G_OBJECT_CLASS (gclue_web_source_parent_class)->finalize (gsource);
+ }
+@@ -274,10 +275,8 @@ gclue_web_source_constructed (GObject *object)
+ 
+         G_OBJECT_CLASS (gclue_web_source_parent_class)->constructed (object);
+ 
+-        priv->soup_session = soup_session_new_with_options
+-                        (SOUP_SESSION_REMOVE_FEATURE_BY_TYPE,
+-                         SOUP_TYPE_PROXY_RESOLVER_DEFAULT,
+-                         NULL);
++        priv->soup_session = soup_session_new ();
++        soup_session_remove_feature_by_type (priv->soup_session, G_TYPE_PROXY_RESOLVER);
+ 
+         monitor = g_network_monitor_get_default ();
+         priv->network_changed_id =
+@@ -330,25 +329,33 @@ gclue_web_source_refresh (GClueWebSource *source)
+ }
+ 
+ static void
+-submit_query_callback (SoupSession *session,
+-                       SoupMessage *query,
+-                       gpointer     user_data)
++submit_query_callback (SoupSession  *session,
++                       GAsyncResult *result)
+ {
+-        SoupURI *uri;
+-        g_autofree char *str = NULL;
++        g_autoptr(GBytes) body = NULL;
++        g_autoptr(GError) local_error = NULL;
++        SoupMessage *query;
++        g_autofree char *uri_str = NULL;
++        gint status_code;
+ 
+-        uri = soup_message_get_uri (query);
+-        str = soup_uri_to_string (uri, FALSE);
+-        if (query->status_code != SOUP_STATUS_OK &&
+-            query->status_code != SOUP_STATUS_NO_CONTENT) {
++        query = soup_session_get_async_result_message (session, result);
++        uri_str = g_uri_to_string (soup_message_get_uri (query));
++
++        body = soup_session_send_and_read_finish (session, result, &local_error);
++        if (!body) {
+                 g_warning ("Failed to submit location data to '%s': %s",
+-                           str,
+-                           query->reason_phrase);
+-		return;
+-	}
++                           uri_str, local_error->message);
++                return;
++        }
+ 
+-        g_debug ("Successfully submitted location data to '%s'",
+-                 str);
++        status_code = soup_message_get_status (query);
++        if (status_code != SOUP_STATUS_OK && status_code != SOUP_STATUS_NO_CONTENT) {
++                g_warning ("Failed to submit location data to '%s': %s",
++                           uri_str, soup_message_get_reason_phrase (query));
++                return;
++        }
++
++        g_debug ("Successfully submitted location data to '%s'", uri_str);
+ }
+ 
+ #define SUBMISSION_ACCURACY_THRESHOLD 100
+@@ -362,8 +369,8 @@ on_submit_source_location_notify (GObject    *source_object,
+         GClueLocationSource *source = GCLUE_LOCATION_SOURCE (source_object);
+         GClueWebSource *web = GCLUE_WEB_SOURCE (user_data);
+         GClueLocation *location;
+-        SoupMessage *query;
+-        GError *error = NULL;
++        g_autoptr(SoupMessage) query = NULL;
++        g_autoptr(GError) error = NULL;
+ 
+         location = gclue_location_source_get_location (source);
+         if (location == NULL ||
+@@ -386,16 +393,17 @@ on_submit_source_location_notify (GObject    *source_object,
+                 if (error != NULL) {
+                         g_warning ("Failed to create submission query: %s",
+                                    error->message);
+-                        g_error_free (error);
+                 }
+ 
+                 return;
+         }
+ 
+-        soup_session_queue_message (web->priv->soup_session,
+-                                    query,
+-                                    submit_query_callback,
+-                                    web);
++        soup_session_send_and_read_async (web->priv->soup_session,
++                                          query,
++                                          G_PRIORITY_DEFAULT,
++                                          NULL,
++                                          (GAsyncReadyCallback)submit_query_callback,
++                                          web);
+ }
+ 
+ /**
+diff --git a/src/meson.build b/src/meson.build
+index 13eb1ba..1c55b3b 100644
+--- a/src/meson.build
++++ b/src/meson.build
+@@ -1,5 +1,5 @@
+ geoclue_deps = base_deps + [ dependency('json-glib-1.0', version: '>= 0.14.0'),
+-                             dependency('libsoup-2.4', version: '>= 2.42.0') ]
++                             dependency('libsoup-3.0', version: '>= 3.0.0') ]
+ 
+ sources = [ libgeoclue_public_api_gen_sources[1],
+             geoclue_iface_sources,
diff --git a/debian/patches/fix-nowifi-query.patch b/debian/patches/fix-nowifi-query.patch
deleted file mode 100644
index 15147a8c876208bfadcea07c073c026fbd0b6f78..0000000000000000000000000000000000000000
--- a/debian/patches/fix-nowifi-query.patch
+++ /dev/null
@@ -1,66 +0,0 @@
-Description: Fix getting a location if the computer has no wifi card
- This reverts 2 upstream commits: 194529c7e7123b06d41eb8025cd4375aba271068 and
- 715cfbf5bec8002fb1e9759b6c34bc070f977807
-Origin: vendor
-Bug: https://gitlab.freedesktop.org/geoclue/geoclue/-/issues/142
-Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=978437
-
---- a/src/gclue-wifi.c
-+++ b/src/gclue-wifi.c
-@@ -837,7 +837,6 @@ gclue_wifi_get_accuracy_level (GClueWifi
-         return wifi->priv->accuracy_level;
- }
- 
--/* Can return NULL without setting @error, signifying an empty BSS list. */
- static GList *
- get_bss_list (GClueWifi *wifi,
-               GError   **error)
-@@ -859,22 +858,8 @@ gclue_wifi_create_query (GClueWebSource
- {
-         GList *bss_list; /* As in Access Points */
-         SoupMessage *msg;
--        g_autoptr(GError) local_error = NULL;
- 
--        bss_list = get_bss_list (GCLUE_WIFI (source), &local_error);
--        if (local_error != NULL) {
--                g_propagate_error (error, g_steal_pointer (&local_error));
--                return NULL;
--        }
--
--        /* Empty list? */
--        if (bss_list == NULL) {
--                g_set_error_literal (error,
--                                     G_IO_ERROR,
--                                     G_IO_ERROR_FAILED,
--                                     "No WiFi networks found");
--                return NULL;
--        }
-+        bss_list = get_bss_list (GCLUE_WIFI (source), NULL);
- 
-         msg = gclue_mozilla_create_query (bss_list, NULL, error);
-         g_list_free (bss_list);
-@@ -896,22 +881,10 @@ gclue_wifi_create_submit_query (GClueWeb
- {
-         GList *bss_list; /* As in Access Points */
-         SoupMessage * msg;
--        g_autoptr(GError) local_error = NULL;
- 
--        bss_list = get_bss_list (GCLUE_WIFI (source), &local_error);
--        if (local_error != NULL) {
--                g_propagate_error (error, g_steal_pointer (&local_error));
-+        bss_list = get_bss_list (GCLUE_WIFI (source), error);
-+        if (bss_list == NULL)
-                 return NULL;
--        }
--
--        /* Empty list? */
--        if (bss_list == NULL) {
--                g_set_error_literal (error,
--                                     G_IO_ERROR,
--                                     G_IO_ERROR_FAILED,
--                                     "No WiFi networks found");
--                return NULL;
--        }
- 
-         msg = gclue_mozilla_create_submit_query (location,
-                                                  bss_list,
diff --git a/debian/patches/series b/debian/patches/series
index ec781600477aed2e89baa7351e70c8340000ef15..06d1843b36dcab13f484196ad36ffe385920da6d 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1,6 +1 @@
-0002-config-Make-the-Mozilla-API-key-configurable.patch
-0003-service-Sync-in_use-property-when-apps-get-connected.patch
-0003-Revert-Fixed-hang-on-startup-when-client-app-is-a-sy.patch
-0004-service-Stop-the-client-for-system-applications-when.patch
-fix-nowifi-query.patch
-0006-client-info-Support-cgroup-v2.patch
+Add-support-for-building-with-libsoup3.patch
diff --git a/demo/gclue-service-agent.c b/demo/gclue-service-agent.c
index 1530fd9cd82b9ec976c7fdf7473cc79c960b077e..ac967954604854c08937abd42d928f667fe4b44e 100644
--- a/demo/gclue-service-agent.c
+++ b/demo/gclue-service-agent.c
@@ -134,9 +134,7 @@ gclue_service_agent_class_init (GClueServiceAgentClass *klass)
 static void
 gclue_service_agent_init (GClueServiceAgent *agent)
 {
-        agent->priv = G_TYPE_INSTANCE_GET_PRIVATE (agent,
-                                                   GCLUE_TYPE_SERVICE_AGENT,
-                                                   GClueServiceAgentPrivate);
+        agent->priv = gclue_service_agent_get_instance_private (agent);
 }
 
 static void
diff --git a/demo/where-am-i.c b/demo/where-am-i.c
index 5934161f69c0ad0a0aa0d99dd0ab51a59986ed7d..aa873a458c038835af1794e24f5fbe239d78035c 100644
--- a/demo/where-am-i.c
+++ b/demo/where-am-i.c
@@ -84,7 +84,6 @@ print_location (GClueSimple *simple)
         GClueLocation *location;
         gdouble altitude, speed, heading;
         GVariant *timestamp;
-        GTimeVal tv = { 0 };
         const char *desc;
 
         location = gclue_simple_get_location (simple);
@@ -111,11 +110,13 @@ print_location (GClueSimple *simple)
         timestamp = gclue_location_get_timestamp (location);
         if (timestamp) {
                 GDateTime *date_time;
+                guint64 sec, usec;
                 gchar *str;
 
-                g_variant_get (timestamp, "(tt)", &tv.tv_sec, &tv.tv_usec);
+                g_variant_get (timestamp, "(tt)", &sec, &usec);
 
-                date_time = g_date_time_new_from_timeval_local (&tv);
+                /* Ignore usec, since it is not in the output format */
+                date_time = g_date_time_new_from_unix_local ((gint64) sec);
                 str = g_date_time_format
                       (date_time,
                        "%c (%s seconds since the Epoch)");
@@ -145,30 +146,29 @@ on_simple_ready (GObject      *source_object,
 {
         GError *error = NULL;
 
-        simple = gclue_simple_new_finish (res, &error);
+        simple = gclue_simple_new_with_thresholds_finish (res, &error);
         if (error != NULL) {
             g_critical ("Failed to connect to GeoClue2 service: %s", error->message);
 
             exit (-1);
         }
         client = gclue_simple_get_client (simple);
-        g_object_ref (client);
-        g_print ("Client object: %s\n",
-                 g_dbus_proxy_get_object_path (G_DBUS_PROXY (client)));
-        if (time_threshold > 0) {
-                gclue_client_set_time_threshold (client, time_threshold);
+        if (client) {
+                g_object_ref (client);
+                g_print ("Client object: %s\n",
+                         g_dbus_proxy_get_object_path (G_DBUS_PROXY (client)));
+
+                g_signal_connect (client,
+                                  "notify::active",
+                                  G_CALLBACK (on_client_active_notify),
+                                  NULL);
         }
-
         print_location (simple);
 
         g_signal_connect (simple,
                           "notify::location",
                           G_CALLBACK (print_location),
                           NULL);
-        g_signal_connect (client,
-                          "notify::active",
-                          G_CALLBACK (on_client_active_notify),
-                          NULL);
 }
 
 gint
@@ -192,11 +192,13 @@ main (gint argc, gchar *argv[])
 
         g_timeout_add_seconds (timeout, on_location_timeout, NULL);
 
-        gclue_simple_new ("geoclue-where-am-i",
-                          accuracy_level,
-                          NULL,
-                          on_simple_ready,
-                          NULL);
+        gclue_simple_new_with_thresholds ("geoclue-where-am-i",
+                                          accuracy_level,
+                                          time_threshold,
+                                          0,
+                                          NULL,
+                                          on_simple_ready,
+                                          NULL);
 
         main_loop = g_main_loop_new (NULL, FALSE);
         g_main_loop_run (main_loop);
diff --git a/docs/lib/libgeoclue-sections.txt b/docs/lib/libgeoclue-sections.txt
index 8352198e6bdece798a6320bfc6793c37c4e41103..6be1c69b1d5df0a48dcfde6f6b7dea6edbc24dcc 100644
--- a/docs/lib/libgeoclue-sections.txt
+++ b/docs/lib/libgeoclue-sections.txt
@@ -245,6 +245,8 @@ GClueManagerProxyPrivate
 <TITLE>GClueSimple</TITLE>
 gclue_simple_new
 gclue_simple_new_finish
+gclue_simple_new_with_thresholds
+gclue_simple_new_with_thresholds_finish
 gclue_simple_new_sync
 gclue_simple_get_client
 gclue_simple_get_location
diff --git a/interface/meson.build b/interface/meson.build
index 9e230a7a6ab251aa0c0dbdf02197674b26efc2d4..74954dbb618bb4253de491100b75a4774b29ba92 100644
--- a/interface/meson.build
+++ b/interface/meson.build
@@ -66,10 +66,12 @@ if get_option('enable-backend')
       interface_prefix: 'fi.w1.wpa_supplicant1.',
       annotations: annotations)
 
-  compass_iface_sources = gnome.gdbus_codegen(
-      'compass-interface',
-      'net.hadess.SensorProxy.xml',
-      interface_prefix: 'net.hadess.SensorProxy')
+  if get_option('compass')
+    compass_iface_sources = gnome.gdbus_codegen(
+        'compass-interface',
+        'net.hadess.SensorProxy.xml',
+        interface_prefix: 'net.hadess.SensorProxy')
+  endif
 endif
 
 install_data('org.freedesktop.GeoClue2.Agent.xml',
diff --git a/interface/org.freedesktop.portal.Location.xml b/interface/org.freedesktop.portal.Location.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0695f4b0a6e6206bd2dc195a500bc063f5601d81
--- /dev/null
+++ b/interface/org.freedesktop.portal.Location.xml
@@ -0,0 +1,168 @@
+<?xml version="1.0"?>
+<!--
+ Copyright (C) 2018 Red Hat, Inc.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+ Author: Matthias Clasen <mclasen@redhat.com>
+-->
+
+<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
+  <!--
+      org.freedesktop.portal.Location:
+      @short_description: Portal for obtaining information about the location
+
+      This simple interface lets sandboxed applications query basic
+      information about the location.
+
+      This documentation describes version 1 of this interface.
+  -->
+  <interface name="org.freedesktop.portal.Location">
+    <!--
+        CreateSession:
+        @options: Vardict with optional further information
+        @handle: Object path for the created #org.freedesktop.portal.Session object
+
+        Create a location session. A successfully created session can at
+        any time be closed using org.freedesktop.portal.Session::Close, or may
+        at any time be closed by the portal implementation, which will be
+        signalled via org.freedesktop.portal.Session::Closed.
+`
+        Supported keys in the @options vardict include:
+        <variablelist>
+          <varlistentry>
+            <term>session_handle_token s</term>
+            <listitem><para>
+              A string that will be used as the last element of the session handle. Must be a valid
+              object path element. See the #org.freedesktop.portal.Session documentation for
+              more information about the session handle.
+            </para></listitem>
+          </varlistentry>
+          <varlistentry>
+            <term>distance-threshold u</term>
+            <listitem><para>
+              Distance threshold in meters. Default is 0.
+            </para></listitem>
+          </varlistentry>
+          <varlistentry>
+            <term>time-threshold u</term>
+            <listitem><para>
+              Time threshold in seconds. Default is 0.
+            </para></listitem>
+          </varlistentry>
+          <varlistentry>
+            <term>accuracy u</term>
+            <listitem><para>
+              Requested accuracy. Default is EXACT.
+              Values: NONE 0, COUNTRY 1, CITY 2, NEIGHBORHOOD 3, STREET 4, EXACT 5
+            </para></listitem>
+          </varlistentry>
+        </variablelist>
+    -->
+    <method name="CreateSession">
+      <arg type="a{sv}" name="options" direction="in"/>
+      <arg type="o" name="handle" direction="out"/>
+    </method>
+
+    <!--
+        Start:
+        @session_handle: Object path for the #org.freedesktop.portal.Session object
+        @parent_window: Identifier for the application window, see <link linkend="parent_window">Common Conventions</link>
+        @options: Vardict with optional further information
+        @handle: Object path for the #org.freedesktop.portal.Request object representing this call
+
+        Start the location session.
+        An application can only attempt start a session once.
+
+        Supported keys in the @options vardict include:
+        <variablelist>
+          <varlistentry>
+            <term>handle_token s</term>
+            <listitem><para>
+              A string that will be used as the last element of the @handle. Must be a valid
+              object path element. See the #org.freedesktop.portal.Request documentation for
+              more information about the @handle.
+            </para></listitem>
+          </varlistentry>
+        </variablelist>
+    -->
+    <method name="Start">
+      <arg type="o" name="session_handle" direction="in"/>
+      <arg type="s" name="parent_window" direction="in"/>
+      <arg type="a{sv}" name="options" direction="in"/>
+      <arg type="o" name="handle" direction="out"/>
+    </method>
+
+    <!--
+        LocationUpdated:
+        @session_handle: Object path for the #org.freedesktop.portal.Session object
+        @location: Vardict with the current location data
+
+        The LocationUpdated signal is emitted when the location has changed, as well
+        as when the initial location has been determined.
+
+        The following results may get returned via the @location:
+        <variablelist>
+          <varlistentry>
+            <term>latitude d</term>
+            <listitem><para>
+              The latitude, in degrees.
+            </para></listitem>
+          </varlistentry>
+          <varlistentry>
+            <term>longitude d</term>
+            <listitem><para>
+              The longitude, in degrees.
+            </para></listitem>
+          </varlistentry>
+          <varlistentry>
+            <term>altitude d</term>
+            <listitem><para>
+              The altitude, in meters.
+            </para></listitem>
+          </varlistentry>
+          <varlistentry>
+            <term>accuracy d</term>
+            <listitem><para>
+              The accuracy, in meters.
+            </para></listitem>
+          </varlistentry>
+          <varlistentry>
+            <term>speed d</term>
+            <listitem><para>
+              The speed, in meters per second.
+            </para></listitem>
+          </varlistentry>
+          <varlistentry>
+            <term>heading d</term>
+            <listitem><para>
+              The heading, in degrees, going clockwise. North 0, East 90, South 180, West 270.
+            </para></listitem>
+          </varlistentry>
+          <varlistentry>
+            <term>timestamp (tt)</term>
+            <listitem><para>
+              The timestamp, as seconds and microsections since the Unix epoch.
+            </para></listitem>
+          </varlistentry>
+        </variablelist>
+      -->
+    <signal name="LocationUpdated">
+      <arg type="o" name="session_handle" direction="out"/>
+      <arg type="a{sv}" name="location" direction="out"/>
+    </signal>
+
+    <property name="version" type="u" access="read"/>
+  </interface>
+</node>
diff --git a/libgeoclue/gclue-simple.c b/libgeoclue/gclue-simple.c
index 33958e0ef84eebfab161170db2c4c5df7f2e9244..1284e4d484a40e6451a4e60e49b57f6ed5458e43 100644
--- a/libgeoclue/gclue-simple.c
+++ b/libgeoclue/gclue-simple.c
@@ -46,6 +46,7 @@
 
 #include "gclue-simple.h"
 #include "gclue-helpers.h"
+#include "xdp-location.h"
 
 #define BUS_NAME "org.freedesktop.GeoClue2"
 
@@ -56,6 +57,8 @@ struct _GClueSimplePrivate
 {
         char *desktop_id;
         GClueAccuracyLevel accuracy_level;
+        guint distance_threshold;
+        guint time_threshold;
 
         GClueClient *client;
         GClueLocation *location;
@@ -64,6 +67,12 @@ struct _GClueSimplePrivate
 
         GTask *task;
         GCancellable *cancellable;
+
+        char *sender;
+        XdpLocation *portal;
+        gulong location_updated_id;
+        guint response_id;
+        char *session_id;
 };
 
 G_DEFINE_TYPE_WITH_CODE (GClueSimple,
@@ -80,11 +89,15 @@ enum
         PROP_ACCURACY_LEVEL,
         PROP_CLIENT,
         PROP_LOCATION,
+        PROP_DISTANCE_THRESHOLD,
+        PROP_TIME_THRESHOLD,
         LAST_PROP
 };
 
 static GParamSpec *gParamSpecs[LAST_PROP];
 
+static void clear_portal (GClueSimple *simple);
+
 static void
 gclue_simple_finalize (GObject *object)
 {
@@ -102,6 +115,8 @@ gclue_simple_finalize (GObject *object)
         g_clear_object (&priv->location);
         g_clear_object (&priv->task);
 
+        clear_portal (GCLUE_SIMPLE (object));
+
         /* Chain up to the parent class */
         G_OBJECT_CLASS (gclue_simple_parent_class)->finalize (object);
 }
@@ -123,6 +138,14 @@ gclue_simple_get_property (GObject    *object,
                 g_value_set_object (value, simple->priv->location);
                 break;
 
+        case PROP_DISTANCE_THRESHOLD:
+                g_value_set_uint (value, simple->priv->distance_threshold);
+                break;
+
+        case PROP_TIME_THRESHOLD:
+                g_value_set_uint (value, simple->priv->time_threshold);
+                break;
+
         default:
                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
         }
@@ -145,6 +168,14 @@ gclue_simple_set_property (GObject      *object,
                 simple->priv->accuracy_level = g_value_get_enum (value);
                 break;
 
+        case PROP_DISTANCE_THRESHOLD:
+                simple->priv->distance_threshold = g_value_get_uint (value);
+                break;
+
+        case PROP_TIME_THRESHOLD:
+                simple->priv->time_threshold = g_value_get_uint (value);
+                break;
+
         default:
                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
         }
@@ -194,7 +225,8 @@ gclue_simple_class_init (GClueSimpleClass *klass)
         /**
          * GClueSimple:client:
          *
-         * The client proxy.
+         * The client proxy. This is %NULL if @simple is not using a client proxy
+         * (i-e when inside the Flatpak sandbox).
          */
         gParamSpecs[PROP_CLIENT] = g_param_spec_object ("client",
                                                         "Client",
@@ -218,6 +250,44 @@ gclue_simple_class_init (GClueSimpleClass *klass)
         g_object_class_install_property (object_class,
                                          PROP_LOCATION,
                                          gParamSpecs[PROP_LOCATION]);
+
+        /**
+         * GClueSimple:distance-threshold:
+         *
+         * The current distance threshold in meters. This value is used by the
+         * service when it gets new location info. If the distance moved is
+         * below the threshold, it won't emit the LocationUpdated signal.
+         *
+         * When set to 0 (default), it always emits the signal.
+         */
+        gParamSpecs[PROP_DISTANCE_THRESHOLD] = g_param_spec_uint
+                ("distance-threshold",
+                 "DistanceThreshold",
+                 "DistanceThreshold",
+                 0, G_MAXUINT32, 0,
+                 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
+        g_object_class_install_property (object_class,
+                                         PROP_DISTANCE_THRESHOLD,
+                                         gParamSpecs[PROP_DISTANCE_THRESHOLD]);
+
+        /**
+         * GClueSimple:time-threshold:
+         *
+         * The current time threshold in seconds. This value is used by the
+         * service when it gets new location info. If the time passed is
+         * below the threshold, it won't emit the LocationUpdated signal.
+         *
+         * When set to 0 (default), it always emits the signal.
+         */
+        gParamSpecs[PROP_TIME_THRESHOLD] = g_param_spec_uint
+                ("time-threshold",
+                 "TimeThreshold",
+                 "TimeThreshold",
+                 0, G_MAXUINT32, 0,
+                 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
+        g_object_class_install_property (object_class,
+                                         PROP_TIME_THRESHOLD,
+                                         gParamSpecs[PROP_TIME_THRESHOLD]);
 }
 
 static void
@@ -315,6 +385,14 @@ on_client_created (GObject      *source_object,
 
                 return;
         }
+        if (priv->distance_threshold != 0) {
+                gclue_client_set_distance_threshold
+                        (priv->client, priv->distance_threshold);
+        }
+        if (priv->time_threshold != 0) {
+                gclue_client_set_time_threshold
+                        (priv->client, priv->time_threshold);
+        }
 
         priv->task = task;
         priv->update_id =
@@ -329,6 +407,282 @@ on_client_created (GObject      *source_object,
                                  task);
 }
 
+/* We use the portal if we are inside a flatpak,
+ * or if GTK_USE_PORTAL is set in the environment.
+ */
+static gboolean
+should_use_portal (void)
+{
+  static const char *use_portal = NULL;
+
+  if (G_UNLIKELY (use_portal == NULL))
+    {
+      if (g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS))
+        use_portal = "1";
+      else
+        {
+          use_portal = g_getenv ("GTK_USE_PORTAL");
+          if (!use_portal)
+            use_portal = "";
+        }
+    }
+
+  return use_portal[0] == '1';
+}
+
+#define PORTAL_BUS_NAME "org.freedesktop.portal.Desktop"
+#define PORTAL_OBJECT_PATH "/org/freedesktop/portal/desktop"
+#define PORTAL_REQUEST_INTERFACE "org.freedesktop.portal.Request"
+#define PORTAL_SESSION_INTERFACE "org.freedesktop.portal.Session"
+
+static void
+clear_portal (GClueSimple *simple)
+{
+        GClueSimplePrivate *priv = simple->priv;
+
+        if (priv->portal) {
+                GDBusConnection *bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (priv->portal));
+
+                if (priv->session_id)
+                        g_dbus_connection_call (bus,
+                                                PORTAL_BUS_NAME,
+                                                priv->session_id,
+                                                PORTAL_SESSION_INTERFACE,
+                                                "Close",
+                                                NULL, NULL, 0, -1, NULL, NULL, NULL);
+
+                if (priv->response_id) {
+                        g_dbus_connection_signal_unsubscribe (bus, priv->response_id);
+                        priv->response_id = 0;
+                }
+        }
+
+        g_clear_object (&priv->portal);
+        priv->location_updated_id = 0;
+        g_clear_pointer (&priv->session_id, g_free);
+        g_clear_pointer (&priv->sender, g_free);
+}
+
+static void
+on_portal_location_updated (XdpLocation *portal,
+                            const char *session_handle,
+                            GVariant *data,
+                            gpointer user_data)
+{
+        GClueSimple *simple = user_data;
+        GClueSimplePrivate *priv = simple->priv;
+        double latitude;
+        double longitude;
+        double altitude;
+        double accuracy;
+        double speed;
+        double heading;
+        const char *description;
+        GVariant *timestamp;
+        GClueLocation *location = gclue_location_skeleton_new ();
+
+        g_variant_lookup (data, "Latitude", "d", &latitude);
+        g_variant_lookup (data, "Longitude", "d", &longitude);
+        g_variant_lookup (data, "Altitude", "d", &altitude);
+        g_variant_lookup (data, "Accuracy", "d", &accuracy);
+        g_variant_lookup (data, "Speed", "d", &speed);
+        g_variant_lookup (data, "Heading", "d", &heading);
+        g_variant_lookup (data, "Description", "&s", &description);
+        g_variant_lookup (data, "Timestamp", "@(tt)", &timestamp);
+
+        gclue_location_set_latitude (location, latitude);
+        gclue_location_set_longitude (location, longitude);
+        gclue_location_set_altitude (location, altitude);
+        gclue_location_set_accuracy (location, accuracy);
+        gclue_location_set_speed (location, speed);
+        gclue_location_set_heading (location, heading);
+        gclue_location_set_description (location, description);
+        gclue_location_set_timestamp (location, timestamp);
+
+        g_set_object (&priv->location, location);
+
+        if (priv->task) {
+                g_task_return_boolean (priv->task, TRUE);
+                g_clear_object (&priv->task);
+        }
+        else {
+                g_object_notify (G_OBJECT (simple), "location");
+        }
+
+        g_object_unref (location);
+}
+
+static void
+on_started (GDBusConnection *bus,
+            const char *sender_name,
+            const char *object_path,
+            const char *interface_name,
+            const char *signal_name,
+            GVariant *parameters,
+            gpointer user_data)
+{
+        GClueSimple *simple = user_data;
+        GClueSimplePrivate *priv = simple->priv;
+        guint32 response;
+        g_autoptr(GVariant) ret = NULL;
+
+        g_dbus_connection_signal_unsubscribe (bus, priv->response_id);
+        priv->response_id = 0;
+
+        g_variant_get (parameters, "(u@a{sv})", &response, &ret);
+
+        if (response != 0) {
+                clear_portal (simple);
+                g_task_return_new_error (priv->task, G_IO_ERROR, G_IO_ERROR_FAILED, "Start failed");
+                g_clear_object (&priv->task);
+        }
+}
+
+static void
+on_portal_started_finish (GObject      *source_object,
+                          GAsyncResult *res,
+                          gpointer      user_data)
+{       
+        GTask *task = G_TASK (user_data);
+        GClueSimple *simple = g_task_get_source_object (task);
+        GClueSimplePrivate *priv = simple->priv;
+        GError *error = NULL;
+
+        if (!xdp_location_call_start_finish (priv->portal, NULL, res, &error)) {
+                clear_portal (simple);
+                g_task_return_new_error (priv->task, G_IO_ERROR, G_IO_ERROR_FAILED, "Start failed");
+                g_clear_object (&priv->task);
+        }
+}
+
+static void
+on_session_created (GObject *source,
+                    GAsyncResult *result,
+                    gpointer user_data)
+{
+        GTask *task = G_TASK (user_data);
+        GClueSimple *simple = g_task_get_source_object (task);
+        GClueSimplePrivate *priv = simple->priv;
+        GDBusConnection *bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (priv->portal));
+        GError *error = NULL;
+        g_autofree char *handle = NULL;
+        g_autofree char *token = NULL;
+        g_autofree char *request_path = NULL;
+        GVariantBuilder options;
+
+        if (!xdp_location_call_create_session_finish (priv->portal, &handle, result, &error)) {
+                clear_portal (simple);
+                g_task_return_error (task, error);
+                g_object_unref (task);
+                return;
+        }
+
+        if (!g_str_equal (handle, priv->session_id)) {
+                clear_portal (simple);
+                g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "Unexpected session id");
+                g_object_unref (task);
+                return;
+        }
+
+        priv->task = task;
+
+        token = g_strdup_printf ("geoclue%d", g_random_int_range (0, G_MAXINT));
+        request_path = g_strconcat (PORTAL_OBJECT_PATH, "/request/", priv->sender, "/", token, NULL);
+        priv->response_id = g_dbus_connection_signal_subscribe (bus,
+                                                                PORTAL_BUS_NAME,
+                                                                PORTAL_REQUEST_INTERFACE,
+                                                                "Response",
+                                                                request_path,
+                                                                NULL,
+                                                                G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
+                                                                on_started,
+                                                                simple,
+                                                                NULL);
+
+        g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
+        g_variant_builder_add (&options, "{sv}", "handle_token", g_variant_new_string (token));
+        xdp_location_call_start (priv->portal,
+                                 priv->session_id,
+                                 "", /* FIXME parent window */
+                                 g_variant_builder_end (&options),
+                                 NULL,
+                                 on_portal_started_finish,
+                                 task);
+}
+
+static int
+accuracy_level_to_portal (GClueAccuracyLevel level)
+{
+        switch (level) {
+        case GCLUE_ACCURACY_LEVEL_NONE:
+                return 0;
+        case GCLUE_ACCURACY_LEVEL_COUNTRY:
+                return 1;
+        case GCLUE_ACCURACY_LEVEL_CITY:
+                return 2;
+        case GCLUE_ACCURACY_LEVEL_NEIGHBORHOOD:
+                return 3;
+        case GCLUE_ACCURACY_LEVEL_STREET:
+                return 4;
+        case GCLUE_ACCURACY_LEVEL_EXACT:
+                return 5;
+        default:
+                g_assert_not_reached ();
+        }
+
+        return 0;
+}
+
+static void
+on_portal_created (GObject      *source_object,
+                   GAsyncResult *res,
+                   gpointer      user_data)
+{
+        GTask *task = G_TASK (user_data);
+        GClueSimple *simple = g_task_get_source_object (task);
+        GClueSimplePrivate *priv = simple->priv;
+        GDBusConnection *bus;
+        GError *error = NULL;
+        int i;
+        g_autofree char *session_token = NULL;
+        GVariantBuilder options;
+
+        priv->portal = xdp_location_proxy_new_for_bus_finish (res, &error);
+
+        if (error != NULL) {
+                g_task_return_error (task, error);
+                g_object_unref (task);
+                return;
+        }
+
+        bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (priv->portal));
+
+        priv->location_updated_id = g_signal_connect (priv->portal,
+                                                      "location-updated",
+                                                      G_CALLBACK (on_portal_location_updated),
+                                                      simple);
+
+        priv->sender = g_strdup (g_dbus_connection_get_unique_name (bus) + 1);
+        for (i = 0; priv->sender[i]; i++)
+                if (priv->sender[i] == '.')
+                        priv->sender[i] = '_';
+
+        session_token = g_strdup_printf ("geoclue%d", g_random_int_range (0, G_MAXINT));
+        priv->session_id = g_strconcat (PORTAL_OBJECT_PATH, "/session/", priv->sender, "/",
+                                        session_token, NULL);
+
+        g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
+        g_variant_builder_add (&options, "{sv}",
+                               "session_handle_token", g_variant_new_string (session_token));
+        g_variant_builder_add (&options, "{sv}", "distance-threshold", g_variant_new_uint32 (0));
+        g_variant_builder_add (&options, "{sv}", "time-threshold", g_variant_new_uint32 (0));
+        g_variant_builder_add (&options, "{sv}", "accuracy", g_variant_new_uint32 (accuracy_level_to_portal (simple->priv->accuracy_level)));
+
+        xdp_location_call_create_session (priv->portal,
+                                          g_variant_builder_end (&options),
+                                          NULL, on_session_created, task);
+}
+
 static void
 gclue_simple_init_async (GAsyncInitable     *initable,
                          int                 io_priority,
@@ -341,12 +695,22 @@ gclue_simple_init_async (GAsyncInitable     *initable,
 
         task = g_task_new (initable, cancellable, callback, user_data);
 
-        gclue_client_proxy_create_full (simple->priv->desktop_id,
-                                        simple->priv->accuracy_level,
-                                        GCLUE_CLIENT_PROXY_CREATE_AUTO_DELETE,
-                                        cancellable,
-                                        on_client_created,
-                                        task);
+        if (should_use_portal ()) {
+                xdp_location_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+                                                G_DBUS_PROXY_FLAGS_NONE,
+                                                PORTAL_BUS_NAME,
+                                                PORTAL_OBJECT_PATH,
+                                                cancellable,
+                                                on_portal_created,
+                                                task);
+        } else {
+                gclue_client_proxy_create_full (simple->priv->desktop_id,
+                                                simple->priv->accuracy_level,
+                                                GCLUE_CLIENT_PROXY_CREATE_AUTO_DELETE,
+                                                cancellable,
+                                                on_client_created,
+                                                task);
+        }
 }
 
 static gboolean
@@ -367,9 +731,7 @@ gclue_simple_async_initable_init (GAsyncInitableIface *iface)
 static void
 gclue_simple_init (GClueSimple *simple)
 {
-        simple->priv = G_TYPE_INSTANCE_GET_PRIVATE (simple,
-                                                  GCLUE_TYPE_SIMPLE,
-                                                  GClueSimplePrivate);
+        simple->priv = gclue_simple_get_instance_private (simple);
         simple->priv->cancellable = g_cancellable_new ();
 }
 
@@ -433,6 +795,24 @@ gclue_simple_new_finish (GAsyncResult *result,
                 return NULL;
 }
 
+/**
+ * gclue_simple_new_with_thresholds_finish:
+ * @result: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to
+ *          #gclue_simple_new_with_thresholds().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with #gclue_simple_new_with_thresholds().
+ *
+ * Returns: (transfer full) (type GClueSimple): The constructed proxy
+ * object or %NULL if @error is set.
+ */
+GClueSimple *
+gclue_simple_new_with_thresholds_finish (GAsyncResult *result,
+                                         GError      **error)
+{
+        return gclue_simple_new_finish (result, error);
+}
+
 static void
 on_simple_ready (GObject      *source_object,
                  GAsyncResult *res,
@@ -499,11 +879,100 @@ gclue_simple_new_sync (const char        *desktop_id,
 
         return simple;
 }
+
+/**
+ * gclue_simple_new_with_thresholds:
+ * @desktop_id: The desktop file id (the basename of the desktop file).
+ * @accuracy_level: The requested accuracy level as #GClueAccuracyLevel.
+ * @time_threshold: Time threshold in seconds, 0 for no limit.
+ * @distance_threshold: Distance threshold in meters, 0 for no limit.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the results are ready.
+ * @user_data: User data to pass to @callback.
+ *
+ * Asynchronously creates a #GClueSimple instance. Use
+ * #gclue_simple_new_with_thresholds_finish() to get the created #GClueSimple instance.
+ *
+ * See #gclue_simple_new_with_thresholds_sync() for the synchronous,
+ * blocking version of this function.
+ */
+void
+gclue_simple_new_with_thresholds (const char         *desktop_id,
+                                  GClueAccuracyLevel  accuracy_level,
+                                  guint               time_threshold,
+                                  guint               distance_threshold,
+                                  GCancellable       *cancellable,
+                                  GAsyncReadyCallback callback,
+                                  gpointer            user_data)
+{
+        g_async_initable_new_async (GCLUE_TYPE_SIMPLE,
+                                    G_PRIORITY_DEFAULT,
+                                    cancellable,
+                                    callback,
+                                    user_data,
+                                    "desktop-id", desktop_id,
+                                    "accuracy-level", accuracy_level,
+                                    "time-threshold", time_threshold,
+                                    "distance-threshold", distance_threshold,
+                                    NULL);
+}
+
+/**
+ * gclue_simple_new_with_thresholds_sync:
+ * @desktop_id: The desktop file id (the basename of the desktop file).
+ * @accuracy_level: The requested accuracy level as #GClueAccuracyLevel.
+ * @time_threshold: Time threshold in seconds, 0 for no limit.
+ * @distance_threshold: Distance threshold in meters, 0 for no limit.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * The synchronous and blocking version of #gclue_simple_new_with_thresholds().
+ *
+ * Returns: (transfer full) (type GClueSimple): The new #GClueSimple object or
+ * %NULL if @error is set.
+ */
+GClueSimple *
+gclue_simple_new_with_thresholds_sync (const char        *desktop_id,
+                                       GClueAccuracyLevel accuracy_level,
+                                       guint              time_threshold,
+                                       guint              distance_threshold,
+                                       GCancellable      *cancellable,
+                                       GError           **error)
+{
+        GClueSimple *simple;
+        GMainLoop *main_loop;
+        GTask *task;
+
+        task = g_task_new (NULL, cancellable, NULL, NULL);
+        main_loop = g_main_loop_new (NULL, FALSE);
+        g_task_set_task_data (task,
+                              main_loop,
+                              (GDestroyNotify) g_main_loop_unref);
+
+        gclue_simple_new_with_thresholds (desktop_id,
+                                          accuracy_level,
+                                          time_threshold,
+                                          distance_threshold,
+                                          cancellable,
+                                          on_simple_ready,
+                                          task);
+
+        g_main_loop_run (main_loop);
+
+        simple = g_task_propagate_pointer (task, error);
+
+        g_object_unref (task);
+
+        return simple;
+}
+
+
 /**
  * gclue_simple_get_client:
  * @simple: A #GClueSimple object.
  *
- * Gets the client proxy.
+ * Gets the client proxy, or %NULL if @simple is not using a client proxy (i-e
+ * when inside the Flatpak sandbox).
  *
  * Returns: (transfer none) (type GClueClientProxy): The client object.
  */
@@ -521,7 +990,7 @@ gclue_simple_get_client (GClueSimple *simple)
  *
  * Gets the current location.
  *
- * Returns: (transfer none) (type GClueLocationProxy): The last known location
+ * Returns: (transfer none) (type GClueLocation): The last known location
  * as #GClueLocation.
  */
 GClueLocation *
diff --git a/libgeoclue/gclue-simple.h b/libgeoclue/gclue-simple.h
index 27c2ae66e4ad13556153c4f66be69fad14db9ac2..566db3cf4dc5794ede7b25a1c31dba96c3b9fe4a 100644
--- a/libgeoclue/gclue-simple.h
+++ b/libgeoclue/gclue-simple.h
@@ -56,6 +56,8 @@ struct _GClueSimpleClass
         GObjectClass parent_class;
 };
 
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (GClueSimple, g_object_unref)
+
 GType           gclue_simple_get_type     (void) G_GNUC_CONST;
 
 void            gclue_simple_new          (const char         *desktop_id,
@@ -69,6 +71,23 @@ GClueSimple *   gclue_simple_new_sync     (const char        *desktop_id,
                                            GClueAccuracyLevel accuracy_level,
                                            GCancellable      *cancellable,
                                            GError           **error);
+void            gclue_simple_new_with_thresholds
+                                          (const char         *desktop_id,
+                                           GClueAccuracyLevel  accuracy_level,
+                                           guint               time_threshold,
+                                           guint               distance_threshold,
+                                           GCancellable       *cancellable,
+                                           GAsyncReadyCallback callback,
+                                           gpointer            user_data);
+GClueSimple *   gclue_simple_new_with_thresholds_finish (GAsyncResult   *result,
+                                                         GError        **error);
+GClueSimple *   gclue_simple_new_with_thresholds_sync
+                                          (const char        *desktop_id,
+                                           GClueAccuracyLevel accuracy_level,
+                                           guint              time_threshold,
+                                           guint              distance_threshold,
+                                           GCancellable      *cancellable,
+                                           GError           **error);
 GClueClient *   gclue_simple_get_client   (GClueSimple        *simple);
 GClueLocation * gclue_simple_get_location (GClueSimple        *simple);
 
diff --git a/libgeoclue/meson.build b/libgeoclue/meson.build
index e228ec34c6d8d4c1e5c9ccf7418c7aa79f76f234..5f05dad458d34f20a220b58cd0b3da895e93f1f8 100644
--- a/libgeoclue/meson.build
+++ b/libgeoclue/meson.build
@@ -5,7 +5,8 @@ gclue_client = gnome.gdbus_codegen(
     interface_prefix: 'org.freedesktop.GeoClue2.',
     namespace: 'GClue',
     install_header: true,
-    install_dir: include_subdir)
+    install_dir: include_subdir,
+    autocleanup: 'all')
 
 # Location interface
 gclue_location = gnome.gdbus_codegen(
@@ -14,7 +15,8 @@ gclue_location = gnome.gdbus_codegen(
     interface_prefix: 'org.freedesktop.GeoClue2.',
     namespace: 'GClue',
     install_header: true,
-    install_dir: include_subdir)
+    install_dir: include_subdir,
+    autocleanup: 'all')
 
 # Manager interface
 gclue_manager = gnome.gdbus_codegen(
@@ -23,13 +25,23 @@ gclue_manager = gnome.gdbus_codegen(
     interface_prefix: 'org.freedesktop.GeoClue2.',
     namespace: 'GClue',
     install_header: true,
-    install_dir: include_subdir)
+    install_dir: include_subdir,
+    autocleanup: 'all')
+
+# Location portal interface
+location_portal = gnome.gdbus_codegen('xdp-location',
+				'../interface/org.freedesktop.portal.Location.xml',
+				interface_prefix: 'org.freedesktop.portal.',
+				namespace: 'Xdp',
+				install_header: false,
+				autocleanup: 'all')
 
 libgeoclue_sources = files('gclue-helpers.c', 'gclue-simple.c')
 libgeoclue_sources += gclue_client[0]
 libgeoclue_sources += gclue_location[0]
 libgeoclue_sources += gclue_manager[0]
 libgeoclue_sources += libgeoclue_public_api_gen_sources[1]
+libgeoclue_priv_sources = location_portal
 
 libgeoclue_headers = files('geoclue.h', 'gclue-helpers.h', 'gclue-simple.h')
 
@@ -43,7 +55,7 @@ c_args = [ '-DG_LOG_DOMAIN="Geoclue"' ]
 include_dirs = [ libgeoclue_public_api_inc, include_directories('.', '..') ]
 link_whole = [ libgeoclue_public_api ]
 libgeoclue = library('geoclue-2',
-                     libgeoclue_sources,
+                     [ libgeoclue_sources, libgeoclue_priv_sources ],
                      libgeoclue_headers,
                      include_directories: include_dirs,
                      dependencies: base_deps,
@@ -55,17 +67,17 @@ libgeoclue = library('geoclue-2',
 libgeoclue_dep = declare_dependency(link_with: libgeoclue,
                                     include_directories: include_dirs,
                                     dependencies: base_deps,
-                                    sources: [ libgeoclue_sources, libgeoclue_headers ])
+                                    sources: [ libgeoclue_sources,
+                                               libgeoclue_priv_sources,
+                                               libgeoclue_headers ])
 
-gir = find_program('g-ir-scanner', required: false)
-vapigen = find_program('vapigen', required: false)
-enable_gir = get_option('introspection')
+gir = find_program('g-ir-scanner', required: get_option('introspection'))
 pkg_requirements = ['glib-2.0', 'gio-2.0', 'gio-unix-2.0']
 gir_sources = [ libgeoclue_sources,
                 libgeoclue_headers,
                 libgeoclue_public_api_gen_sources[1] ]
 
-if gir.found() and enable_gir
+if gir.found()
   geo_gir = gnome.generate_gir(libgeoclue,
                                sources: gir_sources,
                                namespace: 'Geoclue',
@@ -78,6 +90,8 @@ if gir.found() and enable_gir
                                install: true,
                                header: 'geoclue.h',
                                extra_args: [ '--quiet' ])
+
+  vapigen = find_program('vapigen', required: get_option('vapi'))
   if vapigen.found()
     gnome.generate_vapi('libgeoclue-' + gclue_api_version,
                         sources: geo_gir[0],
diff --git a/meson.build b/meson.build
index f0ffe8be333c05e6451e0b66c1447e6e36c5f836..8aa5c3139a4fd8e3100e4700be5316c7ae89475f 100644
--- a/meson.build
+++ b/meson.build
@@ -1,4 +1,4 @@
-project('geoclue', 'c', version: '2.5.7', meson_version : '>= 0.47.2')
+project('geoclue', 'c', version: '2.6.0', meson_version : '>= 0.47.2')
 
 gclue_version = meson.project_version()
 ver_arr = gclue_version.split('.')
@@ -30,10 +30,12 @@ conf.set_quoted('PACKAGE_BUGREPORT', 'https://gitlab.freedesktop.org/geoclue/geo
 conf.set_quoted('TEST_SRCDIR', meson.source_root() + '/data/')
 conf.set_quoted('LOCALEDIR', localedir)
 conf.set_quoted('SYSCONFDIR', sysconfdir)
+conf.set_quoted('MOZILLA_API_KEY', get_option('mozilla-api-key'))
 conf.set10('GCLUE_USE_3G_SOURCE', get_option('3g-source'))
 conf.set10('GCLUE_USE_CDMA_SOURCE', get_option('cdma-source'))
 conf.set10('GCLUE_USE_MODEM_GPS_SOURCE', get_option('modem-gps-source'))
 conf.set10('GCLUE_USE_NMEA_SOURCE', get_option('nmea-source'))
+conf.set10('GCLUE_USE_COMPASS', get_option('compass'))
 
 configure_file(output: 'config.h', configuration : conf)
 configinc = include_directories('.')
@@ -91,6 +93,7 @@ summary = '''
         CDMA source:              @8@
         Modem GPS source:         @9@
         Network NMEA source:      @10@
+        Compass:                  @11@
 '''.format(gclue_version,
            get_option('prefix'),
            cc.get_id(),
@@ -101,5 +104,6 @@ summary = '''
            get_option('3g-source'),
            get_option('cdma-source'),
            get_option('modem-gps-source'),
-           get_option('nmea-source'))
+           get_option('nmea-source'),
+           get_option('compass'))
 message(summary)
diff --git a/meson_options.txt b/meson_options.txt
index 83bc60ed5163e13780bd5261e90b5422b326f75c..5b8c42d2f6b9dcdf38814e69f30e094876835b65 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -4,6 +4,9 @@ option('libgeoclue',
 option('introspection',
         type: 'boolean', value: true,
         description: 'Enable convenience library introspection generation')
+option('vapi',
+        type: 'boolean', value: true,
+        description: 'Enable convenience library vapi generation (ignored if introspection is disabled)')
 option('gtk-doc',
        type: 'boolean', value: true,
        description: 'Whether to generate the API reference for Geocode-GLib')
@@ -19,6 +22,9 @@ option('modem-gps-source',
 option('nmea-source',
        type: 'boolean', value: true,
        description: 'Enable network NMEA source (requires Avahi libraries)')
+option('compass',
+       type: 'boolean', value: true,
+       description: 'Enable setting heading from net.hadess.SensorProxy compass')
 option('enable-backend',
        type: 'boolean', value: true,
        description: 'Enable backend (the geoclue service)')
@@ -34,3 +40,6 @@ option('systemd-system-unit-dir',
 option('dbus-srv-user',
        type: 'string', value: 'root',
        description: 'The user (existing) as which the service will run')
+option('mozilla-api-key',
+       type: 'string', value: 'geoclue',
+       description: 'Your API key for Mozilla Location Service')
diff --git a/src/gclue-3g-tower.h b/src/gclue-3g-tower.h
index 2bbcae4a8ce232e057065757ea4e3c0c1116f60f..43332454caba55b46f001c9d112c0f2b40ecbf85 100644
--- a/src/gclue-3g-tower.h
+++ b/src/gclue-3g-tower.h
@@ -24,13 +24,22 @@
 
 G_BEGIN_DECLS
 
+typedef enum {
+  GCLUE_TOWER_TEC_UNKNOWN = 0,
+  GCLUE_TOWER_TEC_3G = 1,
+  GCLUE_TOWER_TEC_4G = 2,
+} GClueTowerTec;
+
 typedef struct _GClue3GTower GClue3GTower;
 
+#define GCLUE_3G_TOWER_OPERATOR_CODE_STR_LEN 6
+#define GCLUE_3G_TOWER_COUNTRY_CODE_STR_LEN 3
+
 struct _GClue3GTower {
-        guint   mcc;
-        guint   mnc;
+        gchar   opc[GCLUE_3G_TOWER_OPERATOR_CODE_STR_LEN + 1];
         gulong  lac;
         gulong  cell_id;
+        GClueTowerTec tec;
 };
 
 G_END_DECLS
diff --git a/src/gclue-3g.c b/src/gclue-3g.c
index 44731029a0b422088903de0c4fd781e6681c2b77..bbcb791e9dcf40a031c0870a6b4f7db44288069f 100644
--- a/src/gclue-3g.c
+++ b/src/gclue-3g.c
@@ -50,9 +50,9 @@ G_DEFINE_TYPE_WITH_CODE (GClue3G,
                          GCLUE_TYPE_WEB_SOURCE,
                          G_ADD_PRIVATE (GClue3G))
 
-static gboolean
+static GClueLocationSourceStartResult
 gclue_3g_start (GClueLocationSource *source);
-static gboolean
+static GClueLocationSourceStopResult
 gclue_3g_stop (GClueLocationSource *source);
 static SoupMessage *
 gclue_3g_create_query (GClueWebSource *web,
@@ -152,7 +152,7 @@ gclue_3g_init (GClue3G *source)
 {
         GClue3GPrivate *priv;
 
-        source->priv = G_TYPE_INSTANCE_GET_PRIVATE ((source), GCLUE_TYPE_3G, GClue3GPrivate);
+        source->priv = gclue_3g_get_instance_private (source);
         priv = source->priv;
 
         priv->cancellable = g_cancellable_new ();
@@ -188,7 +188,9 @@ gclue_3g_get_singleton (void)
         static GClue3G *source = NULL;
 
         if (source == NULL) {
-                source = g_object_new (GCLUE_TYPE_3G, NULL);
+                source = g_object_new (GCLUE_TYPE_3G,
+                                       "compute-movement", FALSE,
+                                       NULL);
                 g_object_weak_ref (G_OBJECT (source),
                                    on_3g_destroyed,
                                    &source);
@@ -248,37 +250,41 @@ gclue_3g_get_available_accuracy_level (GClueWebSource *web,
 }
 
 static void
-on_fix_3g (GClueModem *modem,
-           guint       mcc,
-           guint       mnc,
-           gulong      lac,
-           gulong      cell_id,
+on_fix_3g (GClueModem   *modem,
+           const gchar  *opc,
+           gulong        lac,
+           gulong        cell_id,
+           GClueTowerTec tec,
            gpointer    user_data)
 {
         GClue3GPrivate *priv = GCLUE_3G (user_data)->priv;
 
         if (priv->tower == NULL)
                 priv->tower = g_slice_new0 (GClue3GTower);
-        priv->tower->mcc = mcc;
-        priv->tower->mnc = mnc;
+        g_strlcpy (priv->tower->opc, opc,
+                   GCLUE_3G_TOWER_OPERATOR_CODE_STR_LEN + 1);
         priv->tower->lac = lac;
         priv->tower->cell_id = cell_id;
+        priv->tower->tec = tec;
 
         gclue_web_source_refresh (GCLUE_WEB_SOURCE (user_data));
 }
 
-static gboolean
+static GClueLocationSourceStartResult
 gclue_3g_start (GClueLocationSource *source)
 {
         GClueLocationSourceClass *base_class;
         GClue3GPrivate *priv;
+        GClueLocationSourceStartResult base_result;
 
-        g_return_val_if_fail (GCLUE_IS_LOCATION_SOURCE (source), FALSE);
+        g_return_val_if_fail (GCLUE_IS_LOCATION_SOURCE (source),
+                              GCLUE_LOCATION_SOURCE_START_RESULT_FAILED);
         priv = GCLUE_3G (source)->priv;
 
         base_class = GCLUE_LOCATION_SOURCE_CLASS (gclue_3g_parent_class);
-        if (!base_class->start (source))
-                return FALSE;
+        base_result = base_class->start (source);
+        if (base_result != GCLUE_LOCATION_SOURCE_START_RESULT_OK)
+                return base_result;
 
         if (priv->tower != NULL) {
                 g_slice_free (GClue3GTower, priv->tower);
@@ -295,21 +301,23 @@ gclue_3g_start (GClueLocationSource *source)
                                        priv->cancellable,
                                        on_3g_enabled,
                                        source);
-        return TRUE;
+        return base_result;
 }
 
-static gboolean
+static GClueLocationSourceStopResult
 gclue_3g_stop (GClueLocationSource *source)
 {
         GClue3GPrivate *priv = GCLUE_3G (source)->priv;
         GClueLocationSourceClass *base_class;
         GError *error = NULL;
+        GClueLocationSourceStopResult base_result;
 
         g_return_val_if_fail (GCLUE_IS_LOCATION_SOURCE (source), FALSE);
 
         base_class = GCLUE_LOCATION_SOURCE_CLASS (gclue_3g_parent_class);
-        if (!base_class->stop (source))
-                return FALSE;
+        base_result = base_class->stop (source);
+        if (base_result == GCLUE_LOCATION_SOURCE_STOP_RESULT_STILL_USED)
+                return base_result;
 
         g_signal_handlers_disconnect_by_func (G_OBJECT (priv->modem),
                                               G_CALLBACK (on_fix_3g),
@@ -324,5 +332,5 @@ gclue_3g_stop (GClueLocationSource *source)
                         g_error_free (error);
                 }
 
-        return TRUE;
+        return base_result;
 }
diff --git a/src/gclue-cdma.c b/src/gclue-cdma.c
index 8f4f129f79165ca202bf885f4c78179845ad8b89..a35c07ad1efb0c9826f03e8419600583696440a8 100644
--- a/src/gclue-cdma.c
+++ b/src/gclue-cdma.c
@@ -47,9 +47,9 @@ G_DEFINE_TYPE_WITH_CODE (GClueCDMA,
                          GCLUE_TYPE_LOCATION_SOURCE,
                          G_ADD_PRIVATE (GClueCDMA))
 
-static gboolean
+static GClueLocationSourceStartResult
 gclue_cdma_start (GClueLocationSource *source);
-static gboolean
+static GClueLocationSourceStopResult
 gclue_cdma_stop (GClueLocationSource *source);
 
 static void
@@ -141,7 +141,7 @@ gclue_cdma_init (GClueCDMA *source)
 {
         GClueCDMAPrivate *priv;
 
-        source->priv = G_TYPE_INSTANCE_GET_PRIVATE ((source), GCLUE_TYPE_CDMA, GClueCDMAPrivate);
+        source->priv = gclue_cdma_get_instance_private (source);
         priv = source->priv;
 
         priv->cancellable = g_cancellable_new ();
@@ -177,7 +177,9 @@ gclue_cdma_get_singleton (void)
         static GClueCDMA *source = NULL;
 
         if (source == NULL) {
-                source = g_object_new (GCLUE_TYPE_CDMA, NULL);
+                source = g_object_new (GCLUE_TYPE_CDMA,
+                                       "compute-movement", FALSE,
+                                       NULL);
                 g_object_weak_ref (G_OBJECT (source),
                                    on_cdma_destroyed,
                                    &source);
@@ -204,18 +206,21 @@ on_fix_cdma (GClueModem *modem,
         g_object_unref (location);
 }
 
-static gboolean
+static GClueLocationSourceStartResult
 gclue_cdma_start (GClueLocationSource *source)
 {
         GClueLocationSourceClass *base_class;
         GClueCDMAPrivate *priv;
+        GClueLocationSourceStartResult base_result;
 
-        g_return_val_if_fail (GCLUE_IS_LOCATION_SOURCE (source), FALSE);
+        g_return_val_if_fail (GCLUE_IS_LOCATION_SOURCE (source),
+                              GCLUE_LOCATION_SOURCE_START_RESULT_FAILED);
         priv = GCLUE_CDMA (source)->priv;
 
         base_class = GCLUE_LOCATION_SOURCE_CLASS (gclue_cdma_parent_class);
-        if (!base_class->start (source))
-                return FALSE;
+        base_result = base_class->start (source);
+        if (base_result != GCLUE_LOCATION_SOURCE_START_RESULT_OK)
+                return base_result;
 
         g_signal_connect (priv->modem,
                           "fix-cdma",
@@ -228,21 +233,23 @@ gclue_cdma_start (GClueLocationSource *source)
                                          on_cdma_enabled,
                                          source);
 
-        return TRUE;
+        return base_result;
 }
 
-static gboolean
+static GClueLocationSourceStopResult
 gclue_cdma_stop (GClueLocationSource *source)
 {
         GClueCDMAPrivate *priv = GCLUE_CDMA (source)->priv;
         GClueLocationSourceClass *base_class;
         GError *error = NULL;
+        GClueLocationSourceStopResult base_result;
 
         g_return_val_if_fail (GCLUE_IS_LOCATION_SOURCE (source), FALSE);
 
         base_class = GCLUE_LOCATION_SOURCE_CLASS (gclue_cdma_parent_class);
-        if (!base_class->stop (source))
-                return FALSE;
+        base_result = base_class->stop (source);
+        if (base_result == GCLUE_LOCATION_SOURCE_STOP_RESULT_STILL_USED)
+                return base_result;
 
         g_signal_handlers_disconnect_by_func (G_OBJECT (priv->modem),
                                               G_CALLBACK (on_fix_cdma),
@@ -257,5 +264,5 @@ gclue_cdma_stop (GClueLocationSource *source)
                         g_error_free (error);
                 }
 
-        return TRUE;
+        return base_result;
 }
diff --git a/src/gclue-client-info.c b/src/gclue-client-info.c
index d609b3449e1335a729b189cbf24116cb37c06d3b..7fc2ac7bbc6958fcc2671a07f0f4b3691ac3e644 100644
--- a/src/gclue-client-info.c
+++ b/src/gclue-client-info.c
@@ -181,6 +181,42 @@ on_name_vanished (GDBusConnection *connection,
                        0);
 }
 
+
+static gchar *
+parse_cgroup_v2 (GStrv lines)
+{
+        const char *unit, *name;
+        char *dash, *xdg_id;
+        g_autofree char *scope = NULL;
+
+        /* Cgroup v2 is always a single line:
+         * 0::/user.slice/user-1000.slice/user@1000.service/app.slice/app-flatpak-org.gnome.Maps-3358.scope
+         */
+        if (g_strv_length (lines) != 2)
+                return NULL;
+
+        if (!g_str_has_prefix (lines[0], "0::"))
+                 return NULL;
+
+        unit = lines[0] + strlen ("0::");
+        scope = g_path_get_basename (unit);
+        if (!g_str_has_prefix (scope, "app-flatpak-") ||
+            !g_str_has_suffix (scope, ".scope"))
+                return NULL;
+
+        name = scope + strlen("app-flatpak-");
+        dash = strchr (name, '-');
+        if (dash == NULL)
+                return NULL;
+        *dash = 0;
+
+        xdg_id = g_strdup (name);
+        g_debug ("Found xdg_id %s", xdg_id);
+
+        return xdg_id;
+}
+
+
 /* Based on got_credentials_cb() from xdg-app source code */
 static char *
 get_xdg_id (guint32 pid)
@@ -188,7 +224,7 @@ get_xdg_id (guint32 pid)
         char *xdg_id = NULL;
         g_autofree char *path = NULL;
         g_autofree char *content = NULL;
-        gchar **lines;
+        g_auto(GStrv) lines = NULL;
         int i;
 
         path = g_strdup_printf ("/proc/%u/cgroup", pid);
@@ -197,24 +233,33 @@ get_xdg_id (guint32 pid)
                 return NULL;
         lines =  g_strsplit (content, "\n", -1);
 
+	xdg_id = parse_cgroup_v2 (lines);
+	if (xdg_id != NULL)
+		return xdg_id;
+
         for (i = 0; lines[i] != NULL; i++) {
                 const char *unit = lines[i] + strlen ("1:name=systemd:");
                 g_autofree char *scope = NULL;
                 const char *name;
                 char *dash;
+                const char *prefix = NULL;
 
                 if (!g_str_has_prefix (lines[i], "1:name=systemd:"))
                         continue;
 
                 scope = g_path_get_basename (unit);
-                if ((!g_str_has_prefix (scope, "xdg-app-") &&
-                     !g_str_has_prefix (scope, "flatpak-")) ||
-                    !g_str_has_suffix (scope, ".scope"))
+
+                if (g_str_has_prefix (scope, "xdg-app-"))
+                        prefix = "xdg-app-";
+                else if (g_str_has_prefix (scope, "flatpak-"))
+                        prefix = "flatpak-";
+                else if (g_str_has_prefix (scope, "app-flatpak-"))
+                        prefix = "app-flatpak-";
+
+                if (prefix == NULL || !g_str_has_suffix (scope, ".scope"))
                         break;
 
-                /* strlen("flatpak-") == strlen("xdg-app-")
-                 * so all is good here */
-                name = scope + strlen("xdg-app-");
+                name = scope + strlen(prefix);
                 dash = strchr (name, '-');
 
                 if (dash == NULL)
@@ -224,8 +269,6 @@ get_xdg_id (guint32 pid)
                 xdg_id = g_strdup (name);
         }
 
-        g_strfreev (lines);
-
         return xdg_id;
 }
 
@@ -372,9 +415,7 @@ gclue_client_info_async_initable_init (GAsyncInitableIface *iface)
 static void
 gclue_client_info_init (GClueClientInfo *info)
 {
-        info->priv = G_TYPE_INSTANCE_GET_PRIVATE (info,
-                                                  GCLUE_TYPE_CLIENT_INFO,
-                                                  GClueClientInfoPrivate);
+        info->priv = gclue_client_info_get_instance_private(info);
 }
 
 void
diff --git a/src/gclue-compass.c b/src/gclue-compass.c
index 0a602757ca8db0491ba961b2c9927962c57ef2dc..f425aac4eb5150b70c5f9f9a8bfd5972fd6c708d 100644
--- a/src/gclue-compass.c
+++ b/src/gclue-compass.c
@@ -196,10 +196,7 @@ on_compass_proxy_ready (GObject      *source_object,
 static void
 gclue_compass_init (GClueCompass *compass)
 {
-        compass->priv =
-                G_TYPE_INSTANCE_GET_PRIVATE (compass,
-                                             GCLUE_TYPE_COMPASS,
-                                             GClueCompassPrivate);
+        compass->priv = gclue_compass_get_instance_private(compass);
         compass->priv->cancellable = g_cancellable_new ();
 
         compass_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
diff --git a/src/gclue-config.c b/src/gclue-config.c
index 0714d01b4baa7041b5a6ec9a7275ab1b03c9434b..8c1cc3de3c5e13f75dc4c34f3ed03cb3d6504875 100644
--- a/src/gclue-config.c
+++ b/src/gclue-config.c
@@ -43,8 +43,10 @@ struct _GClueConfigPrivate
         gboolean enable_cdma_source;
         gboolean enable_modem_gps_source;
         gboolean enable_wifi_source;
+        gboolean enable_compass;
         char *wifi_submit_url;
         char *wifi_submit_nick;
+        char *nmea_socket;
 
         GList *app_configs;
 };
@@ -83,6 +85,7 @@ gclue_config_finalize (GObject *object)
         g_clear_pointer (&priv->wifi_url, g_free);
         g_clear_pointer (&priv->wifi_submit_url, g_free);
         g_clear_pointer (&priv->wifi_submit_nick, g_free);
+        g_clear_pointer (&priv->nmea_socket, g_free);
 
         g_list_foreach (priv->app_configs, (GFunc) app_config_free, NULL);
 
@@ -120,7 +123,7 @@ static void
 load_app_configs (GClueConfig *config)
 {
         const char *known_groups[] = { "agent", "wifi", "3g", "cdma",
-                                       "modem-gps", "network-nmea",
+                                       "modem-gps", "network-nmea", "compass",
                                        NULL };
         GClueConfigPrivate *priv = config->priv;
         gsize num_groups = 0, i;
@@ -216,8 +219,9 @@ load_enable_source_config (GClueConfig *config,
         return enable;
 }
 
-#define DEFAULT_WIFI_URL "https://location.services.mozilla.com/v1/geolocate?key=geoclue"
-#define DEFAULT_WIFI_SUBMIT_URL "https://location.services.mozilla.com/v1/submit?key=geoclue"
+#define DEFAULT_WIFI_URL "https://location.services.mozilla.com/v1/geolocate?key=" MOZILLA_API_KEY
+#define DEFAULT_WIFI_SUBMIT_URL "https://location.services.mozilla.com/v1/submit?key=" MOZILLA_API_KEY
+#define DEFAULT_WIFI_SUBMIT_NICK "geoclue"
 
 static void
 load_wifi_config (GClueConfig *config)
@@ -269,6 +273,7 @@ load_wifi_config (GClueConfig *config)
                 g_debug ("Failed to get config \"wifi/submission-nick\": %s",
                          error->message);
                 g_error_free (error);
+                priv->wifi_submit_nick = g_strdup (DEFAULT_WIFI_SUBMIT_NICK);
         }
 }
 
@@ -296,8 +301,26 @@ load_modem_gps_config (GClueConfig *config)
 static void
 load_network_nmea_config (GClueConfig *config)
 {
+        GError *error = NULL;
         config->priv->enable_nmea_source =
                 load_enable_source_config (config, "network-nmea");
+        if (!config->priv->enable_nmea_source)
+                return;
+        config->priv->nmea_socket = g_key_file_get_string (config->priv->key_file,
+                                                           "network-nmea",
+                                                           "nmea-socket",
+                                                           &error);
+        if (error != NULL) {
+                g_debug ("`nmea-socket` configuration not set.");
+                g_clear_error (&error);
+        }
+}
+
+static void
+load_compass_config (GClueConfig *config)
+{
+        config->priv->enable_compass =
+                load_enable_source_config (config, "compass");
 }
 
 static void
@@ -305,10 +328,7 @@ gclue_config_init (GClueConfig *config)
 {
         GError *error = NULL;
 
-        config->priv =
-                G_TYPE_INSTANCE_GET_PRIVATE (config,
-                                            GCLUE_TYPE_CONFIG,
-                                            GClueConfigPrivate);
+        config->priv = gclue_config_get_instance_private(config);
         config->priv->key_file = g_key_file_new ();
         g_key_file_load_from_file (config->priv->key_file,
                                    CONFIG_FILE_PATH,
@@ -329,6 +349,7 @@ gclue_config_init (GClueConfig *config)
         load_cdma_config (config);
         load_modem_gps_config (config);
         load_network_nmea_config (config);
+        load_compass_config (config);
 }
 
 GClueConfig *
@@ -430,6 +451,12 @@ gclue_config_is_system_component (GClueConfig *config,
         return (app_config != NULL && app_config->system);
 }
 
+const char *
+gclue_config_get_nmea_socket (GClueConfig *config)
+{
+        return config->priv->nmea_socket;
+}
+
 const char *
 gclue_config_get_wifi_url (GClueConfig *config)
 {
@@ -462,6 +489,13 @@ gclue_config_get_wifi_submit_data (GClueConfig *config)
         return config->priv->wifi_submit;
 }
 
+void
+gclue_config_set_wifi_submit_data (GClueConfig *config,
+                                   gboolean     submit)
+{
+        config->priv->wifi_submit = submit;
+}
+
 gboolean
 gclue_config_get_enable_wifi_source (GClueConfig *config)
 {
@@ -493,9 +527,14 @@ gclue_config_get_enable_nmea_source (GClueConfig *config)
 }
 
 void
-gclue_config_set_wifi_submit_data (GClueConfig *config,
-                                   gboolean     submit)
+gclue_config_set_nmea_socket (GClueConfig *config,
+                                   const char  *nmea_socket)
 {
+        config->priv->nmea_socket = g_strdup(nmea_socket);
+}
 
-        config->priv->wifi_submit = submit;
+gboolean
+gclue_config_get_enable_compass (GClueConfig *config)
+{
+        return config->priv->enable_compass;
 }
diff --git a/src/gclue-config.h b/src/gclue-config.h
index 6dc10c9683861d98b93f2448d932ca58335fcedb..1231d56bec8e2f97dae49179240a49e12908076a 100644
--- a/src/gclue-config.h
+++ b/src/gclue-config.h
@@ -73,20 +73,25 @@ GClueAppPerm        gclue_config_get_app_perm           (GClueConfig     *config
                                                          GClueClientInfo *app_info);
 gboolean            gclue_config_is_system_component    (GClueConfig     *config,
                                                          const char      *desktop_id);
+const char *        gclue_config_get_nmea_socket        (GClueConfig     *config);
+void                gclue_config_set_nmea_socket        (GClueConfig     *config,
+                                                         const char  *nmea_socket);
+
 const char *        gclue_config_get_wifi_url           (GClueConfig     *config);
 const char *        gclue_config_get_wifi_submit_url    (GClueConfig     *config);
 const char *        gclue_config_get_wifi_submit_nick   (GClueConfig     *config);
 void                gclue_config_set_wifi_submit_nick   (GClueConfig     *config,
                                                          const char      *nick);
 gboolean            gclue_config_get_wifi_submit_data   (GClueConfig     *config);
+void                gclue_config_set_wifi_submit_data   (GClueConfig     *config,
+                                                         gboolean         submit);
 gboolean            gclue_config_get_enable_wifi_source (GClueConfig     *config);
 gboolean            gclue_config_get_enable_3g_source   (GClueConfig     *config);
 gboolean            gclue_config_get_enable_cdma_source (GClueConfig     *config);
 gboolean            gclue_config_get_enable_modem_gps_source
                                                         (GClueConfig     *config);
 gboolean            gclue_config_get_enable_nmea_source (GClueConfig     *config);
-void                gclue_config_set_wifi_submit_data   (GClueConfig     *config,
-                                                         gboolean         submit);
+gboolean            gclue_config_get_enable_compass     (GClueConfig     *config);
 
 G_END_DECLS
 
diff --git a/src/gclue-location-source.c b/src/gclue-location-source.c
index 4ae4c57a862ce9c1cf5b0f3dbd1c4eb980e29fcf..5d7347d4b35d5c6306f755cb4bb92543243ff6c9 100644
--- a/src/gclue-location-source.c
+++ b/src/gclue-location-source.c
@@ -20,8 +20,13 @@
  */
 
 #include <glib.h>
+#include <config.h>
 #include "gclue-location-source.h"
+
+#if GCLUE_USE_COMPASS
 #include "gclue-compass.h"
+#include "gclue-config.h"
+#endif
 
 /**
  * SECTION:gclue-location-source
@@ -31,9 +36,9 @@
  * The interface all geolocation sources must implement.
  **/
 
-static gboolean
+static GClueLocationSourceStartResult
 start_source (GClueLocationSource *source);
-static gboolean
+static GClueLocationSourceStopResult
 stop_source (GClueLocationSource *source);
 
 struct _GClueLocationSourcePrivate
@@ -48,7 +53,9 @@ struct _GClueLocationSourcePrivate
         gboolean compute_movement;
         gboolean scramble_location;
 
+#if GCLUE_USE_COMPASS
         GClueCompass *compass;
+#endif
 
         guint heading_changed_id;
 };
@@ -72,6 +79,7 @@ enum
 
 static GParamSpec *gParamSpecs[LAST_PROP];
 
+#if GCLUE_USE_COMPASS
 static gboolean
 set_heading_from_compass (GClueLocationSource *source,
                           GClueLocation       *location)
@@ -89,7 +97,8 @@ set_heading_from_compass (GClueLocationSource *source,
             heading == curr_heading)
                 return FALSE;
 
-        g_debug ("%s got new heading %f", G_OBJECT_TYPE_NAME (source), heading);
+        g_debug ("%s got new heading from compass: %f",
+                 G_OBJECT_TYPE_NAME (source), heading);
         /* We trust heading from compass more than any other source so we always
          * override existing heading
          */
@@ -111,6 +120,7 @@ on_compass_heading_changed (GObject    *gobject,
         if (set_heading_from_compass (source, source->priv->location))
                 g_object_notify (G_OBJECT (source), "location");
 }
+#endif /* GCLUE_USE_COMPASS */
 
 static void
 gclue_location_source_get_property (GObject    *object,
@@ -276,64 +286,72 @@ gclue_location_source_class_init (GClueLocationSourceClass *klass)
 static void
 gclue_location_source_init (GClueLocationSource *source)
 {
-        source->priv =
-                G_TYPE_INSTANCE_GET_PRIVATE (source,
-                                             GCLUE_TYPE_LOCATION_SOURCE,
-                                             GClueLocationSourcePrivate);
+        source->priv = gclue_location_source_get_instance_private (source);
         source->priv->compute_movement = TRUE;
         source->priv->time_threshold = gclue_min_uint_new ();
 }
 
-static gboolean
+static GClueLocationSourceStartResult
 start_source (GClueLocationSource *source)
 {
         source->priv->active_counter++;
         if (source->priv->active_counter > 1) {
                 g_debug ("%s already active, not starting.",
                          G_OBJECT_TYPE_NAME (source));
-                return TRUE;
+                return GCLUE_LOCATION_SOURCE_START_RESULT_ALREADY_STARTED;
         }
 
+#if GCLUE_USE_COMPASS
         if (source->priv->compute_movement) {
-                source->priv->compass = gclue_compass_get_singleton ();
-                source->priv->heading_changed_id = g_signal_connect
-                        (G_OBJECT (source->priv->compass),
-                         "notify::heading",
-                         G_CALLBACK (on_compass_heading_changed),
-                         source);
+                GClueConfig *config = gclue_config_get_singleton ();
+
+                if (gclue_config_get_enable_compass (config)) {
+                        source->priv->compass = gclue_compass_get_singleton ();
+                        source->priv->heading_changed_id = g_signal_connect
+                                (G_OBJECT (source->priv->compass),
+                                 "notify::heading",
+                                 G_CALLBACK (on_compass_heading_changed),
+                                 source);
+                } else {
+                        source->priv->compass = NULL;
+                        g_debug ("Compass is disabled in config");
+                }
         }
+#endif
 
         g_object_notify (G_OBJECT (source), "active");
         g_debug ("%s now active", G_OBJECT_TYPE_NAME (source));
-        return TRUE;
+        return GCLUE_LOCATION_SOURCE_START_RESULT_OK;
 }
 
-static gboolean
+static GClueLocationSourceStopResult
 stop_source (GClueLocationSource *source)
 {
         if (source->priv->active_counter == 0) {
                 g_debug ("%s already inactive, not stopping.",
                          G_OBJECT_TYPE_NAME (source));
-                return TRUE;
+                return GCLUE_LOCATION_SOURCE_STOP_RESULT_ALREADY_STOPPED;
         }
 
         source->priv->active_counter--;
         if (source->priv->active_counter > 0) {
                 g_debug ("%s still in use, not stopping.",
                          G_OBJECT_TYPE_NAME (source));
-                return FALSE;
+                return GCLUE_LOCATION_SOURCE_STOP_RESULT_STILL_USED;
         }
 
+#if GCLUE_USE_COMPASS
         if (source->priv->compass) {
                 g_signal_handler_disconnect (source->priv->compass,
                                              source->priv->heading_changed_id);
                 g_clear_object (&source->priv->compass);
         }
+#endif
 
         g_object_notify (G_OBJECT (source), "active");
         g_debug ("%s now inactive", G_OBJECT_TYPE_NAME (source));
 
-        return TRUE;
+        return GCLUE_LOCATION_SOURCE_STOP_RESULT_OK;
 }
 
 /**
@@ -441,7 +459,9 @@ gclue_location_source_set_location (GClueLocationSource *source,
                 gclue_location_set_speed (priv->location, speed);
         }
 
+#if GCLUE_USE_COMPASS
         set_heading_from_compass (source, location);
+#endif
         heading = gclue_location_get_heading (location);
         if (heading == GCLUE_LOCATION_HEADING_UNKNOWN) {
                 if (cur_location != NULL && priv->compute_movement)
diff --git a/src/gclue-location-source.h b/src/gclue-location-source.h
index 51d26166b7470f3cb0ab9246ea0349ffeacc78b1..620f601954361826d34a42d52de3df77624b1f9f 100644
--- a/src/gclue-location-source.h
+++ b/src/gclue-location-source.h
@@ -38,6 +38,19 @@ G_BEGIN_DECLS
 #define GCLUE_IS_LOCATION_SOURCE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  GCLUE_TYPE_LOCATION_SOURCE))
 #define GCLUE_LOCATION_SOURCE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  GCLUE_TYPE_LOCATION_SOURCE, GClueLocationSourceClass))
 
+typedef enum {
+        GCLUE_LOCATION_SOURCE_START_RESULT_FAILED = 0,
+        GCLUE_LOCATION_SOURCE_START_RESULT_ALREADY_STARTED,
+        GCLUE_LOCATION_SOURCE_START_RESULT_OK
+} GClueLocationSourceStartResult;
+
+typedef enum {
+        GCLUE_LOCATION_SOURCE_STOP_RESULT_FAILED = 0,
+        GCLUE_LOCATION_SOURCE_STOP_RESULT_ALREADY_STOPPED,
+        GCLUE_LOCATION_SOURCE_STOP_RESULT_STILL_USED,
+        GCLUE_LOCATION_SOURCE_STOP_RESULT_OK
+} GClueLocationSourceStopResult;
+
 typedef struct _GClueLocationSource        GClueLocationSource;
 typedef struct _GClueLocationSourceClass   GClueLocationSourceClass;
 typedef struct _GClueLocationSourcePrivate GClueLocationSourcePrivate;
@@ -54,8 +67,8 @@ struct _GClueLocationSourceClass
 {
         GObjectClass parent_class;
 
-        gboolean (*start) (GClueLocationSource *source);
-        gboolean (*stop)  (GClueLocationSource *source);
+        GClueLocationSourceStartResult (*start) (GClueLocationSource *source);
+        GClueLocationSourceStopResult (*stop)  (GClueLocationSource *source);
 };
 
 GType gclue_location_source_get_type (void) G_GNUC_CONST;
diff --git a/src/gclue-location.c b/src/gclue-location.c
index 3190f6e30fda436d2bee73e57e86d1eac4dd9c0a..b0e5e0d0367d0354cfe0018ac21c45cd591a0ecf 100644
--- a/src/gclue-location.c
+++ b/src/gclue-location.c
@@ -24,12 +24,14 @@
  */
 
 #include "gclue-location.h"
+#include "gclue-nmea-utils.h"
 #include <math.h>
 #include <string.h>
 #include <stdlib.h>
 
 #define TIME_DIFF_THRESHOLD 60000000 /* 60 seconds */
 #define EARTH_RADIUS_KM 6372.795
+#define KNOTS_IN_METERS_PER_SECOND 0.51444
 
 struct _GClueLocationPrivate {
         char   *description;
@@ -228,13 +230,13 @@ static void
 gclue_location_constructed (GObject *object)
 {
         GClueLocation *location = GCLUE_LOCATION (object);
-        GTimeVal tv;
+        gint64 timestamp;
 
         if (location->priv->timestamp != 0)
                 return;
 
-        g_get_current_time (&tv);
-        gclue_location_set_timestamp (location, tv.tv_sec);
+        timestamp = g_get_real_time () / G_USEC_PER_SEC;
+        gclue_location_set_timestamp (location, timestamp);
 }
 
 static void
@@ -390,9 +392,7 @@ gclue_location_class_init (GClueLocationClass *klass)
 static void
 gclue_location_init (GClueLocation *location)
 {
-        location->priv = G_TYPE_INSTANCE_GET_PRIVATE ((location),
-                                                      GCLUE_TYPE_LOCATION,
-                                                      GClueLocationPrivate);
+        location->priv = gclue_location_get_instance_private (location);
 
         location->priv->altitude = GCLUE_LOCATION_ALTITUDE_UNKNOWN;
         location->priv->accuracy = GCLUE_LOCATION_ACCURACY_UNKNOWN;
@@ -491,6 +491,9 @@ parse_nmea_timestamp (const char *nmea_ts)
         now = g_date_time_new_now_utc ();
         ret = g_date_time_to_unix (now);
 
+	if( now == NULL)
+                goto parse_error;
+
         if (strlen (nmea_ts) < 6) {
                 if (strlen (nmea_ts) >= 1)
                         /* Empty string just means no ts, so no warning */
@@ -514,6 +517,8 @@ parse_nmea_timestamp (const char *nmea_ts)
                                   hours,
                                   minutes,
                                   seconds);
+        if (ts == NULL)
+                goto parse_error;
 
         if (g_date_time_difference (ts, now) > TIME_DIFF_THRESHOLD) {
                 g_debug ("NMEA timestamp '%s' in future. Assuming yesterday's.",
@@ -595,17 +600,7 @@ gclue_location_new_full (gdouble     latitude,
                              NULL);
 }
 
-/**
- * gclue_location_create_from_gga:
- * @gga: NMEA GGA sentence
- * @error: Place-holder for errors.
- *
- * Creates a new #GClueLocation object from a GGA sentence.
- *
- * Returns: a new #GClueLocation object, or %NULL on error. Unref using
- * #g_object_unref() when done with it.
- **/
-GClueLocation *
+static GClueLocation *
 gclue_location_create_from_gga (const char *gga, GError **error)
 {
         GClueLocation *location = NULL;
@@ -656,6 +651,121 @@ out:
         return location;
 }
 
+static GClueLocation *
+gclue_location_create_from_rmc (const char     *rmc,
+                                GClueLocation  *prev_location,
+                                GError        **error)
+{
+        GClueLocation *location = NULL;
+        char **parts = g_strsplit (rmc, ",", -1);
+
+        if (g_strv_length (parts) < 13)
+                goto error;
+
+        guint64 timestamp = parse_nmea_timestamp (parts[1]);
+        gdouble lat = parse_coordinate_string (parts[3], parts[4]);
+        gdouble lon = parse_coordinate_string (parts[5], parts[6]);
+
+        if (lat == INVALID_COORDINATE || lon == INVALID_COORDINATE)
+                goto error;
+
+        gdouble speed = GCLUE_LOCATION_SPEED_UNKNOWN;
+        if (parts[7][0] != '\0')
+                speed = g_ascii_strtod (parts[7], NULL) * KNOTS_IN_METERS_PER_SECOND;
+
+        gdouble heading = GCLUE_LOCATION_HEADING_UNKNOWN;
+        if (parts[8][0] != '\0')
+                heading = g_ascii_strtod (parts[8], NULL);
+
+        /* Some receivers use '0.0,0.0' as invalid speed and heading */
+        if (speed == 0.0 && heading == 0.0) {
+                speed = GCLUE_LOCATION_SPEED_UNKNOWN;
+                heading = GCLUE_LOCATION_HEADING_UNKNOWN;
+        }
+
+        location = g_object_new (GCLUE_TYPE_LOCATION,
+                                 "latitude", lat,
+                                 "longitude", lon,
+                                 "timestamp", timestamp,
+                                 "speed", speed,
+                                 "heading", heading,
+                                 NULL);
+
+        if (prev_location != NULL) {
+                g_object_set (location,
+                              "accuracy",
+                              gclue_location_get_accuracy (prev_location),
+                              NULL);
+                g_object_set (location,
+                              "altitude",
+                              gclue_location_get_altitude (prev_location),
+                              NULL);
+        }
+
+        goto out;
+error:
+        g_set_error_literal (error,
+                             G_IO_ERROR,
+                             G_IO_ERROR_INVALID_ARGUMENT,
+                             "Invalid NMEA RMC sentence");
+out:
+        g_strfreev (parts);
+        return location;
+}
+
+/**
+ * gclue_location_create_from_nmeas:
+ * @nmea: A NULL terminated array NMEA sentence strings
+ * @prev_location: Previous location provided from the location source
+ * @error: Place-holder for errors.
+ *
+ * Creates a new #GClueLocation object by combining data from multiple NMEA
+ * sentences.
+ *
+ * Returns: a new #GClueLocation object if GGA or RMC sentences are found,
+ * a %NULL on all other cases and errors. Unref using #g_object_unref() when
+ * done with it.
+ **/
+GClueLocation *
+gclue_location_create_from_nmeas (const char     *nmeas[],
+                                  GClueLocation  *prev_location,
+                                  GError        **error)
+{
+        GClueLocation *gga_loc = NULL;
+        GClueLocation *rmc_loc = NULL;
+        const char **iter;
+
+        for (iter = nmeas; *iter != NULL; iter++) {
+                if (!gga_loc && gclue_nmea_type_is (*iter, "GGA"))
+                        gga_loc = gclue_location_create_from_gga (*iter, NULL);
+                if (!rmc_loc && gclue_nmea_type_is (*iter, "RMC"))
+                        rmc_loc = gclue_location_create_from_rmc
+                                (*iter, prev_location, NULL);
+                if (gga_loc && rmc_loc)
+                    break;
+        }
+
+        if (gga_loc && rmc_loc) {
+                gclue_location_set_speed
+                        (gga_loc, gclue_location_get_speed(rmc_loc));
+                gclue_location_set_heading
+                        (gga_loc, gclue_location_get_heading(rmc_loc));
+                g_object_unref (rmc_loc);
+
+                return gga_loc;
+        }
+        if (gga_loc)
+                return gga_loc;
+        if (rmc_loc)
+                return rmc_loc;
+
+        g_set_error_literal (error,
+                             G_IO_ERROR,
+                             G_IO_ERROR_INVALID_ARGUMENT,
+                             "Valid NMEA GGA or RMC sentence not found");
+        return NULL;
+}
+
 /**
  * gclue_location_duplicate:
  * @location: the #GClueLocation instance to duplicate.
@@ -839,8 +949,8 @@ gclue_location_set_speed_from_prev_location (GClueLocation *location,
                goto out;
         }
 
-        speed = gclue_location_get_distance_from (location, prev_location) *
-                1000.0 / (timestamp - prev_timestamp);
+        speed = gclue_location_get_distance_from (location, prev_location) /
+                (timestamp - prev_timestamp);
 
 out:
         location->priv->speed = speed;
@@ -895,7 +1005,7 @@ void
 gclue_location_set_heading_from_prev_location (GClueLocation *location,
                                                GClueLocation *prev_location)
 {
-        gdouble dx, dy, angle, lat, lon, prev_lat, prev_lon;
+        gdouble dlat, dlon, x, y, angle, lat, lon, prev_lat, prev_lon;
 
         g_return_if_fail (GCLUE_IS_LOCATION (location));
         g_return_if_fail (prev_location == NULL ||
@@ -912,35 +1022,42 @@ gclue_location_set_heading_from_prev_location (GClueLocation *location,
         prev_lat = gclue_location_get_latitude (prev_location);
         prev_lon = gclue_location_get_longitude (prev_location);
 
-        dx = (lat - prev_lat);
-        dy = (lon - prev_lon);
+        if (lat == prev_lat && lon == prev_lon) {
+               location->priv->heading = GCLUE_LOCATION_HEADING_UNKNOWN;
+
+               return;
+        }
 
-        /* atan2 takes in coordinate values of a 2D space and returns the angle
-         * which the line from origin to that coordinate makes with the positive
-         * X-axis, in the range (-PI,+PI]. Converting it into degrees we get the
-         * angle in range (-180,180]. This means East = 0 degree,
-         * West = -180 degrees, North = 90 degrees, South = -90 degrees.
+        /* atan2(y, x) is a function which takes in coordinate values of
+         * a 2D point and returns the angle of line from origin to that
+         * coordinate makes with the positive X-axis, in the range (-PI,+PI].
          *
-         * Passing atan2 a negative value of dx will flip the angles about
-         * Y-axis. This means the angle now returned will be the angle with
-         * respect to negative X-axis. Which makes West = 0 degree,
-         * East = 180 degrees, North = 90 degrees, South = -90 degrees. */
-        angle = atan2(dy, -dx) * 180.0 / M_PI;
-
-        /* Now, North is supposed to be 0 degree. Lets subtract 90 degrees
-         * from angle. After this step West = -90 degrees, East = 90 degrees,
-         * North = 0 degree, South = -180 degrees. */
-        angle -= 90.0;
-
-        /* As we know, angle ~= angle + 360; using this on negative values would
-         * bring the the angle in range [0,360).
+         * We want to match the inclusive end (+PI) of atan2's range to our
+         * desired zero direction (north), so we take the vector pointing
+         * south as our X-axis and the vector pointing east as our Y-axis
+         * and measure our movement vector coordinates */
+        dlon = (lon - prev_lon);
+        dlat = (lat - prev_lat);
+        /* in that basis.
          *
-         * After this step West = 270 degrees, East = 90 degrees,
-         * North = 0 degree, South = 180 degrees. */
-        if (angle < 0)
-                angle += 360.0;
-
-        location->priv->heading = angle;
+         * Latitudes decrease towards the south and longitudes
+         * increase towards the east, so: */
+        x = -dlat;
+        y = dlon;
+        angle = atan2(y, x) * 180.0 / M_PI;
+        /* We now have the angle of our movement vector measured from the
+         * vector pointing south in degrees, with the positive direction
+         * being counterclockwise.
+         *
+         * We want to convert it to heading: Degrees from from north with
+         * positive values only and positive direction being clockwise.
+         *
+         * If angle is positive (the movement is more towards east than
+         * west), we need to subtract it from the heading of our reference
+         * vector (south == 180 deg). If the angle is negative, we need to
+         * add its negative to the heading of the reference vector. Both
+         * cases result in */
+        location->priv->heading = 180.0 - angle;
 
         g_object_notify (G_OBJECT (location), "heading");
 }
@@ -950,11 +1067,11 @@ gclue_location_set_heading_from_prev_location (GClueLocation *location,
  * @loca: a #GClueLocation
  * @locb: a #GClueLocation
  *
- * Calculates the distance in km, along the curvature of the Earth,
+ * Calculates the distance in meters, along the curvature of the Earth,
  * between 2 locations. Note that altitude changes are not
  * taken into account.
  *
- * Returns: a distance in km.
+ * Returns: a distance in meters.
  **/
 double
 gclue_location_get_distance_from (GClueLocation *loca,
@@ -977,5 +1094,5 @@ gclue_location_get_distance_from (GClueLocation *loca,
         a = sin (dlat / 2) * sin (dlat / 2) +
             sin (dlon / 2) * sin (dlon / 2) * cos (lat1) * cos (lat2);
         c = 2 * atan2 (sqrt (a), sqrt (1-a));
-        return EARTH_RADIUS_KM * c;
+        return 1000.0 * EARTH_RADIUS_KM * c;
 }
diff --git a/src/gclue-location.h b/src/gclue-location.h
index 00b18bb9e5b9db55730824111fbfcb3eb59a1874..0db57ac95068d5fdcf7200b5edd2f51701d58512 100644
--- a/src/gclue-location.h
+++ b/src/gclue-location.h
@@ -58,6 +58,8 @@ struct _GClueLocationClass
         GObjectClass parent_class;
 };
 
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (GClueLocation, g_object_unref)
+
 GType gclue_location_get_type (void);
 
 /**
@@ -137,9 +139,10 @@ GClueLocation *gclue_location_new_full
                                    guint64     timestamp,
                                    const char *description);
 
-GClueLocation *gclue_location_create_from_gga
-                                  (const char *gga,
-                                   GError    **error);
+GClueLocation *gclue_location_create_from_nmeas
+                                  (const char     *nmeas[],
+                                   GClueLocation  *prev_location,
+                                   GError        **error);
 
 GClueLocation *gclue_location_duplicate
                                   (GClueLocation *location);
diff --git a/src/gclue-locator.c b/src/gclue-locator.c
index 650be4307eba4173d9dad8a3e870cdaca3f33306..150d82963fe1b4cfedb6c6709d9d4dcb9c2aa093 100644
--- a/src/gclue-locator.c
+++ b/src/gclue-locator.c
@@ -49,9 +49,9 @@
  * location sources from rest of the code
  */
 
-static gboolean
+static GClueLocationSourceStartResult
 gclue_locator_start (GClueLocationSource *source);
-static gboolean
+static GClueLocationSourceStopResult 
 gclue_locator_stop (GClueLocationSource *source);
 
 struct _GClueLocatorPrivate
@@ -60,8 +60,6 @@ struct _GClueLocatorPrivate
         GList *active_sources;
 
         GClueAccuracyLevel accuracy_level;
-
-        guint time_threshold;
 };
 
 G_DEFINE_TYPE_WITH_CODE (GClueLocator,
@@ -78,6 +76,9 @@ enum
 
 static GParamSpec *gParamSpecs[LAST_PROP];
 
+#define MAX_SPEED        500       /* Meters per second */
+#define MAX_LOCATION_AGE (30 * 60) /* Seconds. */
+
 static void
 set_location (GClueLocator  *locator,
               GClueLocation *location)
@@ -90,20 +91,40 @@ set_location (GClueLocator  *locator,
         g_debug ("New location available");
 
         if (cur_location != NULL) {
-            if (gclue_location_get_timestamp (location) <
-                gclue_location_get_timestamp (cur_location)) {
+            guint64 cur_timestamp, new_timestamp;
+            double dist, speed;
+
+            cur_timestamp = gclue_location_get_timestamp (cur_location);
+            new_timestamp = gclue_location_get_timestamp (location);
+            if (new_timestamp < cur_timestamp) {
                     g_debug ("New location older than current, ignoring.");
                     return;
             }
 
-            if (gclue_location_get_distance_from (location, cur_location)
-                * 1000 <
-                gclue_location_get_accuracy (location) &&
+            dist = gclue_location_get_distance_from (location, cur_location);
+            if (new_timestamp > cur_timestamp) {
+                guint64 age = new_timestamp - cur_timestamp;
+
+                if (age < MAX_LOCATION_AGE) {
+                    speed = dist / age;
+                } else {
+                    /* The previous location is too old?
+                     * Force the speed to be within the allowed range then.
+                     */
+                    speed = 0;
+                }
+            } else {
+                speed = G_MAXDOUBLE;
+            }
+
+            if ((dist <= gclue_location_get_accuracy (location) ||
+                 speed > MAX_SPEED) &&
                 gclue_location_get_accuracy (location) >
                 gclue_location_get_accuracy (cur_location)) {
                     /* We only take the new location if either the previous one
-                     * lies outside its accuracy circle or its more or as
-                     * accurate as previous one.
+                     * lies outside its accuracy circle and was reachable with
+                     * a reasonable speed, OR it is more or as accurate as
+                     * the previous one.
                      */
                     g_debug ("Ignoring less accurate new location");
                     return;
@@ -427,25 +448,25 @@ gclue_locator_class_init (GClueLocatorClass *klass)
 static void
 gclue_locator_init (GClueLocator *locator)
 {
-        locator->priv =
-                G_TYPE_INSTANCE_GET_PRIVATE (locator,
-                                            GCLUE_TYPE_LOCATOR,
-                                            GClueLocatorPrivate);
+        locator->priv = gclue_locator_get_instance_private (locator);
 }
 
-static gboolean
+static GClueLocationSourceStartResult
 gclue_locator_start (GClueLocationSource *source)
 {
         GClueLocationSourceClass *base_class;
         GClueLocator *locator;
         GList *node;
+        GClueLocationSourceStartResult base_result;
 
-        g_return_val_if_fail (GCLUE_IS_LOCATOR (source), FALSE);
+        g_return_val_if_fail (GCLUE_IS_LOCATOR (source),
+                              GCLUE_LOCATION_SOURCE_START_RESULT_FAILED);
         locator = GCLUE_LOCATOR (source);
 
         base_class = GCLUE_LOCATION_SOURCE_CLASS (gclue_locator_parent_class);
-        if (!base_class->start (source))
-                return FALSE;
+        base_result = base_class->start (source);
+        if (base_result != GCLUE_LOCATION_SOURCE_START_RESULT_OK)
+                return base_result;
 
         for (node = locator->priv->sources; node != NULL; node = node->next) {
                 GClueLocationSource *src = GCLUE_LOCATION_SOURCE (node->data);
@@ -468,22 +489,24 @@ gclue_locator_start (GClueLocationSource *source)
                 start_source (locator, src);
         }
 
-        return TRUE;
+        return base_result;
 }
 
-static gboolean
+static GClueLocationSourceStopResult
 gclue_locator_stop (GClueLocationSource *source)
 {
         GClueLocationSourceClass *base_class;
         GClueLocator *locator;
         GList *node;
+        GClueLocationSourceStopResult base_result;
 
         g_return_val_if_fail (GCLUE_IS_LOCATOR (source), FALSE);
         locator = GCLUE_LOCATOR (source);
 
         base_class = GCLUE_LOCATION_SOURCE_CLASS (gclue_locator_parent_class);
-        if (!base_class->stop (source))
-                return FALSE;
+        base_result = base_class->stop (source);
+        if (base_result == GCLUE_LOCATION_SOURCE_STOP_RESULT_STILL_USED)
+                return base_result;
 
         for (node = locator->priv->active_sources; node != NULL; node = node->next) {
                 GClueLocationSource *src = GCLUE_LOCATION_SOURCE (node->data);
@@ -497,7 +520,7 @@ gclue_locator_stop (GClueLocationSource *source)
 
         g_list_free (locator->priv->active_sources);
         locator->priv->active_sources = NULL;
-        return TRUE;
+        return base_result;
 }
 
 GClueLocator *
diff --git a/src/gclue-main.c b/src/gclue-main.c
index 811666fd3671ec29ac3c773d635a456958cb6fa1..d14cadc38fad66caa26dab6a7f2c4af732f2ebc3 100644
--- a/src/gclue-main.c
+++ b/src/gclue-main.c
@@ -36,6 +36,7 @@ static gboolean version = FALSE;
 static gint inactivity_timeout = 60;
 static gboolean submit_data = FALSE;
 static char *submit_nick = NULL;
+static char *nmea_socket = NULL;
 
 static GOptionEntry entries[] =
 {
@@ -67,6 +68,13 @@ static GOptionEntry entries[] =
           &submit_nick,
           N_("Nickname to submit network data under (2-32 characters)"),
           "NICK" },
+        { "nmea-socket",
+          'u',
+          0,
+          G_OPTION_ARG_STRING,
+          &nmea_socket,
+          N_("Path to nmea UNIX socket"),
+          NULL },
         { NULL }
 };
 
@@ -180,6 +188,8 @@ main (int argc, char **argv)
                 gclue_config_set_wifi_submit_data (config, submit_data);
         if (submit_nick != NULL)
                 gclue_config_set_wifi_submit_nick (config, submit_nick);
+        if (nmea_socket != NULL)
+                gclue_config_set_nmea_socket (config, nmea_socket);
 
         owner_id = g_bus_own_name (G_BUS_TYPE_SYSTEM,
                                    BUS_NAME,
diff --git a/src/gclue-marshal.list b/src/gclue-marshal.list
index 06068f0653029cba0fd3b7091cdac3f5eeea1fd7..1fe04ca040b0d0126ea208e6f805492ba4f98b7c 100644
--- a/src/gclue-marshal.list
+++ b/src/gclue-marshal.list
@@ -1,2 +1,3 @@
 VOID:UINT,UINT,ULONG,ULONG
+VOID:STRING,ULONG,ULONG,ENUM
 VOID:DOUBLE,DOUBLE
diff --git a/src/gclue-min-uint.c b/src/gclue-min-uint.c
index 3d88f8900a6c430227e626d337f4b26c2bf95671..558bfd76de0b8c5e433209f6bf61bbd343d1de97 100644
--- a/src/gclue-min-uint.c
+++ b/src/gclue-min-uint.c
@@ -134,9 +134,7 @@ gclue_min_uint_class_init (GClueMinUINTClass *klass)
 static void
 gclue_min_uint_init (GClueMinUINT *muint)
 {
-        muint->priv = G_TYPE_INSTANCE_GET_PRIVATE (muint,
-                                                   GCLUE_TYPE_MIN_UINT,
-                                                   GClueMinUINTPrivate);
+        muint->priv = gclue_min_uint_get_instance_private (muint);
         muint->priv->all_values = g_hash_table_new (g_direct_hash,
                                                     g_direct_equal);
         muint->priv->notify_value = TRUE;
diff --git a/src/gclue-modem-gps.c b/src/gclue-modem-gps.c
index 6045d6b010e76426e033667dacc23db372bacd27..ce4967ad4cafa67d65fc2d322937f0e5579b3d10 100644
--- a/src/gclue-modem-gps.c
+++ b/src/gclue-modem-gps.c
@@ -48,9 +48,9 @@ G_DEFINE_TYPE_WITH_CODE (GClueModemGPS,
                          GCLUE_TYPE_LOCATION_SOURCE,
                          G_ADD_PRIVATE (GClueModemGPS))
 
-static gboolean
+static GClueLocationSourceStartResult
 gclue_modem_gps_start (GClueLocationSource *source);
-static gboolean
+static GClueLocationSourceStopResult
 gclue_modem_gps_stop (GClueLocationSource *source);
 
 static void
@@ -155,7 +155,7 @@ gclue_modem_gps_init (GClueModemGPS *source)
         GClueModemGPSPrivate *priv;
         GClueMinUINT *threshold;
 
-        source->priv = G_TYPE_INSTANCE_GET_PRIVATE ((source), GCLUE_TYPE_MODEM_GPS, GClueModemGPSPrivate);
+        source->priv = gclue_modem_gps_get_instance_private (source);
         priv = source->priv;
 
         priv->cancellable = g_cancellable_new ();
@@ -209,14 +209,18 @@ gclue_modem_gps_get_singleton (void)
 
 static void
 on_fix_gps (GClueModem *modem,
-            const char *gga,
+            const char *nmeas[],
             gpointer    user_data)
 {
         GClueLocationSource *source = GCLUE_LOCATION_SOURCE (user_data);
-        GClueLocation *location;
+        GClueLocation *prev_location;
+        g_autoptr(GClueLocation) location = NULL;
         GError *error = NULL;
 
-        location = gclue_location_create_from_gga (gga, &error);
+        prev_location = gclue_location_source_get_location (source);
+        location = gclue_location_create_from_nmeas (nmeas,
+                                                     prev_location,
+                                                     &error);
 
         if (error != NULL) {
             g_warning ("Error: %s", error->message);
@@ -229,18 +233,21 @@ on_fix_gps (GClueModem *modem,
                                             location);
 }
 
-static gboolean
+static GClueLocationSourceStartResult
 gclue_modem_gps_start (GClueLocationSource *source)
 {
         GClueLocationSourceClass *base_class;
         GClueModemGPSPrivate *priv;
+        GClueLocationSourceStartResult base_result;
 
-        g_return_val_if_fail (GCLUE_IS_LOCATION_SOURCE (source), FALSE);
+        g_return_val_if_fail (GCLUE_IS_LOCATION_SOURCE (source),
+                              GCLUE_LOCATION_SOURCE_START_RESULT_FAILED);
         priv = GCLUE_MODEM_GPS (source)->priv;
 
         base_class = GCLUE_LOCATION_SOURCE_CLASS (gclue_modem_gps_parent_class);
-        if (!base_class->start (source))
-                return FALSE;
+        base_result = base_class->start (source);
+        if (base_result != GCLUE_LOCATION_SOURCE_START_RESULT_OK)
+                return base_result;
 
         g_signal_connect (priv->modem,
                           "fix-gps",
@@ -253,21 +260,23 @@ gclue_modem_gps_start (GClueLocationSource *source)
                                         on_gps_enabled,
                                         source);
 
-        return TRUE;
+        return base_result;
 }
 
-static gboolean
+static GClueLocationSourceStopResult
 gclue_modem_gps_stop (GClueLocationSource *source)
 {
         GClueModemGPSPrivate *priv = GCLUE_MODEM_GPS (source)->priv;
         GClueLocationSourceClass *base_class;
         GError *error = NULL;
+        GClueLocationSourceStopResult base_result;
 
         g_return_val_if_fail (GCLUE_IS_LOCATION_SOURCE (source), FALSE);
 
         base_class = GCLUE_LOCATION_SOURCE_CLASS (gclue_modem_gps_parent_class);
-        if (!base_class->stop (source))
-                return FALSE;
+        base_result = base_class->stop (source);
+        if (base_result == GCLUE_LOCATION_SOURCE_STOP_RESULT_STILL_USED)
+                return base_result;
 
         g_signal_handlers_disconnect_by_func (G_OBJECT (priv->modem),
                                               G_CALLBACK (on_fix_gps),
@@ -282,5 +291,5 @@ gclue_modem_gps_stop (GClueLocationSource *source)
                         g_error_free (error);
                 }
 
-        return TRUE;
+        return base_result;
 }
diff --git a/src/gclue-modem-manager.c b/src/gclue-modem-manager.c
index 411492f5dce492f2698f7122d13134a9ca16a7ca..9a8cf57ca165238b036577bc5829013773c44f4c 100644
--- a/src/gclue-modem-manager.c
+++ b/src/gclue-modem-manager.c
@@ -24,7 +24,9 @@
 #include <string.h>
 #include <libmm-glib.h>
 #include "gclue-modem-manager.h"
+#include "gclue-nmea-utils.h"
 #include "gclue-marshal.h"
+#include "gclue-3g-tower.h"
 
 /**
  * SECTION:gclue-modem-manager
@@ -260,27 +262,61 @@ gclue_modem_interface_init (GClueModemInterface *iface)
         iface->disable_gps = gclue_modem_manager_disable_gps;
 }
 
+#if !MM_CHECK_VERSION(1, 18, 0)
+static void
+opc_from_mccmnc (MMLocation3gpp *location_3gpp,
+                 gchar          *opc_buf)
+{
+        guint mcc, mnc;
+
+        mcc = mm_location_3gpp_get_mobile_country_code (location_3gpp);
+        mnc = mm_location_3gpp_get_mobile_network_code (location_3gpp);
+
+        if (mcc < 1000 && mnc < 1000) {
+                g_snprintf (opc_buf, GCLUE_3G_TOWER_OPERATOR_CODE_STR_LEN + 1,
+                            "%03u%03u", mcc, mnc);
+        } else {
+                g_warning ("Invalid MCC or MNC value");
+                opc_buf[0] = '\0';
+        }
+}
+#endif
+
 static gboolean
 is_location_3gpp_same (GClueModemManager *manager,
-                       guint       new_mcc,
-                       guint       new_mnc,
-                       gulong      new_lac,
-                       gulong      new_cell_id)
+                       const gchar       *new_opc,
+                       gulong             new_lac,
+                       gulong             new_cell_id)
 {
         GClueModemManagerPrivate *priv = manager->priv;
-        guint mcc, mnc;
+        const gchar *opc;
         gulong lac, cell_id;
+#if !MM_CHECK_VERSION(1, 18, 0)
+        gchar opc_buf[GCLUE_3G_TOWER_OPERATOR_CODE_STR_LEN + 1];
+#endif
 
         if (priv->location_3gpp == NULL)
                 return FALSE;
 
-        mcc = mm_location_3gpp_get_mobile_country_code (priv->location_3gpp);
-        mnc = mm_location_3gpp_get_mobile_network_code (priv->location_3gpp);
+#if MM_CHECK_VERSION(1, 18, 0)
+        opc = mm_location_3gpp_get_operator_code (priv->location_3gpp);
+#else
+        opc_from_mccmnc (priv->location_3gpp, opc_buf);
+        opc = opc_buf;
+#endif
         lac = mm_location_3gpp_get_location_area_code (priv->location_3gpp);
+
+        // Most likely this is an LTE connection and with the mozilla
+        // services they use the tracking area code in place of the
+        // location area code in this case.
+        // https://ichnaea.readthedocs.io/en/latest/api/geolocate.html#cell-tower-fields
+        if (lac == 0x0 || lac == 0xFFFE) {
+                lac = mm_location_3gpp_get_tracking_area_code(priv->location_3gpp);
+        }
+
         cell_id = mm_location_3gpp_get_cell_id (priv->location_3gpp);
 
-        return (mcc == new_mcc &&
-                mnc == new_mnc &&
+        return (g_strcmp0 (opc, new_opc) == 0 &&
                 lac == new_lac &&
                 cell_id == new_cell_id);
 }
@@ -293,10 +329,14 @@ on_get_3gpp_ready (GObject      *source_object,
         GClueModemManager *manager = GCLUE_MODEM_MANAGER (user_data);
         GClueModemManagerPrivate *priv = manager->priv;
         MMModemLocation *modem_location = MM_MODEM_LOCATION (source_object);
-        MMLocation3gpp *location_3gpp;
+        g_autoptr(MMLocation3gpp) location_3gpp = NULL;
         GError *error = NULL;
-        guint mcc, mnc;
+        const gchar *opc;
         gulong lac, cell_id;
+        GClueTowerTec tec = GCLUE_TOWER_TEC_3G;
+#if !MM_CHECK_VERSION(1, 18, 0)
+        gchar opc_buf[GCLUE_3G_TOWER_OPERATOR_CODE_STR_LEN + 1];
+#endif
 
         location_3gpp = mm_modem_location_get_3gpp_finish (modem_location,
                                                            res,
@@ -313,19 +353,36 @@ on_get_3gpp_ready (GObject      *source_object,
                 return;
         }
 
-        mcc = mm_location_3gpp_get_mobile_country_code (location_3gpp);
-        mnc = mm_location_3gpp_get_mobile_network_code (location_3gpp);
+#if MM_CHECK_VERSION(1, 18, 0)
+        opc = mm_location_3gpp_get_operator_code (location_3gpp);
+#else
+        opc_from_mccmnc (location_3gpp, opc_buf);
+        opc = opc_buf;
+#endif
+        if (!opc || !opc[0])
+                return;
+
         lac = mm_location_3gpp_get_location_area_code (location_3gpp);
+
+        // Most likely this is an LTE connection and with the mozilla
+        // services they use the tracking area code in place of the
+        // location area code in this case.
+        // https://ichnaea.readthedocs.io/en/latest/api/geolocate.html#cell-tower-fields
+        if (lac == 0x0 || lac == 0xFFFE) {
+                lac = mm_location_3gpp_get_tracking_area_code(location_3gpp);
+                tec = GCLUE_TOWER_TEC_4G;
+        }
+
         cell_id = mm_location_3gpp_get_cell_id (location_3gpp);
 
-        if (is_location_3gpp_same (manager, mcc, mnc, lac, cell_id)) {
+        if (is_location_3gpp_same (manager, opc, lac, cell_id)) {
                 g_debug ("New 3GPP location is same as last one");
                 return;
         }
         g_clear_object (&priv->location_3gpp);
-        priv->location_3gpp = location_3gpp;
+        priv->location_3gpp = g_steal_pointer (&location_3gpp);
 
-        g_signal_emit (manager, signals[FIX_3G], 0, mcc, mnc, lac, cell_id);
+        g_signal_emit (manager, signals[FIX_3G], 0, opc, lac, cell_id, tec);
 }
 
 static void
@@ -335,7 +392,7 @@ on_get_cdma_ready (GObject      *source_object,
 {
         GClueModemManager *manager = GCLUE_MODEM_MANAGER (user_data);
         MMModemLocation *modem_location = MM_MODEM_LOCATION (source_object);
-        MMLocationCdmaBs *location_cdma;
+        g_autoptr(MMLocationCdmaBs) location_cdma = NULL;
         GError *error = NULL;
 
         location_cdma = mm_modem_location_get_cdma_bs_finish (modem_location,
@@ -382,8 +439,10 @@ on_get_gps_nmea_ready (GObject      *source_object,
         GClueModemManager *manager = GCLUE_MODEM_MANAGER (user_data);
         GClueModemManagerPrivate *priv = manager->priv;
         MMModemLocation *modem_location = MM_MODEM_LOCATION (source_object);
-        MMLocationGpsNmea *location_nmea;
-        const char *gga;
+        g_autoptr(MMLocationGpsNmea) location_nmea = NULL;
+        static const gchar *sentences[3];
+        const gchar *gga, *rmc;
+        gint i = 0;
         GError *error = NULL;
 
         location_nmea = mm_modem_location_get_gps_nmea_finish (modem_location,
@@ -402,20 +461,28 @@ on_get_gps_nmea_ready (GObject      *source_object,
         }
 
         gga = mm_location_gps_nmea_get_trace (location_nmea, "$GPGGA");
-        if (gga == NULL) {
-                g_debug ("No GGA trace");
-                return;
+        if (gga != NULL && gclue_nmea_type_is (gga, "GGA")) {
+                if (is_location_gga_same (manager, gga)) {
+                        g_debug ("New GGA trace is same as last one: %s", gga);
+                        return;
+                }
+                g_debug ("New GPGGA trace: %s", gga);
+                sentences[i++] = gga;
         }
-
-        if (is_location_gga_same (manager, gga)) {
-                g_debug ("New GGA trace is same as last one: %s", gga);
-                return;
+        rmc = mm_location_gps_nmea_get_trace (location_nmea, "$GPRMC");
+        if (rmc != NULL && gclue_nmea_type_is (rmc, "RMC")) {
+                g_debug ("New GPRMC trace: %s", rmc);
+                sentences[i++] = rmc;
         }
-        g_clear_object (&priv->location_nmea);
-        priv->location_nmea = location_nmea;
+        sentences[i] = NULL;
 
-        g_debug ("New GPGGA trace: %s", gga);
-        g_signal_emit (manager, signals[FIX_GPS], 0, gga);
+        if (sentences[0] == NULL)
+                g_debug ("No GGA or RMC trace");
+        else
+                g_signal_emit (manager, signals[FIX_GPS], 0, sentences);
+
+        g_clear_object (&priv->location_nmea);
+        priv->location_nmea = g_steal_pointer (&location_nmea);
 }
 
 static void
@@ -766,9 +833,7 @@ gclue_modem_manager_constructed (GObject *object)
 static void
 gclue_modem_manager_init (GClueModemManager *manager)
 {
-        manager->priv = G_TYPE_INSTANCE_GET_PRIVATE ((manager),
-                                                     GCLUE_TYPE_MODEM_MANAGER,
-                                                     GClueModemManagerPrivate);
+        manager->priv = gclue_modem_manager_get_instance_private (manager);
 }
 
 static void
@@ -927,11 +992,29 @@ gclue_modem_manager_enable_gps (GClueModem         *modem,
                                 GAsyncReadyCallback callback,
                                 gpointer            user_data)
 {
+        MMModemLocationSource assistance_caps;
+
         g_return_if_fail (GCLUE_IS_MODEM_MANAGER (modem));
         g_return_if_fail (gclue_modem_manager_get_is_gps_available (modem));
 
+        assistance_caps = MM_MODEM_LOCATION_SOURCE_NONE;
+#if MM_CHECK_VERSION(1, 12, 0)
+        /* Prefer MSB assistance */
+        if (modem_has_caps (GCLUE_MODEM_MANAGER (modem),
+                            MM_MODEM_LOCATION_SOURCE_AGPS_MSB)) {
+                assistance_caps |= MM_MODEM_LOCATION_SOURCE_AGPS_MSB;
+                g_debug ("Using MSB assisted GPS");
+        } else if (modem_has_caps (GCLUE_MODEM_MANAGER (modem),
+                                   MM_MODEM_LOCATION_SOURCE_AGPS_MSA)) {
+                assistance_caps |= MM_MODEM_LOCATION_SOURCE_AGPS_MSA;
+                g_debug ("Using MSA assisted GPS");
+        } else {
+                g_debug ("Assisted GPS not available");
+        }
+#endif
+
         enable_caps (GCLUE_MODEM_MANAGER (modem),
-                     MM_MODEM_LOCATION_SOURCE_GPS_NMEA,
+                     MM_MODEM_LOCATION_SOURCE_GPS_NMEA | assistance_caps,
                      cancellable,
                      callback,
                      user_data);
@@ -994,15 +1077,22 @@ gclue_modem_manager_disable_gps (GClueModem   *modem,
                                  GError      **error)
 {
         GClueModemManager *manager;
+        MMModemLocationSource assistance_caps;
 
         g_return_val_if_fail (GCLUE_IS_MODEM_MANAGER (modem), FALSE);
         g_return_val_if_fail (gclue_modem_manager_get_is_gps_available (modem), FALSE);
         manager = GCLUE_MODEM_MANAGER (modem);
 
+#if MM_CHECK_VERSION(1, 12, 0)
+        assistance_caps = manager->priv->caps & (MM_MODEM_LOCATION_SOURCE_AGPS_MSA | MM_MODEM_LOCATION_SOURCE_AGPS_MSB);
+#else
+        assistance_caps = MM_MODEM_LOCATION_SOURCE_NONE;
+#endif
+
         g_clear_object (&manager->priv->location_nmea);
         g_debug ("Clearing GPS NMEA caps from modem");
         return clear_caps (manager,
-                           MM_MODEM_LOCATION_SOURCE_GPS_NMEA,
+                           MM_MODEM_LOCATION_SOURCE_GPS_NMEA | assistance_caps,
                            cancellable,
                            error);
 }
diff --git a/src/gclue-modem.c b/src/gclue-modem.c
index c9ea3869563ec5418781beb15e070988c2819ab0..2ce0e1d0e94c4fc0181408a96732f2a6001075c2 100644
--- a/src/gclue-modem.c
+++ b/src/gclue-modem.c
@@ -78,13 +78,13 @@ gclue_modem_default_init (GClueModemInterface *iface)
                       0,
                       NULL,
                       NULL,
-                      gclue_marshal_VOID__UINT_UINT_ULONG_ULONG,
+                      gclue_marshal_VOID__STRING_ULONG_ULONG_ENUM,
                       G_TYPE_NONE,
                       4,
-                      G_TYPE_UINT,
-                      G_TYPE_UINT,
+                      G_TYPE_STRING,
                       G_TYPE_ULONG,
-                      G_TYPE_ULONG);
+                      G_TYPE_ULONG,
+                      G_TYPE_INT);
 
         g_signal_new ("fix-cdma",
                       GCLUE_TYPE_MODEM,
@@ -104,10 +104,10 @@ gclue_modem_default_init (GClueModemInterface *iface)
                       0,
                       NULL,
                       NULL,
-                      g_cclosure_marshal_VOID__STRING,
+                      g_cclosure_marshal_VOID__BOXED,
                       G_TYPE_NONE,
                       1,
-                      G_TYPE_STRING);
+                      G_TYPE_STRV);
 }
 
 gboolean
diff --git a/src/gclue-mozilla.c b/src/gclue-mozilla.c
index ffdb77c6f6d8ace62e71b0aefd5148ffcd1f967b..8e602c13983ed60c63898a88bab6ce02e67dbcd6 100644
--- a/src/gclue-mozilla.c
+++ b/src/gclue-mozilla.c
@@ -40,8 +40,8 @@
  * its easy to switch to Google's API.
  **/
 
-#define BSSID_LEN 7
-#define BSSID_STR_LEN 18
+#define BSSID_LEN 6
+#define BSSID_STR_LEN 17
 #define MAX_SSID_LEN 32
 
 static guint
@@ -53,7 +53,7 @@ variant_to_string (GVariant *variant, guint max_len, char *ret)
         len = g_variant_n_children (variant);
         if (len == 0)
                 return 0;
-        g_return_val_if_fail(len < max_len, 0);
+        g_return_val_if_fail(len <= max_len, 0);
         ret[len] = '\0';
 
         for (i = 0; i < len; i++)
@@ -79,7 +79,7 @@ static gboolean
 get_bssid_from_bss (WPABSS *bss, char *bssid)
 {
         GVariant *variant;
-        char raw_bssid[BSSID_LEN] = { 0 };
+        char raw_bssid[BSSID_LEN + 1] = { 0 };
         guint raw_len, i;
 
         variant = wpa_bss_get_bssid (bss);
@@ -87,12 +87,12 @@ get_bssid_from_bss (WPABSS *bss, char *bssid)
                 return FALSE;
 
         raw_len = variant_to_string (variant, BSSID_LEN, raw_bssid);
-        g_return_val_if_fail (raw_len == BSSID_LEN - 1, FALSE);
+        g_return_val_if_fail (raw_len == BSSID_LEN, FALSE);
 
-        for (i = 0; i < BSSID_LEN - 1; i++) {
+        for (i = 0; i < BSSID_LEN; i++) {
                 unsigned char c = (unsigned char) raw_bssid[i];
 
-                if (i == BSSID_LEN - 2) {
+                if (i == BSSID_LEN - 1) {
                         g_snprintf (bssid + (i * 3), 3, "%02x", c);
                 } else {
                         g_snprintf (bssid + (i * 3), 4, "%02x:", c);
@@ -112,6 +112,30 @@ get_url (void)
         return gclue_config_get_wifi_url (config);
 }
 
+static gboolean
+operator_code_to_mcc_mnc (const gchar *opc,
+                          gint64      *mcc_p,
+                          gint64      *mnc_p)
+{
+        gchar *end;
+        gchar mcc_str[GCLUE_3G_TOWER_COUNTRY_CODE_STR_LEN + 1] = { 0 };
+
+        g_strlcpy (mcc_str, opc, GCLUE_3G_TOWER_COUNTRY_CODE_STR_LEN + 1);
+        *mcc_p = g_ascii_strtoll (mcc_str, &end, 10);
+        if (*end != '\0')
+                goto error;
+
+        *mnc_p = g_ascii_strtoll (opc + GCLUE_3G_TOWER_COUNTRY_CODE_STR_LEN,
+                                  &end, 10);
+        if (*end != '\0')
+                goto error;
+
+        return TRUE;
+error:
+        g_warning ("Operator code conversion failed");
+        return FALSE;
+}
+
 SoupMessage *
 gclue_mozilla_create_query (GList        *bss_list, /* As in Access Points */
                             GClue3GTower *tower,
@@ -126,6 +150,7 @@ gclue_mozilla_create_query (GList        *bss_list, /* As in Access Points */
         const char *uri;
         guint n_non_ignored_bsss;
         GList *iter;
+        gint64 mcc, mnc;
 
         builder = json_builder_new ();
         json_builder_begin_object (builder);
@@ -147,7 +172,9 @@ gclue_mozilla_create_query (GList        *bss_list, /* As in Access Points */
                 n_non_ignored_bsss++;
         }
 
-        if (tower != NULL) {
+        if (tower != NULL &&
+            operator_code_to_mcc_mnc (tower->opc, &mcc, &mnc)) {
+
                 json_builder_set_member_name (builder, "radioType");
                 json_builder_add_string_value (builder, "gsm");
 
@@ -159,11 +186,15 @@ gclue_mozilla_create_query (GList        *bss_list, /* As in Access Points */
                 json_builder_set_member_name (builder, "cellId");
                 json_builder_add_int_value (builder, tower->cell_id);
                 json_builder_set_member_name (builder, "mobileCountryCode");
-                json_builder_add_int_value (builder, tower->mcc);
+                json_builder_add_int_value (builder, mcc);
                 json_builder_set_member_name (builder, "mobileNetworkCode");
-                json_builder_add_int_value (builder, tower->mnc);
+                json_builder_add_int_value (builder, mnc);
                 json_builder_set_member_name (builder, "locationAreaCode");
                 json_builder_add_int_value (builder, tower->lac);
+                if (tower->tec == GCLUE_TOWER_TEC_4G) {
+                        json_builder_set_member_name (builder, "radioType");
+                        json_builder_add_string_value (builder, "lte");
+		}
 
                 json_builder_end_object (builder);
 
@@ -176,7 +207,7 @@ gclue_mozilla_create_query (GList        *bss_list, /* As in Access Points */
 
                 for (iter = bss_list; iter != NULL; iter = iter->next) {
                         WPABSS *bss = WPA_BSS (iter->data);
-                        char mac[BSSID_STR_LEN] = { 0 };
+                        char mac[BSSID_STR_LEN + 1] = { 0 };
                         gint16 strength_dbm;
 
                         if (gclue_mozilla_should_ignore_bss (bss))
@@ -294,12 +325,13 @@ gclue_mozilla_create_submit_query (GClueLocation   *location,
         JsonBuilder *builder;
         JsonGenerator *generator;
         JsonNode *root_node;
-        char *data, *timestamp;
+        char *data, *timestr;
         const char *url, *nick;
         gsize data_len;
         GList *iter;
         gdouble lat, lon, accuracy, altitude;
-        GTimeVal tv;
+        GDateTime *datetime;
+        gint64 mcc, mnc;
 
         url = get_submit_config (&nick);
         if (url == NULL)
@@ -333,12 +365,16 @@ gclue_mozilla_create_submit_query (GClueLocation   *location,
                 json_builder_add_double_value (builder, altitude);
         }
 
-        tv.tv_sec = gclue_location_get_timestamp (location);
-        tv.tv_usec = 0;
-        timestamp = g_time_val_to_iso8601 (&tv);
+        datetime = g_date_time_new_from_unix_local
+                (gclue_location_get_timestamp (location));
+        /* We need to be compatible with GLib 2.56 so we cannot use this:
+         * timestr = g_date_time_format_iso8601 (datetime);
+         * Construct the format manually instead: */
+        timestr = g_date_time_format (datetime, "%Y-%m-%dT%H:%M:%S%:::z");
         json_builder_set_member_name (builder, "time");
-        json_builder_add_string_value (builder, timestamp);
-        g_free (timestamp);
+        json_builder_add_string_value (builder, timestr);
+        g_free (timestr);
+        g_date_time_unref (datetime);
 
         json_builder_set_member_name (builder, "radioType");
         json_builder_add_string_value (builder, "gsm");
@@ -349,7 +385,7 @@ gclue_mozilla_create_submit_query (GClueLocation   *location,
 
                 for (iter = bss_list; iter != NULL; iter = iter->next) {
                         WPABSS *bss = WPA_BSS (iter->data);
-                        char mac[BSSID_STR_LEN] = { 0 };
+                        char mac[BSSID_STR_LEN + 1] = { 0 };
                         gint16 strength_dbm;
                         guint16 frequency;
 
@@ -374,7 +410,9 @@ gclue_mozilla_create_submit_query (GClueLocation   *location,
                 json_builder_end_array (builder); /* wifi */
         }
 
-        if (tower != NULL) {
+        if (tower != NULL &&
+            operator_code_to_mcc_mnc (tower->opc, &mcc, &mnc)) {
+
                 json_builder_set_member_name (builder, "cell");
                 json_builder_begin_array (builder);
 
@@ -385,9 +423,9 @@ gclue_mozilla_create_submit_query (GClueLocation   *location,
                 json_builder_set_member_name (builder, "cid");
                 json_builder_add_int_value (builder, tower->cell_id);
                 json_builder_set_member_name (builder, "mcc");
-                json_builder_add_int_value (builder, tower->mcc);
+                json_builder_add_int_value (builder, mcc);
                 json_builder_set_member_name (builder, "mnc");
-                json_builder_add_int_value (builder, tower->mnc);
+                json_builder_add_int_value (builder, mnc);
                 json_builder_set_member_name (builder, "lac");
                 json_builder_add_int_value (builder, tower->lac);
 
@@ -428,8 +466,8 @@ out:
 gboolean
 gclue_mozilla_should_ignore_bss (WPABSS *bss)
 {
-        char ssid[MAX_SSID_LEN] = { 0 };
-        char bssid[BSSID_STR_LEN] = { 0 };
+        char ssid[MAX_SSID_LEN + 1] = { 0 };
+        char bssid[BSSID_STR_LEN + 1] = { 0 };
         guint len;
 
         if (!get_bssid_from_bss (bss, bssid)) {
diff --git a/src/gclue-nmea-source.c b/src/gclue-nmea-source.c
index c57da6847b08215733bd82cd46393a9047334364..68dbf298aacab3db3c2a0bd8a2a413d64e79c959 100644
--- a/src/gclue-nmea-source.c
+++ b/src/gclue-nmea-source.c
@@ -23,6 +23,8 @@
 
 #include <stdlib.h>
 #include <glib.h>
+#include "gclue-config.h"
+#include "gclue-nmea-utils.h"
 #include "gclue-nmea-source.h"
 #include "gclue-location.h"
 #include "config.h"
@@ -33,6 +35,7 @@
 #include <avahi-common/malloc.h>
 #include <avahi-common/error.h>
 #include <avahi-glib/glib-watch.h>
+#include <gio/gunixsocketaddress.h>
 
 typedef struct AvahiServiceInfo AvahiServiceInfo;
 
@@ -56,9 +59,9 @@ G_DEFINE_TYPE_WITH_CODE (GClueNMEASource,
                          GCLUE_TYPE_LOCATION_SOURCE,
                          G_ADD_PRIVATE (GClueNMEASource))
 
-static gboolean
+static GClueLocationSourceStartResult
 gclue_nmea_source_start (GClueLocationSource *source);
-static gboolean
+static GClueLocationSourceStopResult
 gclue_nmea_source_stop (GClueLocationSource *source);
 
 static void
@@ -90,16 +93,13 @@ avahi_service_new (const char        *identifier,
                    guint16            port,
                    GClueAccuracyLevel accuracy)
 {
-        GTimeVal tv;
-
         AvahiServiceInfo *service = g_slice_new0 (AvahiServiceInfo);
 
         service->identifier = g_strdup (identifier);
         service->host_name = g_strdup (host_name);
         service->port = port;
         service->accuracy = accuracy;
-        g_get_current_time (&tv);
-        service->timestamp = tv.tv_sec;
+        service->timestamp = g_get_real_time () / G_USEC_PER_SEC;
 
         return service;
 }
@@ -201,6 +201,12 @@ add_new_service (GClueNMEASource *source,
         GEnumClass *enum_class;
         GEnumValue *enum_value;
 
+        if (port == 0) {
+	        accuracy = GCLUE_ACCURACY_LEVEL_EXACT;
+
+	        goto CREATE_SERVICE;
+        }
+
         node = avahi_string_list_find (txt, "accuracy");
 
         if (node == NULL) {
@@ -442,59 +448,87 @@ browse_callback (AvahiServiceBrowser   *service_browser,
         }
 }
 
+#define NMEA_STR_LEN 128
 static void
-on_read_gga_sentence (GObject      *object,
-                      GAsyncResult *result,
-                      gpointer      user_data)
+on_read_nmea_sentence (GObject      *object,
+                       GAsyncResult *result,
+                       gpointer      user_data)
 {
         GClueNMEASource *source = GCLUE_NMEA_SOURCE (user_data);
         GDataInputStream *data_input_stream = G_DATA_INPUT_STREAM (object);
         GError *error = NULL;
-        GClueLocation *location;
+        GClueLocation *prev_location;
+        g_autoptr(GClueLocation) location = NULL;
         gsize data_size = 0 ;
         char *message;
+        gint i;
+        static const gchar *sentences[3] = { 0 };
+        static gchar gga[NMEA_STR_LEN] = { 0 };
+        static gchar rmc[NMEA_STR_LEN] = { 0 };
+
 
         message = g_data_input_stream_read_line_finish (data_input_stream,
                                                         result,
                                                         &data_size,
                                                         &error);
 
-        if (message == NULL) {
-                if (error != NULL) {
-                        if (error->code == G_IO_ERROR_CLOSED)
-                                g_debug ("Socket closed.");
-                        else if (error->code != G_IO_ERROR_CANCELLED)
-                                g_warning ("Error when receiving message: %s",
-                                           error->message);
-                        g_error_free (error);
+        do {
+                if (message == NULL) {
+                        if (error != NULL) {
+                                if (error->code == G_IO_ERROR_CLOSED)
+                                        g_debug ("Socket closed.");
+                                else if (error->code != G_IO_ERROR_CANCELLED)
+                                        g_warning ("Error when receiving message: %s",
+                                                   error->message);
+                                g_error_free (error);
+                        } else {
+                                g_debug ("Nothing to read");
+                        }
+                        g_object_unref (data_input_stream);
+
+                        if (source->priv->active_service != NULL)
+                                /* In case service did not advertise it exiting
+                                 * or we failed to receive it's notification.
+                                 */
+                                remove_service (source, source->priv->active_service);
+
+                        gga[0] = '\0';
+                        rmc[0] = '\0';
+                        return;
+                }
+                g_debug ("Network source sent: \"%s\"", message);
+
+                if (gclue_nmea_type_is (message, "GGA")) {
+                        g_strlcpy (gga, message, NMEA_STR_LEN);
+                } else if (gclue_nmea_type_is (message, "RMC")) {
+                        g_strlcpy (rmc, message, NMEA_STR_LEN);
                 } else {
-                        g_debug ("Nothing to read");
+                        g_debug ("Ignoring NMEA sentence, as it's neither GGA or RMC: %s", message);
                 }
-                g_object_unref (data_input_stream);
 
-                if (source->priv->active_service != NULL)
-                        /* In case service did not advertise it exiting
-                         * or we failed to receive it's notification.
-                         */
-                        remove_service (source, source->priv->active_service);
+                message = (char *) g_buffered_input_stream_peek_buffer
+                        (G_BUFFERED_INPUT_STREAM (data_input_stream),
+                         &data_size);
+                if (g_strstr_len (message, data_size, "\n")) {
+                    message = g_data_input_stream_read_line
+                            (data_input_stream, &data_size, NULL, &error);
+                } else {
+                    break;
+                }
+        } while (TRUE);
 
-                return;
-        }
-        g_debug ("Network source sent: \"%s\"", message);
-
-        if (!g_str_has_prefix (message, "$GAGGA") &&  /* Galieo */
-            !g_str_has_prefix (message, "$GBGGA") &&  /* BeiDou */
-            !g_str_has_prefix (message, "$BDGGA") &&  /* BeiDou */
-            !g_str_has_prefix (message, "$GLGGA") &&  /* GLONASS */
-            !g_str_has_prefix (message, "$GNGGA") &&  /* GNSS (combined) */
-            !g_str_has_prefix (message, "$GPGGA") &&  /* GPS, SBAS, QZSS */
-            !g_str_has_prefix (message, "$QZGGA")) {  /* QZSS */
-                g_debug ("Ignoring non-GGA sentence from NMEA source");
-
-                goto READ_NEXT_LINE;
-        }
+        i = 0;
+        if (gga[0])
+                sentences[i++] = gga;
+        if (rmc[0])
+                sentences[i++] = rmc;
+        sentences[i] = NULL;
 
-        location = gclue_location_create_from_gga (message, &error);
+        prev_location = gclue_location_source_get_location
+                (GCLUE_LOCATION_SOURCE (source));
+        location = gclue_location_create_from_nmeas (sentences,
+                                                     prev_location,
+                                                     &error);
 
         if (error != NULL) {
                 g_warning ("Error: %s", error->message);
@@ -504,11 +538,14 @@ on_read_gga_sentence (GObject      *object,
                         (GCLUE_LOCATION_SOURCE (source), location);
         }
 
-READ_NEXT_LINE:
+        gga[0] = '\0';
+        rmc[0] = '\0';
+        sentences[0] = NULL;
+
         g_data_input_stream_read_line_async (data_input_stream,
                                              G_PRIORITY_DEFAULT,
                                              source->priv->cancellable,
-                                             on_read_gga_sentence,
+                                             on_read_nmea_sentence,
                                              source);
 }
 
@@ -543,7 +580,7 @@ on_connection_to_location_server (GObject      *object,
         g_data_input_stream_read_line_async (data_input_stream,
                                              G_PRIORITY_DEFAULT,
                                              source->priv->cancellable,
-                                             on_read_gga_sentence,
+                                             on_read_nmea_sentence,
                                              source);
 }
 
@@ -551,6 +588,8 @@ static void
 connect_to_service (GClueNMEASource *source)
 {
         GClueNMEASourcePrivate *priv = source->priv;
+        GSocketAddress *addr;
+        GSocketConnectable *connectable;
 
         if (priv->all_services == NULL)
                 return;
@@ -563,13 +602,23 @@ connect_to_service (GClueNMEASource *source)
          */
         priv->active_service = (AvahiServiceInfo *) priv->all_services->data;
 
-        g_socket_client_connect_to_host_async
-                (priv->client,
-                 priv->active_service->host_name,
-                 priv->active_service->port,
-                 priv->cancellable,
-                 on_connection_to_location_server,
-                 source);
+        if ( priv->active_service->port != 0 )
+		g_socket_client_connect_to_host_async
+			(priv->client,
+			 priv->active_service->host_name,
+			 priv->active_service->port,
+			 priv->cancellable,
+			 on_connection_to_location_server,
+			 source);
+        else {
+		addr = g_unix_socket_address_new(priv->active_service->host_name);
+		connectable = G_SOCKET_CONNECTABLE (addr);
+		g_socket_client_connect_async (priv->client,
+                               connectable,
+                               priv->cancellable,
+                               on_connection_to_location_server,
+                               source);
+        }
 }
 
 static void
@@ -629,11 +678,11 @@ gclue_nmea_source_init (GClueNMEASource *source)
         AvahiServiceBrowser *service_browser;
         const AvahiPoll *poll_api;
         AvahiGLibPoll *glib_poll;
+        const char *nmea_socket;
+        GClueConfig *config;
         int error;
 
-        source->priv = G_TYPE_INSTANCE_GET_PRIVATE ((source),
-                                                    GCLUE_TYPE_NMEA_SOURCE,
-                                                    GClueNMEASourcePrivate);
+        source->priv = gclue_nmea_source_get_instance_private (source);
         priv = source->priv;
 
         glib_poll = avahi_glib_poll_new (NULL, G_PRIORITY_DEFAULT);
@@ -641,6 +690,17 @@ gclue_nmea_source_init (GClueNMEASource *source)
 
         priv->cancellable = g_cancellable_new ();
 
+        config = gclue_config_get_singleton ();
+
+        nmea_socket = gclue_config_get_nmea_socket (config);
+        if (nmea_socket != NULL) {
+                add_new_service (source,
+                                 "nmea-socket",
+                                 nmea_socket,
+                                 0,
+                                 NULL);
+        }
+
         avahi_client_new (poll_api,
                           0,
                           client_callback,
@@ -696,34 +756,39 @@ gclue_nmea_source_get_singleton (void)
         return source;
 }
 
-static gboolean
+static GClueLocationSourceStartResult
 gclue_nmea_source_start (GClueLocationSource *source)
 {
         GClueLocationSourceClass *base_class;
+        GClueLocationSourceStartResult base_result;
 
-        g_return_val_if_fail (GCLUE_IS_NMEA_SOURCE (source), FALSE);
+        g_return_val_if_fail (GCLUE_IS_NMEA_SOURCE (source),
+                              GCLUE_LOCATION_SOURCE_START_RESULT_FAILED);
 
         base_class = GCLUE_LOCATION_SOURCE_CLASS (gclue_nmea_source_parent_class);
-        if (!base_class->start (source))
-                return FALSE;
+        base_result = base_class->start (source);
+        if (base_result != GCLUE_LOCATION_SOURCE_START_RESULT_OK)
+                return base_result;
 
         connect_to_service (GCLUE_NMEA_SOURCE (source));
 
-        return TRUE;
+        return base_result;
 }
 
-static gboolean
+static GClueLocationSourceStopResult
 gclue_nmea_source_stop (GClueLocationSource *source)
 {
         GClueLocationSourceClass *base_class;
+        GClueLocationSourceStopResult base_result;
 
         g_return_val_if_fail (GCLUE_IS_NMEA_SOURCE (source), FALSE);
 
         base_class = GCLUE_LOCATION_SOURCE_CLASS (gclue_nmea_source_parent_class);
-        if (!base_class->stop (source))
-                return FALSE;
+        base_result = base_class->stop (source);
+        if (base_result == GCLUE_LOCATION_SOURCE_STOP_RESULT_STILL_USED)
+                return base_result;
 
         disconnect_from_service (GCLUE_NMEA_SOURCE (source));
 
-        return TRUE;
+        return base_result;
 }
diff --git a/src/gclue-nmea-source.h b/src/gclue-nmea-source.h
index dff0e76369f72e220a4e31854bbe419f6f1aa0e5..b7c3341537f9747771205adc1f6daca4505b5c28 100644
--- a/src/gclue-nmea-source.h
+++ b/src/gclue-nmea-source.h
@@ -64,7 +64,7 @@ struct _GClueNMEASourceClass {
         GClueLocationSourceClass parent_class;
 };
 
-GClueNMEASource * gclue_nmea_source_get_singleton (void);
+GClueNMEASource *gclue_nmea_source_get_singleton (void);
 
 G_END_DECLS
 
diff --git a/src/gclue-nmea-utils.c b/src/gclue-nmea-utils.c
new file mode 100644
index 0000000000000000000000000000000000000000..b38196d8b296c662a76937be57079b233a449454
--- /dev/null
+++ b/src/gclue-nmea-utils.c
@@ -0,0 +1,38 @@
+/* vim: set et ts=8 sw=8: */
+/*
+ * Geoclue is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Geoclue is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with Geoclue; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <string.h>
+#include "gclue-nmea-utils.h"
+
+/**
+ * gclue_nmea_type_is:
+ * @msg: NMEA sentence
+ * @nmeatype: A three character NMEA sentence type string ("GGA", "RMC" etc.)
+ *
+ * Returns: whether given NMEA sentence is of the given type
+ **/
+gboolean
+gclue_nmea_type_is (const char *msg, const char *nmeatype)
+{
+        g_assert (strnlen (nmeatype, 4) < 4);
+
+        return strnlen (msg, 7) > 6 &&
+                g_str_has_prefix (msg, "$") &&
+                g_str_has_prefix (msg+3, nmeatype);
+}
+
diff --git a/src/gclue-nmea-utils.h b/src/gclue-nmea-utils.h
new file mode 100644
index 0000000000000000000000000000000000000000..618dda7eccec848150a27ef36fcef24bf0e1c205
--- /dev/null
+++ b/src/gclue-nmea-utils.h
@@ -0,0 +1,30 @@
+/* vim: set et ts=8 sw=8: */
+/*
+ * Geoclue is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Geoclue is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with Geoclue; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef GCLUE_NMEA_UTILS_H
+#define GCLUE_NMEA_UTILS_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+gboolean         gclue_nmea_type_is              (const char *msg, const char *nmeatype);
+
+G_END_DECLS
+
+#endif /* GCLUE_NMEA_UTILS_H */
diff --git a/src/gclue-service-client.c b/src/gclue-service-client.c
index 62ea932557c5f02a576939dd7d4f5610a299b3d3..8f0b3533b900f2ab354d83794dd78471e568a0c5 100644
--- a/src/gclue-service-client.c
+++ b/src/gclue-service-client.c
@@ -49,6 +49,7 @@ struct _GClueServiceClientPrivate
 
         GClueServiceLocation *location;
         GClueServiceLocation *prev_location;
+        GClueLocation *signaled_location;
         guint distance_threshold;
         guint time_threshold;
 
@@ -124,24 +125,22 @@ distance_below_threshold (GClueServiceClient *client,
                           GClueLocation      *location)
 {
         GClueServiceClientPrivate *priv = client->priv;
-        GClueLocation *cur_location;
         gdouble distance;
-        gdouble threshold_km;
+        gdouble threshold;
 
         if (priv->distance_threshold == 0)
                 return FALSE;
 
-        g_object_get (priv->location,
-                      "location", &cur_location,
-                      NULL);
-        distance = gclue_location_get_distance_from (cur_location, location);
-        g_object_unref (cur_location);
-
-        threshold_km = priv->distance_threshold / 1000.0;
-        if (distance < threshold_km) {
-                g_debug ("Distance from previous location is %f km and "
-                         "below threshold of %f km.",
-                         distance, threshold_km); 
+        if (!priv->signaled_location)
+                return FALSE;
+
+        distance = gclue_location_get_distance_from (priv->signaled_location,
+                                                     location);
+        threshold = priv->distance_threshold;
+        if (distance < threshold) {
+                g_debug ("Distance from previous location is %f m and "
+                         "below threshold of %f m.",
+                         distance, threshold);
                 return TRUE;
         }
 
@@ -153,22 +152,18 @@ time_below_threshold (GClueServiceClient *client,
                       GClueLocation      *location)
 {
         GClueServiceClientPrivate *priv = client->priv;
-        GClueLocation *cur_location;
-        gint64 cur_ts, ts;
+        gint64 cur_ts, new_ts;
         guint64 diff_ts;
 
         if (priv->time_threshold == 0)
                 return FALSE;
 
-        g_object_get (priv->location,
-                      "location", &cur_location,
-                      NULL);
-
-        cur_ts = gclue_location_get_timestamp (cur_location);
-        ts = gclue_location_get_timestamp (location);
-        diff_ts = ABS (ts - cur_ts);
+        if (!priv->signaled_location)
+                return FALSE;
 
-        g_object_unref (cur_location);
+        cur_ts = gclue_location_get_timestamp (priv->signaled_location);
+        new_ts = gclue_location_get_timestamp (location);
+        diff_ts = ABS (new_ts - cur_ts);
 
         if (diff_ts < priv->time_threshold) {
                 g_debug ("Time difference between previous and new location"
@@ -205,19 +200,19 @@ on_locator_location_changed (GObject    *gobject,
         GClueServiceClient *client = GCLUE_SERVICE_CLIENT (user_data);
         GClueServiceClientPrivate *priv = client->priv;
         GClueLocationSource *locator = GCLUE_LOCATION_SOURCE (gobject);
-        GClueLocation *location_info;
+        GClueLocation *new_location;
         char *path = NULL;
         const char *prev_path;
         GError *error = NULL;
 
-        location_info = gclue_location_source_get_location (locator);
-        if (location_info == NULL)
+        new_location = gclue_location_source_get_location (locator);
+        if (new_location == NULL)
                 return; /* No location found yet */
 
-        if (priv->location != NULL && below_threshold (client, location_info)) {
+        if (priv->location != NULL && below_threshold (client, new_location)) {
                 g_debug ("Updating location, below threshold");
                 g_object_set (priv->location,
-                              "location", location_info,
+                              "location", new_location,
                               NULL);
                 return;
         }
@@ -232,7 +227,7 @@ on_locator_location_changed (GObject    *gobject,
         priv->location = gclue_service_location_new (priv->client_info,
                                                      path,
                                                      priv->connection,
-                                                     location_info,
+                                                     new_location,
                                                      &error);
         if (priv->location == NULL)
                 goto error_out;
@@ -244,8 +239,12 @@ on_locator_location_changed (GObject    *gobject,
 
         gclue_dbus_client_set_location (GCLUE_DBUS_CLIENT (client), path);
 
+        g_clear_object (&priv->signaled_location);
+        priv->signaled_location = g_object_ref (new_location);
+
         if (!emit_location_updated (client, prev_path, path, &error))
                 goto error_out;
+
         goto out;
 
 error_out:
@@ -262,7 +261,7 @@ start_client (GClueServiceClient *client, GClueAccuracyLevel accuracy_level)
 
         gclue_dbus_client_set_active (GCLUE_DBUS_CLIENT (client), TRUE);
         priv->locator = gclue_locator_new (accuracy_level);
-        gclue_locator_set_time_threshold (priv->locator, 0);
+        gclue_locator_set_time_threshold (priv->locator, priv->time_threshold);
         g_signal_connect (priv->locator,
                           "notify::location",
                           G_CALLBACK (on_locator_location_changed),
@@ -330,7 +329,6 @@ on_agent_props_changed (GDBusProxy *agent_proxy,
         while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) {
                 GClueAccuracyLevel max_accuracy;
                 const char *id;
-                gboolean system_app;
 
                 if (strcmp (key, "MaxAccuracyLevel") != 0)
                         continue;
@@ -338,8 +336,6 @@ on_agent_props_changed (GDBusProxy *agent_proxy,
                 gdbus_client = GCLUE_DBUS_CLIENT (client);
                 id = gclue_dbus_client_get_desktop_id (gdbus_client);
                 max_accuracy = g_variant_get_uint32 (value);
-                system_app = (gclue_client_info_get_xdg_id
-                              (client->priv->client_info) == NULL);
                 /* FIXME: We should be handling all values of max accuracy
                  *        level here, not just 0 and non-0.
                  */
@@ -354,16 +350,16 @@ on_agent_props_changed (GDBusProxy *agent_proxy,
                         start_client (client, accuracy);
                         g_debug ("Re-started '%s'.", id);
                 } else if (max_accuracy == 0 &&
-                           gclue_dbus_client_get_active (gdbus_client) &&
-                           !system_app) {
+                           gclue_dbus_client_get_active (gdbus_client)) {
                         stop_client (client);
                         client->priv->agent_stopped = TRUE;
                         g_debug ("Stopped '%s'.", id);
                 }
 
+                g_variant_unref (value);
+                g_variant_iter_free (iter);
                 break;
         }
-        g_variant_iter_free (iter);
 }
 
 struct _StartData
@@ -657,6 +653,7 @@ gclue_service_client_finalize (GObject *object)
         g_clear_object (&priv->locator);
         g_clear_object (&priv->location);
         g_clear_object (&priv->prev_location);
+        g_clear_object (&priv->signaled_location);
         g_clear_object (&priv->client_info);
 
         /* Chain up to the parent class */
@@ -837,8 +834,9 @@ gclue_service_client_handle_set_property (GDBusConnection *connection,
         } else if (ret && strcmp (property_name, "TimeThreshold") == 0) {
                 priv->time_threshold = gclue_dbus_client_get_time_threshold
                         (client);
-                gclue_locator_set_time_threshold (priv->locator,
-                                                  priv->time_threshold);
+                if (GCLUE_IS_LOCATOR (priv->locator))
+                        gclue_locator_set_time_threshold (priv->locator,
+                                                          priv->time_threshold);
                 g_debug ("%s: New time-threshold:  %u",
                          G_OBJECT_TYPE_NAME (client),
                          priv->time_threshold);
@@ -947,9 +945,7 @@ gclue_service_client_initable_iface_init (GInitableIface *iface)
 static void
 gclue_service_client_init (GClueServiceClient *client)
 {
-        client->priv = G_TYPE_INSTANCE_GET_PRIVATE (client,
-                                                    GCLUE_TYPE_SERVICE_CLIENT,
-                                                    GClueServiceClientPrivate);
+        client->priv = gclue_service_client_get_instance_private (client);
         gclue_dbus_client_set_requested_accuracy_level
                 (GCLUE_DBUS_CLIENT (client), DEFAULT_ACCURACY_LEVEL);
 }
diff --git a/src/gclue-service-location.c b/src/gclue-service-location.c
index c2c1991d43527e058198444b6bcd64153be39b53..214406ec55015867a5cab3b0b09caf22b62aa5f6 100644
--- a/src/gclue-service-location.c
+++ b/src/gclue-service-location.c
@@ -348,9 +348,7 @@ gclue_service_location_class_init (GClueServiceLocationClass *klass)
 static void
 gclue_service_location_init (GClueServiceLocation *location)
 {
-        location->priv = G_TYPE_INSTANCE_GET_PRIVATE (location,
-                                                      GCLUE_TYPE_SERVICE_LOCATION,
-                                                      GClueServiceLocationPrivate);
+        location->priv = gclue_service_location_get_instance_private (location);
         gclue_dbus_location_set_altitude (GCLUE_DBUS_LOCATION (location),
                                           GCLUE_LOCATION_ALTITUDE_UNKNOWN);
 }
diff --git a/src/gclue-service-manager.c b/src/gclue-service-manager.c
index d7f5e55c4901249cdd423c06fadb4018c6411a3b..ba06c355145ad3530a86dbe2eea7d9befe1f7b7c 100644
--- a/src/gclue-service-manager.c
+++ b/src/gclue-service-manager.c
@@ -31,11 +31,6 @@
 #include "gclue-locator.h"
 #include "gclue-config.h"
 
-/* 20 seconds as milliseconds */
-#define AGENT_WAIT_TIMEOUT 20000
-/*  20 seconds as microseconds */
-#define AGENT_WAIT_TIMEOUT_USEC (20 * G_USEC_PER_SEC)
-
 static void
 gclue_service_manager_manager_iface_init (GClueDBusManagerIface *iface);
 static void
@@ -46,10 +41,10 @@ struct _GClueServiceManagerPrivate
         GDBusConnection *connection;
         GList *clients;
         GHashTable *agents;
+        GQueue *clients_waiting_agent;
 
         guint last_client_id;
         guint num_clients;
-        gint64 init_time;
 
         GClueLocator *locator;
 };
@@ -73,6 +68,21 @@ enum
 
 static GParamSpec *gParamSpecs[LAST_PROP];
 
+typedef struct
+{
+        GClueDBusManager *manager;
+        GDBusMethodInvocation *invocation;
+        GClueClientInfo *client_info;
+        gboolean reuse_client;
+
+} OnClientInfoNewReadyData;
+
+static void
+on_client_info_new_ready_data_free (gpointer data)
+{
+        g_slice_free (OnClientInfoNewReadyData, data);
+}
+
 static void
 sync_in_use_property (GClueServiceManager *manager)
 {
@@ -127,7 +137,7 @@ delete_client (GClueServiceManager *manager,
 
                 if (compare_func (l->data, compare_func_data) == 0) {
                         g_object_unref (G_OBJECT (l->data));
-                        priv->clients = g_list_remove_link (priv->clients, l);
+                        priv->clients = g_list_delete_link (priv->clients, l);
                         priv->num_clients--;
                         if (priv->num_clients == 0) {
                                 g_object_notify (G_OBJECT (manager), "active");
@@ -140,6 +150,14 @@ delete_client (GClueServiceManager *manager,
         sync_in_use_property (manager);
 }
 
+static void
+on_client_notify_active (GObject    *gobject,
+                         GParamSpec *pspec,
+                         gpointer    user_data)
+{
+        sync_in_use_property (GCLUE_SERVICE_MANAGER (user_data));
+}
+
 static void
 on_peer_vanished (GClueClientInfo *info,
                   gpointer         user_data)
@@ -155,15 +173,6 @@ on_peer_vanished (GClueClientInfo *info,
                        (char *) bus_name);
 }
 
-typedef struct
-{
-        GClueDBusManager *manager;
-        GDBusMethodInvocation *invocation;
-        GClueClientInfo *client_info;
-        gboolean reuse_client;
-
-} OnClientInfoNewReadyData;
-
 static gboolean
 complete_get_client (OnClientInfoNewReadyData *data)
 {
@@ -175,6 +184,9 @@ complete_get_client (OnClientInfoNewReadyData *data)
         char *path;
         guint32 user_id;
 
+        /* Disconnect on_peer_vanished_before_completion, if it's there */
+        g_signal_handlers_disconnect_by_data (info, data);
+
         user_id = gclue_client_info_get_user_id (info);
         agent_proxy = g_hash_table_lookup (priv->agents,
                                            GINT_TO_POINTER (user_id));
@@ -216,6 +228,11 @@ complete_get_client (OnClientInfoNewReadyData *data)
         }
         g_debug ("Number of connected clients: %u", priv->num_clients);
 
+        g_signal_connect (client,
+                          "notify::active",
+                          G_CALLBACK (on_client_notify_active),
+                          data->manager);
+
         g_signal_connect (info,
                           "peer-vanished",
                           G_CALLBACK (on_peer_vanished),
@@ -240,12 +257,36 @@ error_out:
 out:
         g_clear_error (&error);
         g_clear_object (&info);
-        g_slice_free (OnClientInfoNewReadyData, data);
+        on_client_info_new_ready_data_free (data);
         g_free (path);
 
         return FALSE;
 }
 
+static void
+on_peer_vanished_before_completion (GClueClientInfo *info,
+                                    gpointer         user_data)
+{
+        OnClientInfoNewReadyData *data = (OnClientInfoNewReadyData *) user_data;
+        GClueServiceManager *self = GCLUE_SERVICE_MANAGER (data->manager);
+        GClueServiceManagerPrivate *priv = self->priv;
+        const char *bus_name;
+
+        bus_name = gclue_client_info_get_bus_name (info);
+        g_debug ("Client `%s` vanished before agent appeared",
+                 bus_name);
+        g_signal_handlers_disconnect_by_data (info,
+                                              user_data);
+        g_dbus_method_invocation_return_error (data->invocation,
+                                               G_DBUS_ERROR,
+                                               G_DBUS_ERROR_DISCONNECTED,
+                                               "%s vanished before completion",
+                                               bus_name);
+        g_queue_remove (priv->clients_waiting_agent, data);
+        on_client_info_new_ready_data_free (data);
+        g_object_unref (info);
+}
+
 static void
 on_client_info_new_ready (GObject      *source_object,
                           GAsyncResult *res,
@@ -257,8 +298,6 @@ on_client_info_new_ready (GObject      *source_object,
         GClueAgent *agent_proxy;
         GError *error = NULL;
         guint32 user_id;
-        gint64 now;
-        gboolean system_app;
 
         info = gclue_client_info_new_finish (res, &error);
         if (info == NULL) {
@@ -267,7 +306,7 @@ on_client_info_new_ready (GObject      *source_object,
                                                                G_DBUS_ERROR_FAILED,
                                                                error->message);
                 g_error_free (error);
-                g_slice_free (OnClientInfoNewReadyData, data);
+                on_client_info_new_ready_data_free (data);
 
                 return;
         }
@@ -277,19 +316,20 @@ on_client_info_new_ready (GObject      *source_object,
         user_id = gclue_client_info_get_user_id (info);
         agent_proxy = g_hash_table_lookup (priv->agents,
                                            GINT_TO_POINTER (user_id));
-        now = g_get_monotonic_time ();
-
-        system_app = (gclue_client_info_get_xdg_id (info) == NULL);
-        if (agent_proxy == NULL &&
-            !system_app &&
-            now < (priv->init_time + AGENT_WAIT_TIMEOUT_USEC)) {
+        if (agent_proxy == NULL) {
                 /* Its possible that geoclue was just launched on GetClient
                  * call, in which case agents need some time to register
                  * themselves to us.
                  */
-                g_timeout_add (AGENT_WAIT_TIMEOUT,
-                               (GSourceFunc) complete_get_client,
-                               user_data);
+                g_queue_push_tail (priv->clients_waiting_agent, data);
+                g_signal_connect
+                        (info,
+                         "peer-vanished",
+                         G_CALLBACK (on_peer_vanished_before_completion),
+                         user_data);
+                g_debug ("Client `%s` waiting for agent for user ID '%u'",
+                         gclue_client_info_get_bus_name (info), user_id);
+
                 return;
         }
 
@@ -420,6 +460,7 @@ on_agent_proxy_ready (GObject      *source_object,
         guint32 user_id;
         GClueAgent *agent;
         GError *error = NULL;
+        GList *l;
 
         agent = gclue_agent_proxy_new_for_bus_finish (res, &error);
         if (agent == NULL)
@@ -436,6 +477,19 @@ on_agent_proxy_ready (GObject      *source_object,
 
         gclue_dbus_manager_complete_add_agent (data->manager, data->invocation);
 
+        l = priv->clients_waiting_agent->head;
+        while (l != NULL) {
+                GList *next = l->next;
+                OnClientInfoNewReadyData *d =
+                        (OnClientInfoNewReadyData *) l->data;
+
+                if (gclue_client_info_get_user_id(d->client_info) == user_id) {
+                        complete_get_client (d);
+                        g_queue_delete_link (priv->clients_waiting_agent, l);
+                }
+                l = next;
+        }
+
         goto out;
 
 error_out:
@@ -533,6 +587,11 @@ gclue_service_manager_finalize (GObject *object)
                 priv->clients = NULL;
         }
         g_clear_pointer (&priv->agents, g_hash_table_unref);
+        if (priv->clients_waiting_agent != NULL) {
+                g_queue_free_full (priv->clients_waiting_agent,
+                                   on_client_info_new_ready_data_free);
+                priv->clients_waiting_agent = NULL;
+        }
 
         /* Chain up to the parent class */
         G_OBJECT_CLASS (gclue_service_manager_parent_class)->finalize (object);
@@ -649,15 +708,13 @@ gclue_service_manager_class_init (GClueServiceManagerClass *klass)
 static void
 gclue_service_manager_init (GClueServiceManager *manager)
 {
-        manager->priv = G_TYPE_INSTANCE_GET_PRIVATE (manager,
-                                                     GCLUE_TYPE_SERVICE_MANAGER,
-                                                     GClueServiceManagerPrivate);
+        manager->priv = gclue_service_manager_get_instance_private (manager);
 
         manager->priv->agents = g_hash_table_new_full (g_direct_hash,
                                                        g_direct_equal,
                                                        NULL,
                                                        g_object_unref);
-        manager->priv->init_time = g_get_monotonic_time ();
+        manager->priv->clients_waiting_agent = g_queue_new ();
 }
 
 static gboolean
diff --git a/src/gclue-web-source.c b/src/gclue-web-source.c
index 98e95e8f358dd68a89044169fe4bcbbb701fafa4..bfd70f797229f1748e41b6493d30bc8edd9bd22f 100644
--- a/src/gclue-web-source.c
+++ b/src/gclue-web-source.c
@@ -37,7 +37,9 @@
  **/
 
 static gboolean
-gclue_web_source_start (GClueLocationSource *source);
+get_internet_available (void);
+static void
+refresh_accuracy_level (GClueWebSource *web);
 
 struct _GClueWebSourcePrivate {
         SoupSession *soup_session;
@@ -57,50 +59,128 @@ G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GClueWebSource,
                                   GCLUE_TYPE_LOCATION_SOURCE,
                                   G_ADD_PRIVATE (GClueWebSource))
 
+static void refresh_callback (SoupSession *session,
+                              SoupMessage *query,
+                              gpointer     user_data);
+
 static void
-query_callback (SoupSession *session,
-                SoupMessage *query,
-                gpointer     user_data)
+gclue_web_source_real_refresh_async (GClueWebSource      *source,
+                                     GCancellable        *cancellable,
+                                     GAsyncReadyCallback  callback,
+                                     gpointer             user_data)
 {
+        g_autoptr(GTask) task = NULL;
+        g_autoptr(GError) local_error = NULL;
+
+        task = g_task_new (source, cancellable, callback, user_data);
+        g_task_set_source_tag (task, gclue_web_source_real_refresh_async);
+
+        refresh_accuracy_level (source);
+
+        if (!gclue_location_source_get_active (GCLUE_LOCATION_SOURCE (source))) {
+                g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED,
+                                         "Source is inactive");
+                return;
+        }
+
+        if (!get_internet_available ()) {
+                g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NETWORK_UNREACHABLE,
+                                         "Network unavailable");
+                return;
+        }
+        g_debug ("Network available");
+
+        if (source->priv->query != NULL) {
+                g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_PENDING,
+                                         "Refresh already in progress");
+                return;
+        }
+
+        source->priv->query = GCLUE_WEB_SOURCE_GET_CLASS (source)->create_query (source, &local_error);
+
+        if (source->priv->query == NULL) {
+                g_task_return_error (task, g_steal_pointer (&local_error));
+                return;
+        }
+
+        /* TODO handle cancellation */
+        soup_session_queue_message (source->priv->soup_session,
+                                    source->priv->query,
+                                    refresh_callback,
+                                    g_steal_pointer (&task));
+}
+
+static void
+refresh_callback (SoupSession *session,
+                  SoupMessage *query,
+                  gpointer     user_data)
+{
+        g_autoptr(GTask) task = g_steal_pointer (&user_data);
         GClueWebSource *web;
-        GError *error = NULL;
-        char *contents;
-        char *str;
-        GClueLocation *location;
+        g_autoptr(GError) local_error = NULL;
+        g_autofree char *contents = NULL;
+        g_autofree char *str = NULL;
+        g_autoptr(GClueLocation) location = NULL;
         SoupURI *uri;
 
-        if (query->status_code == SOUP_STATUS_CANCELLED)
+        if (query->status_code == SOUP_STATUS_CANCELLED) {
+                g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+                                         "Operation cancelled");
                 return;
+        }
 
-        web = GCLUE_WEB_SOURCE (user_data);
+        web = GCLUE_WEB_SOURCE (g_task_get_source_object (task));
         web->priv->query = NULL;
 
         if (query->status_code != SOUP_STATUS_OK) {
-                g_warning ("Failed to query location: %s", query->reason_phrase);
-		return;
-	}
+                g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
+                                         "Failed to query location: %s", query->reason_phrase);
+                return;
+        }
 
         contents = g_strndup (query->response_body->data, query->response_body->length);
         uri = soup_message_get_uri (query);
         str = soup_uri_to_string (uri, FALSE);
-        g_debug ("Got following response from '%s':\n%s",
-                 str,
-                 contents);
-        g_free (str);
-        location = GCLUE_WEB_SOURCE_GET_CLASS (web)->parse_response (web,
-                                                                     contents,
-                                                                     &error);
-        g_free (contents);
-        if (error != NULL) {
-                g_warning ("Failed to parse following response: %s\n%s",
-                           error->message,
-                           contents);
+        g_debug ("Got following response from '%s':\n%s", str, contents);
+        location = GCLUE_WEB_SOURCE_GET_CLASS (web)->parse_response (web, contents, &local_error);
+        if (local_error != NULL) {
+                g_task_return_error (task, g_steal_pointer (&local_error));
                 return;
         }
 
         gclue_location_source_set_location (GCLUE_LOCATION_SOURCE (web),
                                             location);
-        g_object_unref (location);
+
+        g_task_return_pointer (task, g_steal_pointer (&location), g_object_unref);
+}
+
+
+static GClueLocation *
+gclue_web_source_real_refresh_finish (GClueWebSource  *source,
+                                      GAsyncResult    *result,
+                                      GError         **error)
+{
+        GTask *task = G_TASK (result);
+
+        return g_task_propagate_pointer (task, error);
+}
+
+static void
+query_callback (GObject      *source_object,
+                GAsyncResult *result,
+                gpointer      user_data)
+{
+        GClueWebSource *web = GCLUE_WEB_SOURCE (source_object);
+        g_autoptr(GError) local_error = NULL;
+        g_autoptr(GClueLocation) location = NULL;
+
+        location = GCLUE_WEB_SOURCE_GET_CLASS (web)->refresh_finish (web, result, &local_error);
+
+        if (local_error != NULL &&
+            !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED)) {
+                g_warning ("Failed to query location: %s", local_error->message);
+                return;
+        }
 }
 
 static gboolean
@@ -139,41 +219,13 @@ on_network_changed (GNetworkMonitor *monitor G_GNUC_UNUSED,
                     gpointer         user_data)
 {
         GClueWebSource *web = GCLUE_WEB_SOURCE (user_data);
-        GError *error = NULL;
         gboolean last_available = web->priv->internet_available;
 
         web->priv->internet_available = get_internet_available ();
         if (last_available == web->priv->internet_available)
                 return; /* We already reacted to network change */
 
-        refresh_accuracy_level (web);
-
-        if (!gclue_location_source_get_active (GCLUE_LOCATION_SOURCE (user_data)))
-                return;
-
-        if (!web->priv->internet_available) {
-                g_debug ("Network unavailable");
-                return;
-        }
-        g_debug ("Network available");
-
-        if (web->priv->query != NULL)
-                return;
-
-        web->priv->query = GCLUE_WEB_SOURCE_GET_CLASS (web)->create_query
-                                        (web,
-                                         &error);
-
-        if (web->priv->query == NULL) {
-                g_warning ("Failed to create query: %s", error->message);
-                g_error_free (error);
-                return;
-        }
-
-        soup_session_queue_message (web->priv->soup_session,
-                                    web->priv->query,
-                                    query_callback,
-                                    web);
+        GCLUE_WEB_SOURCE_GET_CLASS (web)->refresh_async (web, NULL, query_callback, NULL);
 }
 
 static void
@@ -246,10 +298,10 @@ gclue_web_source_constructed (GObject *object)
 static void
 gclue_web_source_class_init (GClueWebSourceClass *klass)
 {
-        GClueLocationSourceClass *source_class = GCLUE_LOCATION_SOURCE_CLASS (klass);
         GObjectClass *gsource_class = G_OBJECT_CLASS (klass);
 
-        source_class->start = gclue_web_source_start;
+        klass->refresh_async = gclue_web_source_real_refresh_async;
+        klass->refresh_finish = gclue_web_source_real_refresh_finish;
 
         gsource_class->finalize = gclue_web_source_finalize;
         gsource_class->constructed = gclue_web_source_constructed;
@@ -258,7 +310,7 @@ gclue_web_source_class_init (GClueWebSourceClass *klass)
 static void
 gclue_web_source_init (GClueWebSource *web)
 {
-        web->priv = G_TYPE_INSTANCE_GET_PRIVATE ((web), GCLUE_TYPE_WEB_SOURCE, GClueWebSourcePrivate);
+        web->priv = gclue_web_source_get_instance_private (web);
 }
 
 /**
@@ -274,22 +326,7 @@ gclue_web_source_refresh (GClueWebSource *source)
 {
         g_return_if_fail (GCLUE_IS_WEB_SOURCE (source));
 
-        /* Make sure ->internet_available is different from
-         * the real availability of internet access */
-        source->priv->internet_available = FALSE;
-        on_network_changed (NULL, TRUE, source);
-}
-
-static gboolean
-gclue_web_source_start (GClueLocationSource *source)
-{
-        GClueLocationSourceClass *base_class;
-
-        base_class = GCLUE_LOCATION_SOURCE_CLASS (gclue_web_source_parent_class);
-        if (!base_class->start (source))
-                return FALSE;
-
-        return TRUE;
+        GCLUE_WEB_SOURCE_GET_CLASS (source)->refresh_async (source, NULL, query_callback, NULL);
 }
 
 static void
@@ -298,18 +335,20 @@ submit_query_callback (SoupSession *session,
                        gpointer     user_data)
 {
         SoupURI *uri;
+        g_autofree char *str = NULL;
 
         uri = soup_message_get_uri (query);
+        str = soup_uri_to_string (uri, FALSE);
         if (query->status_code != SOUP_STATUS_OK &&
             query->status_code != SOUP_STATUS_NO_CONTENT) {
                 g_warning ("Failed to submit location data to '%s': %s",
-                           soup_uri_to_string (uri, FALSE),
+                           str,
                            query->reason_phrase);
 		return;
 	}
 
         g_debug ("Successfully submitted location data to '%s'",
-                 soup_uri_to_string (uri, FALSE));
+                 str);
 }
 
 #define SUBMISSION_ACCURACY_THRESHOLD 100
diff --git a/src/gclue-web-source.h b/src/gclue-web-source.h
index 459f475f946fff13eca45cf1a8fefb3792dc1a90..ea8ea73a40824f4a9679a3247ca1ebe71e5132b0 100644
--- a/src/gclue-web-source.h
+++ b/src/gclue-web-source.h
@@ -62,6 +62,14 @@ struct _GClueWebSourceClass {
         /* <private> */
         GClueLocationSourceClass parent_class;
 
+        void              (*refresh_async)       (GClueWebSource       *source,
+                                                  GCancellable         *cancellable,
+                                                  GAsyncReadyCallback   callback,
+                                                  gpointer              user_data);
+        GClueLocation    *(*refresh_finish)      (GClueWebSource       *source,
+                                                  GAsyncResult         *result,
+                                                  GError              **error);
+
         SoupMessage *     (*create_query)        (GClueWebSource *source,
                                                   GError        **error);
         SoupMessage *     (*create_submit_query) (GClueWebSource  *source,
diff --git a/src/gclue-wifi.c b/src/gclue-wifi.c
index b06737d1eed6677b4f2aecb4da429d6b24977184..210819592ee20748997c09adb7f28de3cd61cf5b 100644
--- a/src/gclue-wifi.c
+++ b/src/gclue-wifi.c
@@ -34,10 +34,21 @@
  */
 #define WIFI_SCAN_TIMEOUT_LOW_ACCURACY  300
 
-#define BSSID_LEN 7
-#define BSSID_STR_LEN 18
+/* WiFi APs at and below this signal level in scan results are ignored.
+ * In dBm units.
+ */
+#define WIFI_SCAN_BSS_NOISE_LEVEL -90
+
+#define BSSID_LEN 6
+#define BSSID_STR_LEN 17
 #define MAX_SSID_LEN 32
 
+/* Drop entries from the cache when they are more than 48 hours old. If we are
+ * polling at high accuracy for that entire period, that gives a maximum cache
+ * size of 17280 entries. At roughly 400B each, that’s about 7MB of heap for a
+ * full cache (excluding overheads). */
+#define CACHE_ENTRY_MAX_AGE_SECONDS (48 * 60 * 60)
+
 /**
  * SECTION:gclue-wifi
  * @short_description: WiFi-based geolocation
@@ -46,11 +57,27 @@
  * Contains functions to get the geolocation based on nearby WiFi networks.
  **/
 
-static gboolean
+static GClueLocationSourceStartResult
 gclue_wifi_start (GClueLocationSource *source);
-static gboolean
+static GClueLocationSourceStopResult
 gclue_wifi_stop (GClueLocationSource *source);
 
+static guint
+variant_hash (gconstpointer key);
+
+static void
+gclue_wifi_refresh_async (GClueWebSource      *source,
+                          GCancellable        *cancellable,
+                          GAsyncReadyCallback  callback,
+                          gpointer             user_data);
+static GClueLocation *
+gclue_wifi_refresh_finish (GClueWebSource  *source,
+                           GAsyncResult    *result,
+                           GError         **error);
+
+static void
+disconnect_cache_prune_timeout (GClueWifi *wifi);
+
 struct _GClueWifiPrivate {
         WPASupplicant *supplicant;
         WPAInterface *interface;
@@ -61,10 +88,20 @@ struct _GClueWifiPrivate {
         gulong bss_added_id;
         gulong bss_removed_id;
         gulong scan_done_id;
+        guint scan_wait_id;
 
         guint scan_timeout;
 
         GClueAccuracyLevel accuracy_level;
+
+        GHashTable *location_cache;  /* (element-type GVariant GClueLocation) (owned) */
+        guint cache_prune_timeout_id;
+        guint cache_hits, cache_misses;
+
+#if GLIB_CHECK_VERSION(2, 64, 0)
+        GMemoryMonitor *memory_monitor;
+        gulong low_memory_warning_id;
+#endif
 };
 
 enum
@@ -114,10 +151,13 @@ gclue_wifi_finalize (GObject *gwifi)
         G_OBJECT_CLASS (gclue_wifi_parent_class)->finalize (gwifi);
 
         disconnect_bss_signals (wifi);
+        disconnect_cache_prune_timeout (wifi);
+
         g_clear_object (&wifi->priv->supplicant);
         g_clear_object (&wifi->priv->interface);
         g_clear_pointer (&wifi->priv->bss_proxies, g_hash_table_unref);
         g_clear_pointer (&wifi->priv->ignored_bss_proxies, g_hash_table_unref);
+        g_clear_pointer (&wifi->priv->location_cache, g_hash_table_unref);
 }
 
 static void
@@ -168,6 +208,8 @@ gclue_wifi_class_init (GClueWifiClass *klass)
 
         source_class->start = gclue_wifi_start;
         source_class->stop = gclue_wifi_stop;
+        web_class->refresh_async = gclue_wifi_refresh_async;
+        web_class->refresh_finish = gclue_wifi_refresh_finish;
         web_class->create_submit_query = gclue_wifi_create_submit_query;
         web_class->create_query = gclue_wifi_create_query;
         web_class->parse_response = gclue_wifi_parse_response;
@@ -205,7 +247,7 @@ variant_to_string (GVariant *variant, guint max_len, char *ret)
         len = g_variant_n_children (variant);
         if (len == 0)
                 return 0;
-        g_return_val_if_fail(len < max_len, 0);
+        g_return_val_if_fail(len <= max_len, 0);
         ret[len] = '\0';
 
         for (i = 0; i < len; i++)
@@ -229,7 +271,7 @@ static gboolean
 get_bssid_from_bss (WPABSS *bss, char *bssid)
 {
         GVariant *variant;
-        char raw_bssid[BSSID_LEN] = { 0 };
+        char raw_bssid[BSSID_LEN + 1] = { 0 };
         guint raw_len, i;
 
         variant = wpa_bss_get_bssid (bss);
@@ -237,12 +279,12 @@ get_bssid_from_bss (WPABSS *bss, char *bssid)
                 return FALSE;
 
         raw_len = variant_to_string (variant, BSSID_LEN, raw_bssid);
-        g_return_val_if_fail (raw_len == BSSID_LEN - 1, FALSE);
+        g_return_val_if_fail (raw_len == BSSID_LEN, FALSE);
 
-        for (i = 0; i < BSSID_LEN - 1; i++) {
+        for (i = 0; i < BSSID_LEN; i++) {
                 unsigned char c = (unsigned char) raw_bssid[i];
 
-                if (i == BSSID_LEN - 2) {
+                if (i == BSSID_LEN - 1) {
                         g_snprintf (bssid + (i * 3), 3, "%02x", c);
                 } else {
                         g_snprintf (bssid + (i * 3), 4, "%02x:", c);
@@ -262,7 +304,7 @@ add_bss_proxy (GClueWifi *wifi,
         if (g_hash_table_replace (wifi->priv->bss_proxies,
                                   g_strdup (path),
                                   bss)) {
-                char ssid[MAX_SSID_LEN] = { 0 };
+                char ssid[MAX_SSID_LEN + 1] = { 0 };
 
                 wifi->priv->bss_list_changed = TRUE;
                 get_ssid_from_bss (bss, ssid);
@@ -279,11 +321,11 @@ on_bss_signal_notify (GObject    *gobject,
         WPABSS *bss = WPA_BSS (gobject);
         const char *path;
 
-        if (wpa_bss_get_signal (bss) <= -90) {
-                char bssid[BSSID_STR_LEN] = { 0 };
+        if (wpa_bss_get_signal (bss) <= WIFI_SCAN_BSS_NOISE_LEVEL) {
+                char bssid[BSSID_STR_LEN + 1] = { 0 };
 
                 get_bssid_from_bss (bss, bssid);
-                g_debug ("WiFi AP '%s' still has very low strength (%u dBm)"
+                g_debug ("WiFi AP '%s' still has very low strength (%d dBm)"
                          ", ignoring again…",
                          bssid,
                          wpa_bss_get_signal (bss));
@@ -306,7 +348,7 @@ on_bss_proxy_ready (GObject      *source_object,
         GClueWifi *wifi = GCLUE_WIFI (user_data);
         WPABSS *bss;
         GError *error = NULL;
-        char ssid[MAX_SSID_LEN] = { 0 };
+        char ssid[MAX_SSID_LEN + 1] = { 0 };
 
         bss = wpa_bss_proxy_new_for_bus_finish (res, &error);
         if (bss == NULL) {
@@ -325,12 +367,12 @@ on_bss_proxy_ready (GObject      *source_object,
         get_ssid_from_bss (bss, ssid);
         g_debug ("WiFi AP '%s' added.", ssid);
 
-        if (wpa_bss_get_signal (bss) <= -90) {
+        if (wpa_bss_get_signal (bss) <= WIFI_SCAN_BSS_NOISE_LEVEL) {
                 const char *path;
-                char bssid[BSSID_STR_LEN] = { 0 };
+                char bssid[BSSID_STR_LEN + 1] = { 0 };
 
                 get_bssid_from_bss (bss, bssid);
-                g_debug ("WiFi AP '%s' has very low strength (%u dBm)"
+                g_debug ("WiFi AP '%s' has very low strength (%d dBm)"
                          ", ignoring for now…",
                          bssid,
                          wpa_bss_get_signal (bss));
@@ -366,7 +408,7 @@ on_bss_added (WPAInterface *object,
 static gboolean
 remove_bss_from_hashtable (const gchar *path, GHashTable *hash_table)
 {
-        char ssid[MAX_SSID_LEN] = { 0 };
+        char ssid[MAX_SSID_LEN + 1] = { 0 };
         WPABSS *bss = NULL;
 
         bss = g_hash_table_lookup (hash_table, path);
@@ -432,6 +474,11 @@ cancel_wifi_scan (GClueWifi *wifi)
                 priv->scan_timeout = 0;
         }
 
+        if (priv->scan_wait_id != 0) {
+                g_source_remove (priv->scan_wait_id);
+                priv->scan_wait_id = 0;
+        }
+
         if (priv->scan_done_id != 0) {
                 g_signal_handler_disconnect (priv->interface,
                                              priv->scan_done_id);
@@ -453,7 +500,25 @@ on_scan_timeout (gpointer user_data)
 
         start_wifi_scan (wifi);
 
-        return FALSE;
+        return G_SOURCE_REMOVE;
+}
+
+static gboolean
+on_scan_wait_done (gpointer wifi)
+{
+        GClueWifiPrivate *priv;
+
+        g_return_val_if_fail (GCLUE_IS_WIFI (wifi), G_SOURCE_REMOVE);
+        priv = GCLUE_WIFI(wifi)->priv;
+
+        if (priv->bss_list_changed) {
+                priv->bss_list_changed = FALSE;
+                g_debug ("Refreshing location…");
+                gclue_web_source_refresh (GCLUE_WEB_SOURCE (wifi));
+        }
+        priv->scan_wait_id = 0;
+
+        return G_SOURCE_REMOVE;
 }
 
 static void
@@ -475,11 +540,10 @@ on_scan_done (WPAInterface *object,
         if (priv->interface == NULL)
                 return;
 
-        if (priv->bss_list_changed) {
-                priv->bss_list_changed = FALSE;
-                g_debug ("Refreshing location…");
-                gclue_web_source_refresh (GCLUE_WEB_SOURCE (wifi));
-        }
+        if (priv->scan_wait_id != 0)
+            g_source_remove (priv->scan_wait_id);
+
+        priv->scan_wait_id = g_timeout_add_seconds (1, on_scan_wait_done, wifi);
 
         /* If there was another scan already scheduled, cancel that and
          * re-schedule. Regardless of our internal book-keeping, this can happen
@@ -586,34 +650,152 @@ disconnect_bss_signals (GClueWifi *wifi)
         g_hash_table_remove_all (priv->ignored_bss_proxies);
 }
 
+static void
+cache_prune (GClueWifi *wifi)
+{
+        GClueWifiPrivate *priv = wifi->priv;
+        GHashTableIter iter;
+        gpointer value;
+        guint64 cutoff_seconds;
+        guint old_cache_size;
+
+        old_cache_size = g_hash_table_size (priv->location_cache);
+        cutoff_seconds = g_get_real_time () / G_USEC_PER_SEC - CACHE_ENTRY_MAX_AGE_SECONDS;
+
+        g_hash_table_iter_init (&iter, priv->location_cache);
+        while (g_hash_table_iter_next (&iter, NULL, &value)) {
+                GClueLocation *location = GCLUE_LOCATION (value);
+                guint64 timestamp_seconds = gclue_location_get_timestamp (location);
+
+                if (timestamp_seconds <= cutoff_seconds)
+                        g_hash_table_iter_remove (&iter);
+        }
+
+        g_debug ("Pruned cache (old size: %u, new size: %u)",
+                 old_cache_size, g_hash_table_size (priv->location_cache));
+}
+
+#if GLIB_CHECK_VERSION(2, 64, 0)
+static void
+cache_empty (GClueWifi *wifi)
+{
+        GClueWifiPrivate *priv = wifi->priv;
+
+        g_debug ("Emptying cache");
+        g_hash_table_remove_all (priv->location_cache);
+}
+#endif  /* GLib ≥ 2.64.0 */
+
 static gboolean
+cache_prune_timeout_cb (gpointer user_data)
+{
+        GClueWifi *wifi = GCLUE_WIFI (user_data);
+
+        cache_prune (wifi);
+
+        return G_SOURCE_CONTINUE;
+}
+
+#if GLIB_CHECK_VERSION(2, 64, 0)
+static void
+low_memory_warning_cb (GMemoryMonitor             *memory_monitor,
+                       GMemoryMonitorWarningLevel  level,
+                       gpointer                    user_data)
+{
+        GClueWifi *wifi = GCLUE_WIFI (user_data);
+
+        if (level == G_MEMORY_MONITOR_WARNING_LEVEL_LOW)
+                cache_prune (wifi);
+        else if (level > G_MEMORY_MONITOR_WARNING_LEVEL_LOW)
+                cache_empty (wifi);
+}
+#endif  /* GLib ≥ 2.64.0 */
+
+static void
+connect_cache_prune_timeout (GClueWifi *wifi)
+{
+        GClueWifiPrivate *priv = wifi->priv;
+
+        g_debug ("Connecting cache prune timeout");
+
+        /* Run the prune at twice the expiry frequency, which should allow us to
+         * sample often enough to keep the expiries at that frequency, as per
+         * Nyquist. */
+        if (priv->cache_prune_timeout_id != 0)
+                g_source_remove (priv->cache_prune_timeout_id);
+        priv->cache_prune_timeout_id = g_timeout_add_seconds (CACHE_ENTRY_MAX_AGE_SECONDS / 2,
+                                                              cache_prune_timeout_cb,
+                                                              wifi);
+
+#if GLIB_CHECK_VERSION(2, 64, 0)
+        if (priv->memory_monitor == NULL) {
+                priv->memory_monitor = g_memory_monitor_dup_default ();
+                priv->low_memory_warning_id = g_signal_connect (priv->memory_monitor,
+                                                                "low-memory-warning",
+                                                                G_CALLBACK (low_memory_warning_cb),
+                                                                wifi);
+        }
+#endif
+}
+
+static void
+disconnect_cache_prune_timeout (GClueWifi *wifi)
+{
+        GClueWifiPrivate *priv = wifi->priv;
+
+        g_debug ("Disconnecting cache prune timeout");
+
+        /* Run one last prune. */
+        cache_prune (wifi);
+
+#if GLIB_CHECK_VERSION(2, 64, 0)
+        if (priv->low_memory_warning_id != 0 && priv->memory_monitor != NULL)
+                g_signal_handler_disconnect (priv->memory_monitor, priv->low_memory_warning_id);
+        g_clear_object (&priv->memory_monitor);
+#endif
+
+        if (priv->cache_prune_timeout_id != 0)
+                g_source_remove (priv->cache_prune_timeout_id);
+        priv->cache_prune_timeout_id = 0;
+}
+
+static GClueLocationSourceStartResult
 gclue_wifi_start (GClueLocationSource *source)
 {
         GClueLocationSourceClass *base_class;
+        GClueLocationSourceStartResult base_result;
 
-        g_return_val_if_fail (GCLUE_IS_WIFI (source), FALSE);
+        g_return_val_if_fail (GCLUE_IS_WIFI (source),
+                              GCLUE_LOCATION_SOURCE_START_RESULT_FAILED);
 
         base_class = GCLUE_LOCATION_SOURCE_CLASS (gclue_wifi_parent_class);
-        if (!base_class->start (source))
-                return FALSE;
+        base_result = base_class->start (source);
+        if (base_result != GCLUE_LOCATION_SOURCE_START_RESULT_OK)
+                return base_result;
 
+        connect_cache_prune_timeout (GCLUE_WIFI (source));
         connect_bss_signals (GCLUE_WIFI (source));
-        return TRUE;
+
+        return base_result;
 }
 
-static gboolean
+static GClueLocationSourceStopResult
 gclue_wifi_stop (GClueLocationSource *source)
 {
         GClueLocationSourceClass *base_class;
+        GClueLocationSourceStopResult base_result;
 
         g_return_val_if_fail (GCLUE_IS_WIFI (source), FALSE);
 
         base_class = GCLUE_LOCATION_SOURCE_CLASS (gclue_wifi_parent_class);
-        if (!base_class->stop (source))
-                return FALSE;
+        base_result = base_class->stop (source);
+        if (base_result == GCLUE_LOCATION_SOURCE_STOP_RESULT_STILL_USED)
+                return base_result;
 
         disconnect_bss_signals (GCLUE_WIFI (source));
-        return TRUE;
+        disconnect_cache_prune_timeout (GCLUE_WIFI (source));
+
+        return base_result;
 }
 
 static GClueAccuracyLevel
@@ -712,7 +894,7 @@ on_interface_removed (WPASupplicant *supplicant,
 static void
 gclue_wifi_init (GClueWifi *wifi)
 {
-        wifi->priv = G_TYPE_INSTANCE_GET_PRIVATE ((wifi), GCLUE_TYPE_WIFI, GClueWifiPrivate);
+        wifi->priv = gclue_wifi_get_instance_private (wifi);
 
         wifi->priv->bss_proxies = g_hash_table_new_full (g_str_hash,
                                                          g_str_equal,
@@ -722,6 +904,10 @@ gclue_wifi_init (GClueWifi *wifi)
                                                                  g_str_equal,
                                                                  g_free,
                                                                  g_object_unref);
+        wifi->priv->location_cache = g_hash_table_new_full (variant_hash,
+                                                            g_variant_equal,
+                                                            (GDestroyNotify) g_variant_unref,
+                                                            g_object_unref);
 }
 
 static void
@@ -799,6 +985,7 @@ gclue_wifi_get_singleton (GClueAccuracyLevel level)
         static GClueWifi *wifi[] = { NULL, NULL };
         guint i;
         gboolean scramble_location = FALSE;
+        gboolean compute_movement = FALSE;
 
         g_return_val_if_fail (level >= GCLUE_ACCURACY_LEVEL_CITY, NULL);
         if (level == GCLUE_ACCURACY_LEVEL_NEIGHBORHOOD)
@@ -812,12 +999,14 @@ gclue_wifi_get_singleton (GClueAccuracyLevel level)
                         scramble_location = TRUE;
         } else {
                 i = 1;
+                compute_movement = TRUE;
         }
 
         if (wifi[i] == NULL) {
                 wifi[i] = g_object_new (GCLUE_TYPE_WIFI,
                                         "accuracy-level", level,
                                         "scramble-location", scramble_location,
+                                        "compute-movement", compute_movement,
                                         NULL);
                 g_object_weak_ref (G_OBJECT (wifi[i]),
                                    on_wifi_destroyed,
@@ -837,19 +1026,10 @@ gclue_wifi_get_accuracy_level (GClueWifi *wifi)
         return wifi->priv->accuracy_level;
 }
 
-/* Can return NULL without setting @error, signifying an empty BSS list. */
+/* Can return NULL, signifying an empty BSS list. */
 static GList *
-get_bss_list (GClueWifi *wifi,
-              GError   **error)
+get_bss_list (GClueWifi *wifi)
 {
-        if (wifi->priv->interface == NULL) {
-                g_set_error_literal (error,
-                                     G_IO_ERROR,
-                                     G_IO_ERROR_FAILED,
-                                     "No WiFi devices available");
-                return NULL;
-        }
-
         return g_hash_table_get_values (wifi->priv->bss_proxies);
 }
 
@@ -857,16 +1037,16 @@ static SoupMessage *
 gclue_wifi_create_query (GClueWebSource *source,
                          GError        **error)
 {
-        GList *bss_list; /* As in Access Points */
+        GClueWifi *wifi = GCLUE_WIFI (source);
+        GList *bss_list = NULL; /* As in Access Points */
         SoupMessage *msg;
-        g_autoptr(GError) local_error = NULL;
 
-        bss_list = get_bss_list (GCLUE_WIFI (source), &local_error);
-        if (local_error != NULL) {
-                g_propagate_error (error, g_steal_pointer (&local_error));
-                return NULL;
+        if (wifi->priv->interface == NULL) {
+                goto create_query;
         }
 
+        bss_list = get_bss_list (wifi);
+
         /* Empty list? */
         if (bss_list == NULL) {
                 g_set_error_literal (error,
@@ -876,6 +1056,7 @@ gclue_wifi_create_query (GClueWebSource *source,
                 return NULL;
         }
 
+create_query:
         msg = gclue_mozilla_create_query (bss_list, NULL, error);
         g_list_free (bss_list);
         return msg;
@@ -894,16 +1075,20 @@ gclue_wifi_create_submit_query (GClueWebSource  *source,
                                 GClueLocation   *location,
                                 GError         **error)
 {
+        GClueWifi *wifi = GCLUE_WIFI (source);
         GList *bss_list; /* As in Access Points */
         SoupMessage * msg;
-        g_autoptr(GError) local_error = NULL;
 
-        bss_list = get_bss_list (GCLUE_WIFI (source), &local_error);
-        if (local_error != NULL) {
-                g_propagate_error (error, g_steal_pointer (&local_error));
+        if (wifi->priv->interface == NULL) {
+                g_set_error_literal (error,
+                                     G_IO_ERROR,
+                                     G_IO_ERROR_FAILED,
+                                     "No WiFi devices available");
                 return NULL;
         }
 
+        bss_list = get_bss_list (wifi);
+
         /* Empty list? */
         if (bss_list == NULL) {
                 g_set_error_literal (error,
@@ -920,3 +1105,198 @@ gclue_wifi_create_submit_query (GClueWebSource  *source,
         g_list_free (bss_list);
         return msg;
 }
+
+static void refresh_cb (GObject      *source_object,
+                        GAsyncResult *result,
+                        gpointer      user_data);
+
+static gint
+bss_compare (gconstpointer a,
+             gconstpointer b)
+{
+        WPABSS **_bss_a = (WPABSS **) a;
+        WPABSS **_bss_b = (WPABSS **) b;
+        WPABSS *bss_a = WPA_BSS (*_bss_a);
+        WPABSS *bss_b = WPA_BSS (*_bss_b);
+        GVariant *bssid_a = wpa_bss_get_bssid (bss_a);
+        GVariant *bssid_b = wpa_bss_get_bssid (bss_b);
+        g_autoptr(GBytes) bssid_bytes_a = NULL;
+        g_autoptr(GBytes) bssid_bytes_b = NULL;
+
+        /* Can’t use g_variant_compare() as it isn’t defined over `ay` types */
+        if (bssid_a == NULL && bssid_b == NULL)
+                return 0;
+        else if (bssid_a == NULL)
+                return -1;
+        else if (bssid_b == NULL)
+                return 1;
+
+        bssid_bytes_a = g_variant_get_data_as_bytes (bssid_a);
+        bssid_bytes_b = g_variant_get_data_as_bytes (bssid_b);
+
+        return g_bytes_compare (bssid_bytes_a, bssid_bytes_b);
+}
+
+static guint
+variant_hash (gconstpointer key)
+{
+        GVariant *variant = (GVariant *) key;
+        g_autoptr(GBytes) bytes = g_variant_get_data_as_bytes (variant);
+        return g_bytes_hash (bytes);
+}
+
+static GVariant *
+get_location_cache_key (GClueWifi *wifi)
+{
+        GHashTableIter iter;
+        gpointer value;
+        g_autoptr(GPtrArray) bss_array = g_ptr_array_new_with_free_func (NULL);  /* (element-type WPABSS) */
+        guint i;
+        GVariantBuilder builder;
+
+        /* The Mozilla service puts BSSID and signal strength for each BSS into
+         * its query. The signal strength can typically vary by ±5 for a
+         * stationary laptop, so quantise by that. Pack the whole lot into a
+         * #GVariant for simplicity, sorted by MAC address. The sorting has to
+         * happen in an array beforehand, as variants are immutable. */
+        g_hash_table_iter_init (&iter, wifi->priv->bss_proxies);
+
+        while (g_hash_table_iter_next (&iter, NULL, &value)) {
+                WPABSS *bss = WPA_BSS (value);
+                if (bss != NULL)
+                        g_ptr_array_add (bss_array, bss);
+        }
+
+        g_ptr_array_sort (bss_array, bss_compare);
+
+        /* Serialise to a variant. */
+        g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayn)"));
+        for (i = 0; i < bss_array->len; i++) {
+                WPABSS *bss = WPA_BSS (bss_array->pdata[i]);
+                GVariant *bssid;
+
+                g_variant_builder_open (&builder, G_VARIANT_TYPE ("(ayn)"));
+
+                bssid = wpa_bss_get_bssid (bss);
+                if (bssid == NULL)
+                        continue;
+
+                g_variant_builder_add_value (&builder, bssid);
+                g_variant_builder_add (&builder, "n", wpa_bss_get_signal (bss) / 10);
+
+                g_variant_builder_close (&builder);
+        }
+
+        return g_variant_builder_end (&builder);
+}
+
+static GClueLocation *
+duplicate_location_new_timestamp (GClueLocation *location)
+{
+        return g_object_new (GCLUE_TYPE_LOCATION,
+                             "latitude", gclue_location_get_latitude (location),
+                             "longitude", gclue_location_get_longitude (location),
+                             "accuracy", gclue_location_get_accuracy (location),
+                             "altitude", gclue_location_get_altitude (location),
+                             "timestamp", 0,
+                             "speed", gclue_location_get_speed (location),
+                             "heading", gclue_location_get_heading (location),
+                             NULL);
+}
+
+static void
+gclue_wifi_refresh_async (GClueWebSource      *source,
+                          GCancellable        *cancellable,
+                          GAsyncReadyCallback  callback,
+                          gpointer             user_data)
+{
+        GClueWifi *wifi = GCLUE_WIFI (source);
+        g_autoptr(GTask) task = g_task_new (source, cancellable, callback, user_data);
+        g_autoptr(GVariant) cache_key = get_location_cache_key (wifi);
+        g_autofree gchar *cache_key_str = g_variant_print (cache_key, FALSE);
+        GClueLocation *cached_location = g_hash_table_lookup (wifi->priv->location_cache, cache_key);
+
+        g_task_set_source_tag (task, gclue_wifi_refresh_async);
+        g_task_set_task_data (task, g_steal_pointer (&cache_key), (GDestroyNotify) g_variant_unref);
+
+        if (gclue_location_source_get_active (GCLUE_LOCATION_SOURCE (source))) {
+                /* Try the cache. */
+                if (cached_location != NULL) {
+                        g_autoptr(GClueLocation) new_location = NULL;
+
+                        g_debug ("Cache hit for key %s: got location %p (%s)",
+                                 cache_key_str, cached_location,
+                                 gclue_location_get_description (cached_location));
+                        wifi->priv->cache_hits++;
+
+                        /* Duplicate the location so its timestamp is updated. */
+                        new_location = duplicate_location_new_timestamp (cached_location);
+                        gclue_location_source_set_location (GCLUE_LOCATION_SOURCE (source), new_location);
+
+                        g_task_return_pointer (task, g_steal_pointer (&new_location), g_object_unref);
+                        return;
+                }
+
+                g_debug ("Cache miss for key %s; querying web service", cache_key_str);
+                wifi->priv->cache_misses++;
+        }
+
+        /* Fall back to querying the web service. */
+        GCLUE_WEB_SOURCE_CLASS (gclue_wifi_parent_class)->refresh_async (source, cancellable, refresh_cb, g_steal_pointer (&task));
+}
+
+static void
+refresh_cb (GObject      *source_object,
+            GAsyncResult *result,
+            gpointer      user_data)
+{
+        GClueWebSource *source = GCLUE_WEB_SOURCE (source_object);
+        GClueWifi *wifi = GCLUE_WIFI (source);
+        g_autoptr(GTask) task = g_steal_pointer (&user_data);
+        g_autoptr(GClueLocation) location = NULL;
+        g_autoptr(GError) local_error = NULL;
+        GVariant *cache_key;
+        g_autofree gchar *cache_key_str = NULL;
+        double cache_hit_ratio;
+
+        /* Finish querying the web service. */
+        location = GCLUE_WEB_SOURCE_CLASS (gclue_wifi_parent_class)->refresh_finish (source, result, &local_error);
+
+        if (local_error != NULL) {
+                g_task_return_error (task, g_steal_pointer (&local_error));
+                return;
+        }
+
+        /* Cache the result. */
+        cache_key = g_task_get_task_data (task);
+        cache_key_str = g_variant_print (cache_key, FALSE);
+        g_hash_table_replace (wifi->priv->location_cache, g_variant_ref (cache_key), g_object_ref (location));
+
+        if (wifi->priv->cache_hits || wifi->priv->cache_misses) {
+                double cache_attempts;
+
+                cache_attempts = wifi->priv->cache_hits;
+                cache_attempts += wifi->priv->cache_misses;
+                cache_hit_ratio = wifi->priv->cache_hits * 100.0 / cache_attempts;
+        } else {
+                cache_hit_ratio = 0;
+        }
+
+        g_debug ("Adding %s / %s to cache (new size: %u; hit ratio %.2f%%)",
+                 cache_key_str,
+                 gclue_location_get_description (location),
+                 g_hash_table_size (wifi->priv->location_cache),
+                 cache_hit_ratio);
+
+        g_task_return_pointer (task, g_steal_pointer (&location), g_object_unref);
+}
+
+static GClueLocation *
+gclue_wifi_refresh_finish (GClueWebSource  *source,
+                           GAsyncResult    *result,
+                           GError         **error)
+{
+        GTask *task = G_TASK (result);
+
+        return g_task_propagate_pointer (task, error);
+}
diff --git a/src/meson.build b/src/meson.build
index e82245f1c3a3cc4b10e348020e2f11bbbc9ec9d9..13eb1baef240240bca625c70eba78534dd6e945c 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -3,8 +3,7 @@ geoclue_deps = base_deps + [ dependency('json-glib-1.0', version: '>= 0.14.0'),
 
 sources = [ libgeoclue_public_api_gen_sources[1],
             geoclue_iface_sources,
-            wpa_supplicant_sources,
-            compass_iface_sources ]
+            wpa_supplicant_sources ]
 
 sources += gnome.genmarshal('gclue-marshal',
                             prefix: 'gclue_marshal',
@@ -17,11 +16,11 @@ include_dirs = [ configinc,
 sources += [ 'gclue-main.c',
              'gclue-3g-tower.h',
              'gclue-client-info.h', 'gclue-client-info.c',
-             'gclue-compass.h', 'gclue-compass.c',
              'gclue-config.h', 'gclue-config.c',
              'gclue-error.h', 'gclue-error.c',
              'gclue-location-source.h', 'gclue-location-source.c',
              'gclue-locator.h', 'gclue-locator.c',
+             'gclue-nmea-utils.h', 'gclue-nmea-utils.c',
              'gclue-service-manager.h', 'gclue-service-manager.c',
              'gclue-service-client.h', 'gclue-service-client.c',
              'gclue-service-location.h', 'gclue-service-location.c',
@@ -32,7 +31,7 @@ sources += [ 'gclue-main.c',
              'gclue-location.h', 'gclue-location.c' ]
 
 if get_option('3g-source') or get_option('cdma-source') or get_option('modem-gps-source')
-    geoclue_deps += [ dependency('mm-glib', version: '>= 1.6') ]
+    geoclue_deps += [ dependency('mm-glib', version: '>= 1.10') ]
     sources += [ 'gclue-modem.c',
 				 'gclue-modem.h',
 				 'gclue-modem-manager.c',
@@ -57,6 +56,10 @@ if get_option('nmea-source')
     sources += [ 'gclue-nmea-source.h', 'gclue-nmea-source.c' ]
 endif
 
+if get_option('compass')
+    sources += [ compass_iface_sources , 'gclue-compass.h', 'gclue-compass.c' ]
+endif
+
 c_args = [ '-DG_LOG_DOMAIN="Geoclue"' ]
 link_with = [ libgeoclue_public_api ]
 executable('geoclue',