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)", ×tamp); + + 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',