diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000000000000000000000000000000000..56ff6a0dedb11e8a34aeca904d0896ca898d6933 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,24 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +trim_trailing_whitespace = true + +[*.[ch]] +indent_size = 8 +indent_style = space +insert_final_newline = true +# For the legacy tabs which still exist in the code: +tab_width = 8 + +[*.xml] +indent_size = 2 +indent_style = space + +[meson.build] +indent_size = 4 +indent_style = space + +[*.md] +max_line_length = 80 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bea3b6931acfdecd4bc93e8ec3debd848a618b57..ba4baa643f6f9abf6c20ab2b7720d622f25c6e43 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,28 +1,26 @@ before_script: - - sed -i '/^#\sdeb-src /s/^#//' '/etc/apt/sources.list' + - sed -i 's/^\(Types. .*\)$/\1 deb-src/' /etc/apt/sources.list.d/debian.sources - apt-get --allow-unauthenticated update && apt-get build-dep --yes geoclue-2.0 - - 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 -# +workflow: + rules: + - if: $CI_PIPELINE_SOURCE == 'merge_request_event' + - if: $CI_PIPELINE_SOURCE == 'push' -ubuntu-18.04: - image: ubuntu:bionic +debian-bookworm: + image: debian:bookworm artifacts: when: always - name: "bionic-${CI_COMMIT_REF_NAME}" + name: "bookworm-${CI_COMMIT_REF_NAME}" paths: - "${CI_PROJECT_DIR}/build" - script: meson build && ninja -C build && ninja -C build test && ninja -C build install + script: meson setup build && ninja -C build && ninja -C build test && ninja -C build install -ubuntu-18.04-no-backend: - image: ubuntu:bionic +debian-bookworm-no-backend: + image: debian:bookworm artifacts: when: always - name: "bionic-no-backend-${CI_COMMIT_REF_NAME}" + name: "bookworm-no-backend-${CI_COMMIT_REF_NAME}" paths: - "${CI_PROJECT_DIR}/build" - script: meson -Denable-backend=false build && ninja -C build && ninja -C build test && ninja -C build install + script: meson setup -Denable-backend=false build && ninja -C build && ninja -C build test && ninja -C build install diff --git a/HACKING.md b/HACKING.md index ef63ca020be05f3ca29cb5241c4a1bec53265f6f..8aea59b3548503ae897f8d9c2d8c2754162be882 100644 --- a/HACKING.md +++ b/HACKING.md @@ -4,10 +4,10 @@ is already included in your distro/OS, you should be able to use the package manager's command to install all build depedndancies. - * gio (>= 2.44.0) + * gio * gobject-introspection * json-glib - * libsoup2.4 (>= 2.42) + * libsoup3.0 * pkg-config Fedora: diff --git a/NEWS b/NEWS index 9a6d731dabebfd1f6093d8e0a67d643949fd1e6f..6dcfebb7648dabdc73096128394dfe2ba9d14822 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,81 @@ +2.7.2 +===== + +- Don't warn about missing conf.d directory +- Accept 11-part RMC NMEA sentences from pre NMEA v2.3 GPS sources +- Log the service client list on SIGUSR1 +- Read GPS locations from non-enabled ModemManager modem, enabling GPS use without a SIM card +- Bump ModemManager dependency to version 1.12 +- Mozilla Location Service (MLS) has been retired, remove MLS related constants and configs: + - Remove DEFAULT_WIFI_URL and DEFAULT_WIFI_SUBMIT_URL + - If wifi URL is not set in config, disable wifi and 3g sources + - Remove Mozilla URLs from default config file + - Remove mozilla-api-key option from meson build +- Allow specifying default Ichnaea-compatible locate and submit URLs in meson build +- Allow setting an empty submission-nick in config +- Add User-Agent header to locate and submit queries +- Upgrade GLib / Gio dependency to version 2.74.0 +- Use GApplication in demo agent, ensuring a unique, user session bound process +- The async constructor gclue_simple_new in libgeoclue now can be canceled properly + +Contributors: +- Kira Bruneau +- Teemu Ikonen +- Maciej S. Szmigiero +- Jan Alexander Steffens (heftig) +- Balló György +- Markus Göllnitz + +2.7.1 +===== + +- Add 'age' field to MLS locate queries +- Location updates now always have an accuracy value +- Improvements to NMEA parsing: + - Parse NMEA timestamps with sub-second accuracy + - Add default accuracy to NMEA RMC locations + - Ignore locations from GGA and RMC sentences if the GNSS fix is not valid +- Prioritize GNSS sources with a recent fix over other sources, preventing location jumps +- Install D-Bus policy in /usr/share, not /etc +- Upgrade GLib / Gio dependency to version 2.68.0 +- Correct gi annotations in GClueSimple +- Various small fixes + +Contributors: +- Chris Talbot +- Gioele Barabucci +- Philipp Hörist +- Teemu Ikonen + +2.7.0 +===== + +- Multiple config files named *.conf are now read from the config directory at @sysconfdir@/geoclue/conf.d, +- HTTP requests are now made via libsoup3.0 instead of libsoup2.4, +- A static location can now be set in @sysconfdir@/geolocation for immobile systems, +- Web source requests are now submitted with combined WiFi and 3GPP tower data, +- Web source now checks connectivity in a way that allows location and submission servers running on localhost, +- Web source submissions are now made using /v2/geosubmit API, +- Web source cell tower submissions now have the correct radio type, +- Web source requests now submit the BSS age property, +- Web source submissions now contain the location speed, +- Web source cache now respects WiFi signal tolerance strictly, +- NMEA source now supports both '\n' and '\r' NMEA delimiters, +- NMEA source can now be made the Web source submit source, +- ModemManager now use signaled calls to get cached location information to avoid performing explicit modem query, +- Location description now contains information about its source, +- GSettings backend no longer complains about being run from a read-only filesystem, +- Many small improvements and fixes, some memory safety related. + +Contributors: +- Andrey Skvortsov +- Carlos Garcia Campos +- Dylan Van Assche +- Shoji Keita +- Teemu Ikonen +- Maciej S. Szmigiero +- Maximiliano Sandoval R + 2.6.0 ===== @@ -12,9 +90,9 @@ - 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 +- Be strict with time and distance threshold - Fix the XDG location portal integration -- Replace agent wait timeout with a queue +- Replace agent wait timeout with a queue - Other bugs fixes Contributors: @@ -22,10 +100,10 @@ Contributors: - Laurent Bigonville - Angus Ainslie - Dan Nicholson -- Guido Günther +- Guido Günther - Jan Alexander Steffens -- Abderrahim Kitouni -- clayton craft +- Abderrahim Kitouni +- clayton craft - Ian Douglas Scott - Chupligin Sergey - Dor Askayo diff --git a/README.md b/README.md index 6242120dc4deab9fb0233edfdbc1b0bfcd962e3c..b3f7d907e93ebc42a3087827abd6c6b6991522f3 100644 --- a/README.md +++ b/README.md @@ -6,67 +6,73 @@ is to make creating location-aware applications as simple as possible. Geoclue is Free Software, licensed under GNU GPLv2+. -Geoclue comprises the following functionalities : +Geoclue comprises the following functionalities : - WiFi-based geolocation (accuracy: in meters) - GPS(A) receivers (accuracy: in centimeters) -- GPS of other devices on the local network, e.g smartphones (accuracy: +- GPS of other devices on the local network, e.g smartphones (accuracy: in centimeters) - 3G modems (accuracy: in kilometers, unless modem has GPS) -- GeoIP (accuracy: city-level) - -WiFi-based geolocation makes use of -[Mozilla Location Service](https://wiki.mozilla.org/CloudServices/Location). - -If geoclue is unable to find you, you can easily fix that by installing -and running a -[simple app](https://wiki.mozilla.org/CloudServices/Location#Contributing) on -your phone. For using phone GPS, you'll need to install the latest version of +- Static location source (reads location from a system-wide file) + +WiFi and cell tower-based geolocation used to use +[Mozilla Location Service](https://wiki.mozilla.org/CloudServices/Location), +which closed on June 12th, 2024. Geoclue currently does not enable WiFi location +by default. Some potential replacements for MLS are: +- [Positon](https://positon.xyz/). *Free-of-charge, network-based location +service for the open-source software community.* Uses data from a commercial +location service, requires API keys. +- [BeaconDB](https://beacondb.net/). *Public domain wireless geolocation +database.* Crowd-sourced WiFi, Bluetooth and cell tower database. +- [OLS](https://codeberg.org/tpikonen/ols). *Offline Location Service.* +Local-data on-device server, mostly useful for devices with GPS. + +For using phone GPS, you can install the latest version of [GeoclueShare app](https://github.com/ankitstarski/GeoclueShare/releases) on your phone (currently, this is supported only on Android devices). -Geoclue was also used for (reverse-)geocoding but that functionality has -been dropped in favour of the +Geoclue was also used for (reverse-)geocoding but that functionality has +been dropped in favour of the [geocode-glib library](http://ftp.gnome.org/pub/GNOME/sources/geocode-glib/). # 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 +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. -There was yet another rewrite that we call geoclue2. The first version to +There was yet another rewrite that we call geoclue2. The first version to introduce the re-write was version 1.99. # Communication and Contribution -- Discussions take place on the +- Discussions take place on the [GNOME Discourse](https://discourse.gnome.org/c/platform). - The IRC chat for geoclue is on __#gnome-maps__ at irc.gimp.org . -- Issues are tracked on +- Issues are tracked on [Gitlab](https://gitlab.freedesktop.org/geoclue/geoclue/issues). # Get Source Code The source code is available as a tar-ball and in a Git repository. -For latest release tarballs, use the `Download` option of Gitlab on the +For latest release tarballs, use the `Download` option of Gitlab on the [tag of your choice](https://gitlab.freedesktop.org/geoclue/geoclue/tags/). -Older (than 2.4.13) releases are available +Older (than 2.4.13) releases are available [here](http://www.freedesktop.org/software/geoclue/releases/2.4/). Git repository for Geoclue: https://gitlab.freedesktop.org/geoclue/geoclue - + # Building Geoclue -The guidelines for building geoclue have been documented -[here](https://gitlab.freedesktop.org/geoclue/geoclue/blob/master/HACKING.md). +The guidelines for building geoclue have been documented +[here](https://gitlab.freedesktop.org/geoclue/geoclue/blob/master/HACKING.md). # Using Geoclue in an application - -- __D-Bus API__: The documentation for using geoclue with D-Bus API is + +- __D-Bus API__: The documentation for using geoclue with D-Bus API is [here](http://www.freedesktop.org/software/geoclue/docs/). -- __Libgeoclue API documentation__: The documentation is available +- __Libgeoclue API documentation__: The documentation is available [here](https://www.freedesktop.org/software/geoclue/docs/libgeoclue/). -- __C user application__: +- __C user application__: [Here](https://gitlab.freedesktop.org/geoclue/geoclue/blob/master/demo/where-am-i.c) -is an example showing a C application that uses -geoclue to locate its user. +is an example showing a C application that uses +geoclue to locate its user. diff --git a/data/geoclue.5.in b/data/geoclue.5.in index b6a5795546269cd19323dc7f5c3b7f0c03588039..1f219955d6f3facfe407dd8424d50885138cba12 100644 --- a/data/geoclue.5.in +++ b/data/geoclue.5.in @@ -1,43 +1,59 @@ -.TH "GEOCLUE CONFIGURATION" 5 +.TH "GEOCLUE CONFIGURATION" 5 .SH NAME geoclue.conf \- geoclue configuration parameters .SH SYNOPSIS -.B @sysconfdir@/geoclue/geoclue.conf +Main configuration file: @sysconfdir@/geoclue/geoclue.conf +.br +Overwriting parameters files: @sysconfdir@/geoclue/conf.d .SH DESCRIPTION .ad .fi -The geoclue geoclue.conf configuration file specifies parameters that -control the operation of geoclue. +The main GeoClue configuration file 'geoclue.conf' specifies parameters +that control the operation of geoclue. Parameters can be overwritten by +placing configuration files in conf.d directory and applied in alphabetic +order. Thus, a configuration file '90-config.conf' will overwrite parameters +specified in another configuration file '50-config.conf' in the conf.d +directory. .PP All configurations settings below are mandatory and the defaults are what you see before you edit them in geoclue.conf. If you want to keep the default values around, copy and comment out the appropriate line(s) before changing them. +.PP +Missing 'enable' key for a particular source in the main configuration file +causes that source to be enabled by default. Adding 'enable' key setting +to any further config file can overwrite this default. .SH AGENT CONFIGURATION OPTIONS .B \fI[agent] is used to begin the agent configuration. -.IP \fBwhitelist +.IP \fBwhitelist .br Whitelist of desktop IDs (without .desktop part) of all agents we recognise, separated by a ';'. -.IP +.IP .B whitelist=geoclue-demo-agent;gnome-shell;io.elementary.desktop.agent-geoclue2 .br .IP \fB[network-nmea] .br Network NMEA source configuration options .IP -.B \fBenable=true +.B \fBenable=true .br Fetch location from NMEA sources on local network? .br -.IP \fB[3G] +.IP +.B \fBnmea-socket=/var/run/gps-share.sock +.br +Use a nmea unix socket as the data source. +If not set, unix socket will not be used. +.br +.IP \fB[3g] .br 3G source configuration options .IP -.B \fBenable=true +.B \fBenable=true .br Enable 3G source .br @@ -45,7 +61,7 @@ Enable 3G source .br CDMA source configuration options .IP -.B \fBenable=true +.B \fBenable=true .br Enable CDMA source .br @@ -53,7 +69,7 @@ Enable CDMA source .br Modem GPS source configuration options .IP -.B \fBenable=true +.B \fBenable=true .br Enable Modem-GPS source .br @@ -61,14 +77,15 @@ Enable Modem-GPS source .br WiFi source configuration options .IP -.B \fBenable=true +.B \fBenable=true .br Enable WiFi source .IP -.B url=\fIhttps://location.services.mozilla.com/v1/geolocate?key=geoclue +.B url=\fIhttps://location.services.mozilla.com/v1/geolocate?key=YOUR_KEY .br -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 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. .IP .B submit-data=false Submit data to Mozilla Location Service @@ -76,16 +93,37 @@ Submit data to Mozilla Location Service If set to true, geoclue will automatically submit network data to Mozilla each time it gets a GPS lock. .IP -.B submission-url=\fIhttps://location.services.mozilla.com/v1/submit?key=geoclue +.B submission-url=\fIhttps://location.services.mozilla.com/v2/geosubmit?key=YOUR_KEY .br URL to submission API of Mozilla Location Service .IP .B submission-nick=geoclue .br -A nickname to submit network data with. A nickname must be 2-32 characters long. +A nickname to submit network data with. If empty, omitted from the submission. Otherwise, must be 2 to 32 characters long. Defaults to "geoclue". +.br +.IP \fB[compass] +.br +Compass configuration options +.IP +.B \fBenable=true +.br +Enable Compass +.br +.IP \fB[static-source] +.br +Static source configuration options. +.br +This source reads location from "geolocation" file in @sysconfdir@. While this file is constantly monitored for changes during geoclue operation, and the reported static location is updated accordingly, this source isn't meant for inputting a dynamically changing location to geoclue (please use the Network NMEA source for that). +.IP +.B \fBenable=true +.br +Enable the static source. +.br +If you make use of this source, you probably should disable other location +sources in geoclue.conf so they won't override the configured static location. .br .SH APPLICATION CONFIGURATION OPTIONS -Having an entry here for an application with +Having an entry here for an application with .B allowed=true means that geoclue will not ask agent to authorize the application. This is to ensure that applications with built-in authorization mechanism (e.g web @@ -165,6 +203,47 @@ system=false .br users= .br +.SH STATIC LOCATION FILE +.SS Basic format: +The static location file in @sysconfdir@ (used by the static source) is a text file consisting of the following: +.nr step 1 1 +.IP \n[step] +Latitude (floating point number; positive values mean north, negative south) +.IP \n+[step] +Longitude (floating point number; positive values mean east, negative west) +.IP \n+[step] +Altitude (floating point number; in meters) +.IP \n+[step] +Accuracy radius (floating point number; in meters) +.RE +.PP +These values need to be separated by newline characters. +.SS Additional format information: +.IP \[bu] +The '\[sh]' character starts a comment, which continues until the end of the current line. +.IP \[bu] +Leading and trailing white-space on each line is ignored. +.IP \[bu] +Empty lines (or containing just white-space or a comment) are ignored. +.SS Example: +.EX +# Example static location file for a machine inside Statue of Liberty torch + +40.6893129 # latitude +-74.0445531 # longitude +96 # altitude +1.83 # accuracy radius (the diameter of the torch is 12 feet) +.EE +.SS Notes: +For extra security, the static location file can be made readable just by the geoclue user: +.EX +# chown @dbus_srv_user@ @sysconfdir@/geolocation +# chmod 600 @sysconfdir@/geolocation +.EE +.br +.SH CLIENT LIST +Sending SIGUSR1 to a running geoclue process prints the current list of clients to the log. +.br .SH AUTHOR .na .nf diff --git a/data/geoclue.conf.in b/data/geoclue.conf.in index 650470d72bed4c1123e792b7444fbb065ece8f2a..0eb59cdd234dfce609b1fc104c5692e8c59b095a 100644 --- a/data/geoclue.conf.in +++ b/data/geoclue.conf.in @@ -25,7 +25,7 @@ enable=true [3g] # Enable 3G source -enable=true +enable=@default_wifi_enable@ # CDMA source configuration options [cdma] @@ -43,14 +43,16 @@ enable=true [wifi] # Enable WiFi source -enable=true +# If this source and the static source below are both disabled a GeoIP-only +# source will be used instead. +enable=@default_wifi_enable@ -# 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 +# URL to a WiFi geolocation service compatible with the Ichnaea API +# (https://ichnaea.readthedocs.io/en/latest/api/geolocate.html). +# An API key can be set by using the 'key' URL parameter. +#url=https://example.com/v1/geolocate?key=YOUR_KEY -# To use the Google geolocation service instead of Mozilla's, uncomment this URL +# To use the Google geolocation service, 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 @@ -60,18 +62,24 @@ enable=true # #url=https://www.googleapis.com/geolocation/v1/geolocate?key=YOUR_KEY -# Submit data to Mozilla Location Service -# If set to true, geoclue will automatically submit network data to Mozilla +# Submit data to WiFi geolocation service +# If set to true, geoclue will automatically submit network data # each time it gets a GPS lock. # +# Currently, only Modem-GPS or Network NMEA sources are supported as providers +# of a location to submit (one at a time). +# If Modem-GPS source is enabled above it will be the exclusive provider +# (regardless whether the system is actually equipped with such modem), +# otherwise Network NMEA source will be considered. +# submit-data=false -# 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 +# URL to submit data to a WiFi geolocation service with an Ichnaea compatible API +# (https://ichnaea.readthedocs.io/en/latest/api/geosubmit2.html). +#submission-url=https://example.com/v2/geosubmit?key=YOUR_KEY -# A nickname to submit network data with. A nickname must be 2-32 characters long. +# A nickname to submit network data with. If empty, omitted from the submission. +# Otherwise, must be 2 to 32 characters long. Defaults to "geoclue". submission-nick=geoclue # Compass configuration options @@ -80,6 +88,17 @@ submission-nick=geoclue # Enable Compass enable=true +# Static source configuration options +# +# This source reads location from "geolocation" file in @sysconfdir@ - please +# consult geoclue(5) man page for the format description of this file. +[static-source] + +# Enable the static source +# If you make use of this source, you probably should disable other location +# sources in this file so they won't override the configured static location. +enable=true + # Application configuration options # # NOTE: Having an entry here for an application with allowed=true means that diff --git a/data/geoclue.service.in b/data/geoclue.service.in index 6449d3055e4abac11bb79c264af7494a459323c7..ec5507de3ef72e399d0456e02199494ed5e4dc4e 100644 --- a/data/geoclue.service.in +++ b/data/geoclue.service.in @@ -5,6 +5,7 @@ Description=Location Lookup Service Type=dbus BusName=org.freedesktop.GeoClue2 User=@dbus_srv_user@ +Environment="GSETTINGS_BACKEND=memory" ExecStart=@libexecdir@/geoclue # Filesystem lockdown diff --git a/data/meson.build b/data/meson.build index a1fc61f18723837d0b25e008e93038c66767ebdf..b22ff558b2aeff7c443f69b643168ce2061406d4 100644 --- a/data/meson.build +++ b/data/meson.build @@ -1,5 +1,6 @@ if get_option('enable-backend') conf = configuration_data() + conf.set('sysconfdir', sysconfdir) if get_option('demo-agent') conf.set('demo_agent', 'geoclue-demo-agent;') @@ -7,6 +8,12 @@ if get_option('enable-backend') conf.set('demo_agent', '') endif + if get_option('default-wifi-url') != '' + conf.set('default_wifi_enable', 'true') + else + conf.set('default_wifi_enable', 'false') + endif + conf_dir = join_paths(sysconfdir, 'geoclue') configure_file(output: 'geoclue.conf', input: 'geoclue.conf.in', @@ -18,6 +25,9 @@ if get_option('enable-backend') conf.set('dbus_srv_user', get_option('dbus-srv-user')) conf.set('sysconfdir', sysconfdir) + confd_dir = join_paths(conf_dir, 'conf.d') + install_emptydir(confd_dir) + service_dir = join_paths(datadir, 'dbus-1', 'system-services') configure_file(output: 'org.freedesktop.GeoClue2.service', input: 'org.freedesktop.GeoClue2.service.in', @@ -33,7 +43,7 @@ if get_option('enable-backend') # DBus Service policy file dbus_service_dir = get_option('dbus-sys-dir') if dbus_service_dir == '' - dbus_service_dir = join_paths(sysconfdir, 'dbus-1', 'system.d') + dbus_service_dir = join_paths(datadir, 'dbus-1', 'system.d') endif configure_file(output: 'org.freedesktop.GeoClue2.conf', input: 'org.freedesktop.GeoClue2.conf.in', @@ -48,7 +58,7 @@ if get_option('enable-backend') if systemd_unit_dir == '' dep = dependency('systemd', required: false) if dep.found() - systemd_unit_dir = dep.get_pkgconfig_variable('systemdsystemunitdir') + systemd_unit_dir = dep.get_variable(pkgconfig: 'systemdsystemunitdir') endif endif diff --git a/debian/README.Debian b/debian/README.Debian index 77baa3ee01000a871e9252ec4ac3be3edb3d3956..7f1315b14322da77e35c12a2d4dc9ba928c090f7 100644 --- a/debian/README.Debian +++ b/debian/README.Debian @@ -1,8 +1,8 @@ geoclue-2.0 for Debian --------------------- -geoclue uses beaconDB to estimate the location of your computer. -Please refer to the Privacy Notice on the beaconDB website for all privacy -concerns: https://beacondb.net/privacy/ +geoclue uses the Mozilla Location Service (MLS) to estimate the location of +your computer. Please refer to the Privacy Notice on the Mozilla website for +all privacy concerns: https://location.services.mozilla.com/privacy - -- Laurent Bigonville <bigon@debian.org> Thu, 24 Dec 2024 14:44:03 +0100 + -- Laurent Bigonville <bigon@debian.org> Thu, 23 Apr 2020 14:28:03 +0200 diff --git a/debian/changelog b/debian/changelog index 2aca00150e4b8823873cd63b8a257f95756e93e0..61f9ad94ae79309895daa383be80b6d4fdf2956d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,10 +1,84 @@ -geoclue-2.0 (2.6.0-2+deb12u1) bookworm; urgency=medium +geoclue-2.0 (2.7.2-2) unstable; urgency=medium - * Switch to beaconDB instead of the new retired MLS (Closes: #1074427) - * d/README.Debian: Update the README to explicitly state we are using beaconDB - * Add SSID to queries, cherry-picked from upstream for beaconDB + * debian/control: Add Provides: ${gir:Provides} for the -dev package + * debian/rules: Use api.beacondb.net instead of beacondb.net - -- Laurent Bigonville <bigon@debian.org> Tue, 24 Dec 2024 18:06:58 +0100 + -- Laurent Bigonville <bigon@debian.org> Sun, 22 Dec 2024 10:11:37 +0100 + +geoclue-2.0 (2.7.2-1) unstable; urgency=low + + * New upstream release + - Bump build-dependencies + * debian/watch: Fix the logic after GitLab changes (Closes: #1079914) + * debian/geoclue-2.0.lintian-overrides: Drop + package-supports-alternative-init-but-no-init.d-script, not used anymore + * debian/control: + - Bump Standards-Version to 4.7.0 (no further changes) + - Update build-dependencies to please lintian: pkg-config => pkgconf + * debian/rules: Use beaconDB as a source, with the maintainer approval + (Closes: #1074427) + * debian/patches: Cherry-pick several patches for beaconDB + + -- Laurent Bigonville <bigon@debian.org> Thu, 12 Dec 2024 11:49:19 +0100 + +geoclue-2.0 (2.7.1-2) unstable; urgency=medium + + * Team upload + * Install systemd service files into /usr/lib. + Add a corresponding versioned Build-Depends on debhelper (>= 13.11.6) to + ensure we have a recent enough version of dh_installsystemd. + * Update lintian override accordingly + + -- Michael Biebl <biebl@debian.org> Wed, 31 Jan 2024 14:45:04 +0100 + +geoclue-2.0 (2.7.1-1) unstable; urgency=medium + + * New upstream release + - Bump libglib2.0-dev BD to >= 2.68.0 + - debian/geoclue-2.0.install: Adjust installation paths of dbus files + * Update apparmor policy to allow several DBus operations, from Ubuntu + Thanks to Georgia Garcia (Closes: #1050694) + + -- Laurent Bigonville <bigon@debian.org> Fri, 22 Sep 2023 13:03:48 +0200 + +geoclue-2.0 (2.7.0-3) unstable; urgency=medium + + * Move D-Bus policy files from /etc/dbus-1 to /usr/share/dbus-1 (Closes: + #1041780) + * Apparmor: Allow to query IPv6 status (Closes: #1041894) + + -- Laurent Bigonville <bigon@debian.org> Sat, 29 Jul 2023 09:06:57 +0200 + +geoclue-2.0 (2.7.0-2) unstable; urgency=medium + + * Apparmor: Allow reading configuration files in /etc/geoclue/conf.d/ + * Apparmor: Allow reading /etc/geolocation. + Thanks to Edward Betts <edward@4angle.com> + + -- Laurent Bigonville <bigon@debian.org> Sat, 15 Jul 2023 12:36:08 +0200 + +geoclue-2.0 (2.7.0-1) unstable; urgency=medium + + [ Michael Biebl ] + * Remove deprecated polkit pkla file + + [ Laurent Bigonville ] + * New upstream release + - Drop d/p/Add-support-for-building-with-libsoup3.patch: Applied upstream + * debian/control: Bump meson build-dependency to >= 0.60.0 + + -- Laurent Bigonville <bigon@debian.org> Sat, 24 Jun 2023 09:34:17 +0200 + +geoclue-2.0 (2.6.0-3) unstable; urgency=medium + + * Do not install the polkit rules files on non-linux architectures. + This should fix the FTBFS on these architectures + * debian/geoclue-2.0.lintian-overrides: Fix the lintian overrides + * debian/control: Bump Standards-Version to 4.6.2 (no further changes) + * debian/control: Drop obsolete Breaks/Replaces + * Add apparmor profile for the geoclue daemon + + -- Laurent Bigonville <bigon@debian.org> Mon, 19 Jun 2023 09:30:37 +0200 geoclue-2.0 (2.6.0-2) unstable; urgency=medium diff --git a/debian/control b/debian/control index c9d72a3ad8b587cc6ba193c00ec8bc668c943d62..48dda31130c9d9e48047d822508640ef9499af3e 100644 --- a/debian/control +++ b/debian/control @@ -3,22 +3,25 @@ Section: utils Priority: optional Maintainer: Laurent Bigonville <bigon@debian.org> Build-Depends: debhelper-compat (= 12), + debhelper (>= 13.11.6), + dh-apparmor, + dh-exec, gobject-introspection (>= 0.9.6), intltool (>= 0.40.0), libavahi-client-dev (>= 0.6.10), libavahi-glib-dev (>= 0.6.10), libgirepository1.0-dev (>= 0.9.6), - libglib2.0-dev (>= 2.44.0), + libglib2.0-dev (>= 2.74.0), libjson-glib-dev (>= 0.14), - libmm-glib-dev (>= 1.6) [linux-any], + libmm-glib-dev (>= 1.12) [linux-any], libnotify-dev, libsoup-3.0-dev, - meson (>= 0.47.2), - pkg-config, + meson (>= 0.60.0), + pkgconf, valac Build-Depends-Indep: gtk-doc-tools <!nodoc>, libglib2.0-doc <!nodoc> Homepage: https://gitlab.freedesktop.org/geoclue/geoclue/wikis/home -Standards-Version: 4.6.0 +Standards-Version: 4.7.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 @@ -30,8 +33,8 @@ Recommends: avahi-daemon, iio-sensor-proxy [linux-any], modemmanager [linux-any], wpasupplicant [!hurd-any] -Breaks: geoclue (>= 2.0), geoclue-2-demo (<< 2.4.12-2~) -Replaces: geoclue (>= 2.0), geoclue-2-demo (<< 2.4.12-2~) +Breaks: geoclue (>= 2.0) +Replaces: geoclue (>= 2.0) Description: geoinformation service GeoClue is a D-Bus geoinformation service. The goal of the Geoclue project is to make creating location-aware applications as simple as possible. @@ -41,8 +44,6 @@ Description: geoinformation service Package: geoclue-2-demo Architecture: any Depends: geoclue-2.0 (= ${binary:Version}), ${misc:Depends}, ${shlibs:Depends} -Breaks: geoclue-2.0 (<< 2.4.10) -Replaces: geoclue-2.0 (<< 2.4.10) Description: geoinformation service (demonstration programs) GeoClue is a D-Bus geoinformation service. The goal of the Geoclue project is to make creating location-aware applications as simple as possible. @@ -82,6 +83,7 @@ Depends: gir1.2-geoclue-2.0 (= ${binary:Version}), ${shlibs:Depends} Suggests: libgeoclue-doc Multi-Arch: same +Provides: ${gir:Provides} Description: convenience library to interact with geoinformation service (devel files) GeoClue is a D-Bus geoinformation service. The goal of the Geoclue project is to make creating location-aware applications as simple as possible. diff --git a/debian/geoclue-2.0.install b/debian/geoclue-2.0.install old mode 100644 new mode 100755 index 7b7032e243b69cbc55ad389abe28892c8c287e7e..dec0248aca1ab384906d698a1ebe872eaec1843b --- a/debian/geoclue-2.0.install +++ b/debian/geoclue-2.0.install @@ -1,13 +1,12 @@ -debian/local/org.freedesktop.GeoClue2.pkla var/lib/polkit-1/localauthority/10-vendor.d/ -etc/dbus-1/system.d/ +#!/usr/bin/dh-exec +debian/local/usr.libexec.geoclue etc/apparmor.d/ etc/geoclue/ etc/xdg/autostart/geoclue-demo-agent.desktop -lib/systemd/system/ +usr/lib/systemd/system/ usr/lib/*/pkgconfig/geoclue-2.0.pc usr/libexec/geoclue-2.0/demos/agent usr/libexec/geoclue usr/share/applications/geoclue-demo-agent.desktop -usr/share/dbus-1/interfaces/ -usr/share/dbus-1/system-services/ +usr/share/dbus-1/ usr/share/man/man5/geoclue.5 -usr/share/polkit-1/rules.d/ +[linux-any] usr/share/polkit-1/rules.d/ diff --git a/debian/geoclue-2.0.lintian-overrides b/debian/geoclue-2.0.lintian-overrides index 499171055e0ed1bae73fe9f402b5bc1cb6273380..4055f3de4e096ddd1663d6a91a0e0bfc28256916 100644 --- a/debian/geoclue-2.0.lintian-overrides +++ b/debian/geoclue-2.0.lintian-overrides @@ -1,5 +1,3 @@ # 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 (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 +geoclue-2.0: dbus-policy-excessively-broad <policy user="geoclue"><allow send_interface="org.freedesktop.DBus.Properties" send_path="/org/freedesktop/GeoClue2/Agent"/> [usr/share/dbus-1/system.d/org.freedesktop.GeoClue2.Agent.conf:2] diff --git a/debian/geoclue-2.0.maintscript b/debian/geoclue-2.0.maintscript new file mode 100644 index 0000000000000000000000000000000000000000..59b916846ec4f2f5d14f72173d0de993ddb5f87d --- /dev/null +++ b/debian/geoclue-2.0.maintscript @@ -0,0 +1,2 @@ +rm_conffile /etc/dbus-1/system.d/org.freedesktop.GeoClue2.Agent.conf 2.7.0-3~ +rm_conffile /etc/dbus-1/system.d/org.freedesktop.GeoClue2.conf 2.7.0-3~ diff --git a/debian/local/org.freedesktop.GeoClue2.pkla b/debian/local/org.freedesktop.GeoClue2.pkla deleted file mode 100644 index 05458bee0a8593e0d6ad30391619c8c884eed728..0000000000000000000000000000000000000000 --- a/debian/local/org.freedesktop.GeoClue2.pkla +++ /dev/null @@ -1,6 +0,0 @@ -[Allow geoclue to query the location of a modem] -Identity=unix-user:geoclue -Action=org.freedesktop.ModemManager1.Device.Control;org.freedesktop.ModemManager1.Location -ResultAny=yes -ResultInactive=yes -ResultActive=yes diff --git a/debian/local/usr.libexec.geoclue b/debian/local/usr.libexec.geoclue new file mode 100644 index 0000000000000000000000000000000000000000..3bb82248a4270a8b8665fd880d0e9dbdc5698530 --- /dev/null +++ b/debian/local/usr.libexec.geoclue @@ -0,0 +1,87 @@ +# vim:syntax=apparmor +abi <abi/3.0>, + +include <tunables/global> + +/usr/libexec/geoclue flags=(attach_disconnected) { + include <abstractions/base> + include <abstractions/dbus-session-strict> + include <abstractions/gnome> + include <abstractions/nameservice> + + /etc/geoclue/geoclue.conf r, + /etc/geoclue/conf.d/ r, + /etc/geoclue/conf.d/*.conf r, + /etc/geolocation r, + /proc/sys/net/ipv6/conf/all/disable_ipv6 r, + /proc/*/cgroup r, + /usr/libexec/geoclue mr, + + # Permissions required to start the geoclue systemd service (LP: #2030951) + dbus send + bus=system + path=/fi/w1/wpa_supplicant1 + interface=org.freedesktop.DBus.Properties + member=GetAll + peer=(label=unconfined), + + dbus send + bus=system + path=/ + interface=org.freedesktop.DBus.Peer + member=Ping, + + dbus send + bus=system + path=/org/freedesktop/DBus + interface=org.freedesktop.DBus + member=RequestName + peer=(label=unconfined), + + dbus send + bus=system + path=/org/freedesktop/ModemManager1 + interface=org.freedesktop.DBus.ObjectManager + member=GetManagedObjects + peer=(label=unconfined), + + dbus send + bus=system + path=/org/freedesktop/NetworkManager + interface=org.freedesktop.DBus.Properties + member=GetAll + peer=(label=unconfined), + + dbus bind + bus=system + name=org.freedesktop.GeoClue2, + + dbus send + bus=system + path=/ + interface=org.freedesktop.Avahi.Server + member={GetAPIVersion,GetState,ServiceBrowserNew}, + + dbus send + bus=system + path=/org/freedesktop/DBus + interface=org.freedesktop.DBus + member=ReleaseName + peer=(label=unconfined), + + dbus receive + bus=system + path=/Client[0-9]*/ServiceBrowser1 + interface=org.freedesktop.Avahi.ServiceBrowser + member={CacheExhausted,AllForNow}, + + dbus receive + bus=system + path=/org/freedesktop/GeoClue2/Manager + interface=org.freedesktop.DBus.Properties + member={GetAll,AddAgent,PropertiesChanged} + peer=(label=unconfined), + + # Site-specific additions and overrides. See local/README for details. + #include <local/usr.libexec.geoclue> +} diff --git a/debian/patches/0003-PATCH-Mozilla-Include-SSID-for-geolocate-and-submiss.patch b/debian/patches/0001-Mozilla-Include-SSID-for-geolocate-and-submission-re.patch similarity index 77% rename from debian/patches/0003-PATCH-Mozilla-Include-SSID-for-geolocate-and-submiss.patch rename to debian/patches/0001-Mozilla-Include-SSID-for-geolocate-and-submission-re.patch index 63de80034d6f66088fa345fb0511d4371415816b..bc5375679b17ee0cc0001852362c12fb42cb481a 100644 --- a/debian/patches/0003-PATCH-Mozilla-Include-SSID-for-geolocate-and-submiss.patch +++ b/debian/patches/0001-Mozilla-Include-SSID-for-geolocate-and-submission-re.patch @@ -1,24 +1,24 @@ From: Chris Talbot <chris@talbothome.com> -Date: Tue, 24 Dec 2024 17:01:30 +0100 -Subject: [PATCH] Mozilla: Include SSID for geolocate and submission requests +Date: Sun, 1 Dec 2024 21:33:38 -0700 +Subject: Mozilla: Include SSID for geolocate and submission requests --- src/gclue-mozilla.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/gclue-mozilla.c b/src/gclue-mozilla.c -index 3ee02c8..1f83f47 100644 +index 9e8feb1..abb2280 100644 --- a/src/gclue-mozilla.c +++ b/src/gclue-mozilla.c -@@ -209,6 +209,7 @@ gclue_mozilla_create_query (GList *bss_list, /* As in Access Points */ +@@ -257,6 +257,7 @@ gclue_mozilla_create_query (GClueMozilla *mozilla, for (iter = bss_list; iter != NULL; iter = iter->next) { WPABSS *bss = WPA_BSS (iter->data); char mac[BSSID_STR_LEN + 1] = { 0 }; + char ssid[MAX_SSID_LEN + 1] = { 0 }; gint16 strength_dbm; + guint age_ms; - if (gclue_mozilla_should_ignore_bss (bss)) -@@ -219,6 +220,10 @@ gclue_mozilla_create_query (GList *bss_list, /* As in Access Points */ +@@ -269,6 +270,10 @@ gclue_mozilla_create_query (GClueMozilla *mozilla, get_bssid_from_bss (bss, mac); json_builder_add_string_value (builder, mac); @@ -29,15 +29,15 @@ index 3ee02c8..1f83f47 100644 json_builder_set_member_name (builder, "signalStrength"); strength_dbm = wpa_bss_get_signal (bss); json_builder_add_int_value (builder, strength_dbm); -@@ -386,6 +391,7 @@ gclue_mozilla_create_submit_query (GClueLocation *location, +@@ -486,6 +491,7 @@ gclue_mozilla_create_submit_query (GClueMozilla *mozilla, for (iter = bss_list; iter != NULL; iter = iter->next) { WPABSS *bss = WPA_BSS (iter->data); char mac[BSSID_STR_LEN + 1] = { 0 }; + char ssid[MAX_SSID_LEN + 1] = { 0 }; gint16 strength_dbm; guint16 frequency; - -@@ -397,6 +403,10 @@ gclue_mozilla_create_submit_query (GClueLocation *location, + guint age_ms; +@@ -499,6 +505,10 @@ gclue_mozilla_create_submit_query (GClueMozilla *mozilla, get_bssid_from_bss (bss, mac); json_builder_add_string_value (builder, mac); @@ -45,6 +45,6 @@ index 3ee02c8..1f83f47 100644 + get_ssid_from_bss (bss, ssid); + json_builder_add_string_value (builder, ssid); + - json_builder_set_member_name (builder, "signal"); + json_builder_set_member_name (builder, "signalStrength"); strength_dbm = wpa_bss_get_signal (bss); json_builder_add_int_value (builder, strength_dbm); diff --git a/debian/patches/0002-Mozilla-replace-rather-than-append-User-Agent.patch b/debian/patches/0002-Mozilla-replace-rather-than-append-User-Agent.patch new file mode 100644 index 0000000000000000000000000000000000000000..31aa215d8973ce0b6368146a1bdeb4c560819066 --- /dev/null +++ b/debian/patches/0002-Mozilla-replace-rather-than-append-User-Agent.patch @@ -0,0 +1,31 @@ +From: Chris Talbot <chris@talbothome.com> +Date: Sun, 1 Dec 2024 21:34:44 -0700 +Subject: Mozilla: replace rather than append User-Agent + +It won't actually attach if you don't do this +--- + src/gclue-mozilla.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/gclue-mozilla.c b/src/gclue-mozilla.c +index abb2280..2b3467d 100644 +--- a/src/gclue-mozilla.c ++++ b/src/gclue-mozilla.c +@@ -301,7 +301,7 @@ gclue_mozilla_create_query (GClueMozilla *mozilla, + uri = gclue_mozilla_get_locate_url (mozilla); + ret = soup_message_new ("POST", uri); + request_headers = soup_message_get_request_headers (ret); +- soup_message_headers_append (request_headers, "User-Agent", USER_AGENT); ++ soup_message_headers_replace (request_headers, "User-Agent", USER_AGENT); + 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); +@@ -566,7 +566,7 @@ gclue_mozilla_create_submit_query (GClueMozilla *mozilla, + + ret = soup_message_new ("POST", url); + request_headers = soup_message_get_request_headers (ret); +- soup_message_headers_append (request_headers, "User-Agent", USER_AGENT); ++ soup_message_headers_replace (request_headers, "User-Agent", USER_AGENT); + if (nick != NULL && nick[0] != '\0') + soup_message_headers_append (request_headers, + "X-Nickname", diff --git a/debian/patches/0003-Web-Source-Set-User-Agent-on-Soup-Session-Constructi.patch b/debian/patches/0003-Web-Source-Set-User-Agent-on-Soup-Session-Constructi.patch new file mode 100644 index 0000000000000000000000000000000000000000..8db3f25e1c311a62496451e0d8e70e51404a60b8 --- /dev/null +++ b/debian/patches/0003-Web-Source-Set-User-Agent-on-Soup-Session-Constructi.patch @@ -0,0 +1,106 @@ +From: Chris Talbot <chris@talbothome.com> +Date: Thu, 5 Dec 2024 06:58:48 -0700 +Subject: Web-Source: Set User-Agent on Soup Session Construction + +Also add OS Info to user-agent +--- + src/gclue-mozilla.c | 6 ------ + src/gclue-web-source.c | 30 ++++++++++++++++++++++++++++++ + 2 files changed, 30 insertions(+), 6 deletions(-) + +diff --git a/src/gclue-mozilla.c b/src/gclue-mozilla.c +index 2b3467d..5680e45 100644 +--- a/src/gclue-mozilla.c ++++ b/src/gclue-mozilla.c +@@ -174,8 +174,6 @@ towertec_to_radiotype (GClueTowerTec tec, + return TRUE; + } + +-#define USER_AGENT (PACKAGE_NAME "/" PACKAGE_VERSION) +- + SoupMessage * + gclue_mozilla_create_query (GClueMozilla *mozilla, + gboolean skip_tower, +@@ -185,7 +183,6 @@ gclue_mozilla_create_query (GClueMozilla *mozilla, + { + gboolean has_tower = FALSE, has_bss = FALSE; + SoupMessage *ret = NULL; +- SoupMessageHeaders *request_headers; + JsonBuilder *builder; + g_autoptr(GList) bss_list = NULL; + JsonGenerator *generator; +@@ -300,8 +297,6 @@ gclue_mozilla_create_query (GClueMozilla *mozilla, + + uri = gclue_mozilla_get_locate_url (mozilla); + ret = soup_message_new ("POST", uri); +- request_headers = soup_message_get_request_headers (ret); +- soup_message_headers_replace (request_headers, "User-Agent", USER_AGENT); + 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); +@@ -566,7 +561,6 @@ gclue_mozilla_create_submit_query (GClueMozilla *mozilla, + + ret = soup_message_new ("POST", url); + request_headers = soup_message_get_request_headers (ret); +- soup_message_headers_replace (request_headers, "User-Agent", USER_AGENT); + if (nick != NULL && nick[0] != '\0') + soup_message_headers_append (request_headers, + "X-Nickname", +diff --git a/src/gclue-web-source.c b/src/gclue-web-source.c +index 92d94b8..56d511b 100644 +--- a/src/gclue-web-source.c ++++ b/src/gclue-web-source.c +@@ -28,6 +28,7 @@ + #include "gclue-error.h" + #include "gclue-location.h" + #include "gclue-mozilla.h" ++#include "config.h" + + /** + * SECTION:gclue-web-source +@@ -402,16 +403,45 @@ gclue_web_source_finalize (GObject *gsource) + G_OBJECT_CLASS (gclue_web_source_parent_class)->finalize (gsource); + } + ++static char * ++get_os_info (void) ++{ ++ g_autofree char *pretty_name = NULL; ++ g_autofree char *os_name = g_get_os_info (G_OS_INFO_KEY_NAME); ++ g_autofree char *os_version = g_get_os_info (G_OS_INFO_KEY_VERSION); ++ ++ if (os_name && os_version) ++ return g_strdup_printf ("%s; %s", os_name, os_version); ++ ++ pretty_name = g_get_os_info (G_OS_INFO_KEY_PRETTY_NAME); ++ if (pretty_name) ++ return g_steal_pointer (&pretty_name); ++ ++ /* Translators: Not marked as translatable as debug output should stay English */ ++ return g_strdup ("Unknown"); ++} ++ ++#define USER_AGENT (PACKAGE_NAME "/" PACKAGE_VERSION) ++ ++static char * ++get_user_agent (void) ++{ ++ g_autofree char *os_info = get_os_info (); ++ return g_strdup_printf ("%s (%s)", USER_AGENT, os_info); ++} ++ + static void + gclue_web_source_constructed (GObject *object) + { + GNetworkMonitor *monitor; + GClueWebSourcePrivate *priv = GCLUE_WEB_SOURCE (object)->priv; ++ g_autofree char *user_agent = get_user_agent (); + + G_OBJECT_CLASS (gclue_web_source_parent_class)->constructed (object); + + priv->soup_session = soup_session_new (); + soup_session_set_proxy_resolver (priv->soup_session, NULL); ++ soup_session_set_user_agent (priv->soup_session, user_agent); + + monitor = g_network_monitor_get_default (); + priv->network_changed_id = diff --git a/debian/patches/Add-support-for-building-with-libsoup3.patch b/debian/patches/Add-support-for-building-with-libsoup3.patch deleted file mode 100644 index d2c88049c2d9dee9e01e9cae1203b55da3b542af..0000000000000000000000000000000000000000 --- a/debian/patches/Add-support-for-building-with-libsoup3.patch +++ /dev/null @@ -1,294 +0,0 @@ -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/beacondb.patch b/debian/patches/beacondb.patch deleted file mode 100644 index fcef95584127915d6b13cf3b12c351048e61384c..0000000000000000000000000000000000000000 --- a/debian/patches/beacondb.patch +++ /dev/null @@ -1,53 +0,0 @@ -From: Laurent Bigonville <bigon@debian.org> -Date: Tue, 24 Dec 2024 16:53:00 +0100 -Subject: Switch to beaconDB - ---- - data/geoclue.conf.in | 11 ++++------- - src/gclue-config.c | 4 ++-- - 2 files changed, 6 insertions(+), 9 deletions(-) - -diff --git a/data/geoclue.conf.in b/data/geoclue.conf.in -index 650470d..db542dc 100644 ---- a/data/geoclue.conf.in -+++ b/data/geoclue.conf.in -@@ -45,10 +45,8 @@ enable=true - # Enable WiFi source - enable=true - --# 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 -+# URL to the WiFi geolocation service. If not set, defaults to beaconsDB -+#url=https://api.beacondb.net/v1/geolocate?key=geoclue_debian - - # To use the Google geolocation service instead of Mozilla's, uncomment this URL - # while changing YOUR_KEY to your Google API key. -@@ -67,9 +65,8 @@ enable=true - submit-data=false - - # 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 -+# beacondb -+#submission-url=https://api.beacondb.net/v2/geosubmit?key=geoclue_debian - - # A nickname to submit network data with. A nickname must be 2-32 characters long. - submission-nick=geoclue -diff --git a/src/gclue-config.c b/src/gclue-config.c -index 8c1cc3d..db1d755 100644 ---- a/src/gclue-config.c -+++ b/src/gclue-config.c -@@ -219,8 +219,8 @@ load_enable_source_config (GClueConfig *config, - return enable; - } - --#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_URL "https://api.beacondb.net/v1/geolocate?key=geoclue_debian" -+#define DEFAULT_WIFI_SUBMIT_URL "https://api.beacondb.net/v2/geosubmit?key=geoclue_debian" - #define DEFAULT_WIFI_SUBMIT_NICK "geoclue" - - static void diff --git a/debian/patches/series b/debian/patches/series index eb2c088463b859b734befa3a7b848da17c84c832..c354f72ecf513986c17a1a0c62b054ab50a865a0 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,3 +1,3 @@ -Add-support-for-building-with-libsoup3.patch -beacondb.patch -0003-PATCH-Mozilla-Include-SSID-for-geolocate-and-submiss.patch +0001-Mozilla-Include-SSID-for-geolocate-and-submission-re.patch +0002-Mozilla-replace-rather-than-append-User-Agent.patch +0003-Web-Source-Set-User-Agent-on-Soup-Session-Constructi.patch diff --git a/debian/rules b/debian/rules index 1371c8e99ced26ff42ee6ac6c99b906651d55a64..07689392af5e9d8a7015cd96554190bff26f5f18 100755 --- a/debian/rules +++ b/debian/rules @@ -19,9 +19,10 @@ endif override_dh_auto_configure: dh_auto_configure -- --libexecdir=/usr/libexec \ -Ddbus-srv-user=geoclue \ - -Dsystemd-system-unit-dir=/lib/systemd/system \ + -Dsystemd-system-unit-dir=/usr/lib/systemd/system \ ${BUILD_DOC} \ - ${MM_NM_FLAGS} + ${MM_NM_FLAGS} \ + -Ddefault-wifi-url=https://api.beacondb.net/v1/geolocate override_dh_auto_test: # testsuite fails on missing latitude (et.al) @@ -36,5 +37,9 @@ override_dh_auto_test: override_dh_missing: dh_missing --list-missing +override_dh_install-arch: + dh_install -a + dh_apparmor --profile-name=usr.libexec.geoclue -pgeoclue-2.0 + override_dh_installchangelogs: dh_installchangelogs NEWS diff --git a/debian/watch b/debian/watch index 45320336e2bcb1eafcd51610f0aa3b80fe512d20..a08ff9c10f27cf2c62fcb9795d8fcbda8af641ac 100644 --- a/debian/watch +++ b/debian/watch @@ -1,3 +1,4 @@ version=4 -https://gitlab.freedesktop.org/geoclue/geoclue/tags \ - /geoclue/geoclue/-/archive/(?:[\d\.]+)/geoclue-@ANY_VERSION@@ARCHIVE_EXT@ +opts="pagemangle=s!"name":"(v?@ANY_VERSION@)"!<a href="https://gitlab.freedesktop.org/geoclue/geoclue/-/archive/$1/geoclue-$1.tar.bz2">!g" \ +https://gitlab.freedesktop.org/api/v4/projects/geoclue%2Fgeoclue/repository/tags \ +https://gitlab.freedesktop.org/geoclue/geoclue/.*/geoclue-v?@ANY_VERSION@@ARCHIVE_EXT@ diff --git a/demo/agent.c b/demo/agent.c index 8e2a3ef47b073a92681f9ed4c7d6c6cd3f97b108..62d0e2fa9db2303eb55e34b5d82665f631ee2512 100644 --- a/demo/agent.c +++ b/demo/agent.c @@ -29,36 +29,47 @@ #include "gclue-service-agent.h" -/* Commandline options */ -static gboolean version; - static GOptionEntry entries[] = { { "version", 0, 0, G_OPTION_ARG_NONE, - &version, + NULL, N_("Display version number"), NULL }, - { NULL } + G_OPTION_ENTRY_NULL }; GClueServiceAgent *agent = NULL; +static gint +handle_local_options_cb (GApplication *app, + GVariantDict *options, + gpointer user_data) +{ + gboolean version; + + if (g_variant_dict_lookup (options, "version", "b", &version)) { + g_print ("%s\n", PACKAGE_VERSION); + return EXIT_SUCCESS; + } + + return -1; +} + static void on_get_bus_ready (GObject *source_object, GAsyncResult *res, gpointer user_data) { - GError *error = NULL; + g_autoptr(GError) error = NULL; GDBusConnection *connection; connection = g_bus_get_finish (res, &error); if (connection == NULL) { g_critical ("Failed to get connection to system bus: %s", error->message); - g_error_free (error); exit (-2); } @@ -68,12 +79,32 @@ on_get_bus_ready (GObject *source_object, #define ABS_PATH ABS_SRCDIR "/agent" +static void +activate_cb (GApplication *app, + gpointer user_data) +{ + g_bus_get (G_BUS_TYPE_SYSTEM, + NULL, + on_get_bus_ready, + NULL); + + g_application_hold (app); +} + +static void +is_registered_cb (GApplication *app, + GParamSpec *pspec, + gpointer user_data) +{ + if (g_application_get_is_registered (app) && g_application_get_is_remote (app)) + g_message ("Another instance of GeoClue DemoAgent is running."); +} + int main (int argc, char **argv) { - GMainLoop *main_loop; - GError *error = NULL; - GOptionContext *context; + g_autoptr (GApplication) app = NULL; + int status = 0; setlocale (LC_ALL, ""); @@ -84,29 +115,20 @@ main (int argc, char **argv) notify_init (_("GeoClue")); - context = g_option_context_new ("- Geoclue Agent service"); - g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); - if (!g_option_context_parse (context, &argc, &argv, &error)) { - g_critical ("option parsing failed: %s\n", error->message); - exit (-1); - } + app = g_application_new ("org.freedesktop.GeoClue2.DemoAgent", + G_APPLICATION_DEFAULT_FLAGS); - if (version) { - g_print ("%s\n", PACKAGE_VERSION); - exit (0); - } + g_application_add_main_option_entries (app, entries); + g_application_set_option_context_parameter_string (app, "- Geoclue Agent service"); - g_bus_get (G_BUS_TYPE_SYSTEM, - NULL, - on_get_bus_ready, - NULL); + g_signal_connect (app, "activate", G_CALLBACK (activate_cb), NULL); + g_signal_connect (app, "handle-local-options", G_CALLBACK (handle_local_options_cb), NULL); + g_signal_connect (app, "notify::is-registered", G_CALLBACK (is_registered_cb), NULL); - main_loop = g_main_loop_new (NULL, FALSE); - g_main_loop_run (main_loop); + status = g_application_run (G_APPLICATION (app), argc, argv); if (agent != NULL) g_object_unref (agent); - g_main_loop_unref (main_loop); - return 0; + return status; } diff --git a/demo/gclue-service-agent.c b/demo/gclue-service-agent.c index ac967954604854c08937abd42d928f667fe4b44e..8758721a1559ec8e2b3fd741c25baada53b15201 100644 --- a/demo/gclue-service-agent.c +++ b/demo/gclue-service-agent.c @@ -192,16 +192,15 @@ on_manager_proxy_ready (GObject *source_object, gpointer user_data) { GClueServiceAgent *agent; - GError *error = NULL; - agent = GCLUE_SERVICE_AGENT (user_data); + g_autoptr(GError) error = NULL; + agent = GCLUE_SERVICE_AGENT (user_data); agent->priv->manager_proxy = g_dbus_proxy_new_for_bus_finish (res, &error); if (agent->priv->manager_proxy == NULL) { g_critical ("Failed to create proxy to %s: %s", MANAGER_PATH, error->message); - g_error_free (error); return; } @@ -335,10 +334,10 @@ gclue_service_agent_handle_authorize_app (GClueAgent *agent, NotifyNotification *notification; NotificationData *data; GError *error = NULL; - char *desktop_file; + g_autofree char *desktop_file = NULL; GDesktopAppInfo *app_info; - char *msg; - const char *reason; + g_autofree char *msg = NULL; + g_autofree char *reason = NULL; desktop_file = g_strjoin (".", desktop_id, "desktop", NULL); app_info = g_desktop_app_info_new (desktop_file); @@ -351,18 +350,17 @@ gclue_service_agent_handle_authorize_app (GClueAgent *agent, return TRUE; } - g_free (desktop_file); msg = g_strdup_printf (_("Allow '%s' to access your location information?"), g_app_info_get_display_name (G_APP_INFO (app_info))); reason = g_desktop_app_info_get_string (app_info, "X-Geoclue-Reason"); if (reason != NULL) { - char *tmp = msg; - msg = g_strdup_printf ("%s\n\n%s", msg, reason); - g_free (tmp); + char *tmp = g_strdup_printf ("%s\n\n%s", msg, reason); + + g_clear_pointer (&msg, g_free); + msg = tmp; } notification = notify_notification_new (_("Geolocation"), msg, "dialog-question"); - g_free (msg); data = g_slice_new0 (NotificationData); data->invocation = invocation; diff --git a/demo/where-am-i.c b/demo/where-am-i.c index aa873a458c038835af1794e24f5fbe239d78035c..8324058b5afff7ac1771bb8ee2e0fc6b2f5f3e5b 100644 --- a/demo/where-am-i.c +++ b/demo/where-am-i.c @@ -109,9 +109,9 @@ print_location (GClueSimple *simple) timestamp = gclue_location_get_timestamp (location); if (timestamp) { - GDateTime *date_time; + g_autoptr(GDateTime) date_time = NULL; guint64 sec, usec; - gchar *str; + g_autofree gchar *str = NULL; g_variant_get (timestamp, "(tt)", &sec, &usec); @@ -120,10 +120,8 @@ print_location (GClueSimple *simple) str = g_date_time_format (date_time, "%c (%s seconds since the Epoch)"); - g_date_time_unref (date_time); g_print ("Timestamp: %s\n", str); - g_free (str); } } diff --git a/interface/fi.w1.wpa_supplicant1.xml b/interface/fi.w1.wpa_supplicant1.xml index a559c891bf573a15d6f57d07f21248da6765b0f4..5a75c31839adde56dbeea6604d4a226c84386bd5 100644 --- a/interface/fi.w1.wpa_supplicant1.xml +++ b/interface/fi.w1.wpa_supplicant1.xml @@ -2,43 +2,39 @@ <!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> <node> - -<interface name="fi.w1.wpa_supplicant1"> - -<signal name="InterfaceAdded"> -<arg name="path" type="o"/> -<arg name="properties" type="a{sv}"/> -</signal> -<signal name="InterfaceRemoved"> -<arg name="path" type="o"/> -</signal> -<property name="Interfaces" type="ao" access="read"/> -</interface> - -<interface name="fi.w1.wpa_supplicant1.Interface"> -<method name="Scan"> - <arg name="args" type="a{sv}" direction="in"/> -</method> -<signal name="BSSAdded"> -<arg name="path" type="o"/> -<arg name="properties" type="a{sv}"/> -</signal> -<signal name="BSSRemoved"> -<arg name="path" type="o"/> -</signal> -<signal name="ScanDone"> -<arg name="success" type="b"/> -</signal> -<property name="State" type="s" access="read"/> -<property name="Ifname" type="s" access="read"/> -<property name="BSSs" type="ao" access="read"/> -</interface> - -<interface name="fi.w1.wpa_supplicant1.BSS"> -<property name="SSID" type="ay" access="read"/> -<property name="BSSID" type="ay" access="read"/> -<property name="Signal" type="n" access="read"/> -<property name="Frequency" type="q" access="read"/> -</interface> - + <interface name="fi.w1.wpa_supplicant1"> + <signal name="InterfaceAdded"> + <arg name="path" type="o"/> + <arg name="properties" type="a{sv}"/> + </signal> + <signal name="InterfaceRemoved"> + <arg name="path" type="o"/> + </signal> + <property access="read" name="Interfaces" type="ao"/> + </interface> + <interface name="fi.w1.wpa_supplicant1.Interface"> + <method name="Scan"> + <arg direction="in" name="args" type="a{sv}"/> + </method> + <signal name="BSSAdded"> + <arg name="path" type="o"/> + <arg name="properties" type="a{sv}"/> + </signal> + <signal name="BSSRemoved"> + <arg name="path" type="o"/> + </signal> + <signal name="ScanDone"> + <arg name="success" type="b"/> + </signal> + <property access="read" name="State" type="s"/> + <property access="read" name="Ifname" type="s"/> + <property access="read" name="BSSs" type="ao"/> + </interface> + <interface name="fi.w1.wpa_supplicant1.BSS"> + <property access="read" name="SSID" type="ay"/> + <property access="read" name="BSSID" type="ay"/> + <property access="read" name="Signal" type="n"/> + <property access="read" name="Frequency" type="q"/> + <property access="read" name="Age" type="u"/> + </interface> </node> diff --git a/libgeoclue/gclue-helpers.c b/libgeoclue/gclue-helpers.c index bbb38097ced127b6f615493dc54b65eba6a4e6a5..de92b3891958974e16e3c5762851fddf9d95ef01 100644 --- a/libgeoclue/gclue-helpers.c +++ b/libgeoclue/gclue-helpers.c @@ -161,7 +161,7 @@ on_get_client_ready (GObject *source_object, { GTask *task = G_TASK (user_data); GClueManager *manager = GCLUE_MANAGER (source_object); - char *client_path; + g_autofree char *client_path = NULL; GError *error = NULL; if (!gclue_manager_call_get_client_finish (manager, @@ -182,7 +182,6 @@ on_get_client_ready (GObject *source_object, g_task_get_cancellable (task), on_client_proxy_ready, task); - g_free (client_path); g_object_unref (manager); } diff --git a/libgeoclue/gclue-simple.c b/libgeoclue/gclue-simple.c index 1284e4d484a40e6451a4e60e49b57f6ed5458e43..4c2f299c16d79beaa8709a9f14dafcac608c8f6b 100644 --- a/libgeoclue/gclue-simple.c +++ b/libgeoclue/gclue-simple.c @@ -63,14 +63,11 @@ struct _GClueSimplePrivate GClueClient *client; GClueLocation *location; - gulong update_id; - GTask *task; GCancellable *cancellable; char *sender; XdpLocation *portal; - gulong location_updated_id; guint response_id; char *session_id; }; @@ -104,16 +101,11 @@ gclue_simple_finalize (GObject *object) GClueSimplePrivate *priv = GCLUE_SIMPLE (object)->priv; g_clear_pointer (&priv->desktop_id, g_free); - if (priv->update_id != 0) { - g_signal_handler_disconnect (priv->client, priv->update_id); - priv->update_id = 0; - } if (priv->cancellable != NULL) g_cancellable_cancel (priv->cancellable); g_clear_object (&priv->cancellable); g_clear_object (&priv->client); g_clear_object (&priv->location); - g_clear_object (&priv->task); clear_portal (GCLUE_SIMPLE (object)); @@ -296,14 +288,13 @@ on_location_proxy_ready (GObject *source_object, gpointer user_data) { GClueSimplePrivate *priv = GCLUE_SIMPLE (user_data)->priv; - GClueLocation *location; - GError *error = NULL; + g_autoptr (GClueLocation) location = NULL; + g_autoptr (GError) error = NULL; location = gclue_location_proxy_new_for_bus_finish (res, &error); if (error != NULL) { if (priv->task != NULL) { - g_task_return_error (priv->task, error); - g_clear_object (&priv->task); + g_task_return_error (priv->task, g_steal_pointer (&error)); } else { g_warning ("Failed to create location proxy: %s", error->message); @@ -311,12 +302,10 @@ on_location_proxy_ready (GObject *source_object, return; } - g_clear_object (&priv->location); - priv->location = location; + g_set_object (&priv->location, location); if (priv->task != NULL) { g_task_return_boolean (priv->task, TRUE); - g_clear_object (&priv->task); } else { g_object_notify (G_OBJECT (user_data), "location"); } @@ -342,27 +331,46 @@ on_location_updated (GClueClient *client, user_data); } +static void +async_init_return_error_when_cancelled (GTask *task) +{ + GCancellable *cancellable = g_task_get_cancellable (task); + + if (cancellable == NULL) + return; + + /* Sub-tasks were returning when the async init is cancelled. With no + * further async call, and the top level task only checking cancellation + * when g_task_return_* is called, we need to listen to this signal. + */ + g_signal_connect_object (cancellable, + "cancelled", + G_CALLBACK (g_task_return_error_if_cancelled), + task, + G_CONNECT_SWAPPED); +} + static void on_client_started (GObject *source_object, GAsyncResult *res, gpointer user_data) { - GTask *task = G_TASK (user_data); + g_autoptr (GTask) task = G_TASK (user_data); GClueClient *client = GCLUE_CLIENT (source_object); GClueSimple *simple; const char *location; - GError *error = NULL; + g_autoptr (GError) error = NULL; simple = g_task_get_source_object (task); gclue_client_call_start_finish (client, res, &error); if (error != NULL) { - g_task_return_error (task, error); - g_clear_object (&simple->priv->task); - + g_task_return_error (task, g_steal_pointer (&error)); return; } + async_init_return_error_when_cancelled (task); + location = gclue_client_get_location (client); on_location_updated (client, NULL, location, simple); @@ -373,16 +381,14 @@ on_client_created (GObject *source_object, GAsyncResult *res, gpointer user_data) { - GTask *task = G_TASK (user_data); + g_autoptr (GTask) task = G_TASK (user_data); GClueSimple *simple = g_task_get_source_object (task); GClueSimplePrivate *priv = simple->priv; - GError *error = NULL; + g_autoptr (GError) error = NULL; priv->client = gclue_client_proxy_create_full_finish (res, &error); if (error != NULL) { - g_task_return_error (task, error); - g_clear_object (&priv->task); - + g_task_return_error (task, g_steal_pointer (&error)); return; } if (priv->distance_threshold != 0) { @@ -394,17 +400,19 @@ on_client_created (GObject *source_object, (priv->client, priv->time_threshold); } - priv->task = task; - priv->update_id = - g_signal_connect (priv->client, - "location-updated", - G_CALLBACK (on_location_updated), - simple); + priv->task = g_steal_pointer (&task); + g_object_add_weak_pointer (G_OBJECT (priv->task), (gpointer*) &priv->task); + + g_signal_connect_object (priv->client, + "location-updated", + G_CALLBACK (on_location_updated), + simple, + G_CONNECT_DEFAULT); gclue_client_call_start (priv->client, - g_task_get_cancellable (task), + g_task_get_cancellable (priv->task), on_client_started, - task); + priv->task); } /* We use the portal if we are inside a flatpak, @@ -458,7 +466,6 @@ clear_portal (GClueSimple *simple) } 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); } @@ -479,7 +486,7 @@ on_portal_location_updated (XdpLocation *portal, double heading; const char *description; GVariant *timestamp; - GClueLocation *location = gclue_location_skeleton_new (); + g_autoptr (GClueLocation) location = gclue_location_skeleton_new (); g_variant_lookup (data, "Latitude", "d", &latitude); g_variant_lookup (data, "Longitude", "d", &longitude); @@ -503,13 +510,10 @@ on_portal_location_updated (XdpLocation *portal, 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 @@ -532,9 +536,7 @@ on_started (GDBusConnection *bus, 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); } } @@ -543,15 +545,18 @@ on_portal_started_finish (GObject *source_object, GAsyncResult *res, gpointer user_data) { - GTask *task = G_TASK (user_data); + g_autoptr (GTask) task = G_TASK (user_data); GClueSimple *simple = g_task_get_source_object (task); GClueSimplePrivate *priv = simple->priv; - GError *error = NULL; + g_autoptr (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); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_task_return_error (task, g_steal_pointer (&error)); + else + g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "Start failed"); + } else { + async_init_return_error_when_cancelled (task); } } @@ -560,31 +565,28 @@ on_session_created (GObject *source, GAsyncResult *result, gpointer user_data) { - GTask *task = G_TASK (user_data); + g_autoptr (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_autoptr (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); + g_task_return_error (task, g_steal_pointer (&error)); 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; + priv->task = g_steal_pointer (&task); + g_object_add_weak_pointer (G_OBJECT (priv->task), (gpointer*) &priv->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); @@ -605,9 +607,9 @@ on_session_created (GObject *source, priv->session_id, "", /* FIXME parent window */ g_variant_builder_end (&options), - NULL, + g_task_get_cancellable (priv->task), on_portal_started_finish, - task); + priv->task); } static int @@ -638,29 +640,30 @@ on_portal_created (GObject *source_object, GAsyncResult *res, gpointer user_data) { - GTask *task = G_TASK (user_data); + g_autoptr (GTask) task = G_TASK (user_data); GClueSimple *simple = g_task_get_source_object (task); GClueSimplePrivate *priv = simple->priv; GDBusConnection *bus; - GError *error = NULL; + g_autoptr (GError) error = NULL; int i; g_autofree char *session_token = NULL; GVariantBuilder options; + GCancellable *cancellable; priv->portal = xdp_location_proxy_new_for_bus_finish (res, &error); if (error != NULL) { - g_task_return_error (task, error); - g_object_unref (task); + g_task_return_error (task, g_steal_pointer (&error)); 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); + g_signal_connect_object (priv->portal, + "location-updated", + G_CALLBACK (on_portal_location_updated), + simple, + G_CONNECT_DEFAULT); priv->sender = g_strdup (g_dbus_connection_get_unique_name (bus) + 1); for (i = 0; priv->sender[i]; i++) @@ -678,9 +681,12 @@ on_portal_created (GObject *source_object, 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))); + cancellable = g_task_get_cancellable (task); xdp_location_call_create_session (priv->portal, g_variant_builder_end (&options), - NULL, on_session_created, task); + cancellable, + on_session_created, + g_steal_pointer (&task)); } static void @@ -702,14 +708,14 @@ gclue_simple_init_async (GAsyncInitable *initable, PORTAL_OBJECT_PATH, cancellable, on_portal_created, - task); + g_object_ref (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); + g_object_ref (task)); } } @@ -718,7 +724,9 @@ gclue_simple_init_finish (GAsyncInitable *initable, GAsyncResult *result, GError **error) { - return g_task_propagate_boolean (G_TASK (result), error); + g_autoptr (GTask) task = G_TASK (result); + + return g_task_propagate_boolean (task, error); } static void @@ -782,13 +790,13 @@ gclue_simple_new_finish (GAsyncResult *result, GError **error) { GObject *object; - GObject *source_object; + g_autoptr (GObject) source_object = NULL; source_object = g_async_result_get_source_object (result); object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), result, error); - g_object_unref (source_object); + if (object != NULL) return GCLUE_SIMPLE (object); else @@ -857,7 +865,7 @@ gclue_simple_new_sync (const char *desktop_id, { GClueSimple *simple; GMainLoop *main_loop; - GTask *task; + g_autoptr (GTask) task = NULL; task = g_task_new (NULL, cancellable, NULL, NULL); main_loop = g_main_loop_new (NULL, FALSE); @@ -875,8 +883,6 @@ gclue_simple_new_sync (const char *desktop_id, simple = g_task_propagate_pointer (task, error); - g_object_unref (task); - return simple; } @@ -941,7 +947,7 @@ gclue_simple_new_with_thresholds_sync (const char *desktop_id, { GClueSimple *simple; GMainLoop *main_loop; - GTask *task; + g_autoptr (GTask) task = NULL; task = g_task_new (NULL, cancellable, NULL, NULL); main_loop = g_main_loop_new (NULL, FALSE); @@ -961,8 +967,6 @@ gclue_simple_new_with_thresholds_sync (const char *desktop_id, simple = g_task_propagate_pointer (task, error); - g_object_unref (task); - return simple; } @@ -974,7 +978,7 @@ gclue_simple_new_with_thresholds_sync (const char *desktop_id, * 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. + * Returns: (nullable) (transfer none) (type GClueClientProxy): The client object. */ GClueClient * gclue_simple_get_client (GClueSimple *simple) @@ -990,7 +994,7 @@ gclue_simple_get_client (GClueSimple *simple) * * Gets the current location. * - * Returns: (transfer none) (type GClueLocation): The last known location + * Returns: (nullable) (transfer none) (type GClueLocation): The last known location * as #GClueLocation. */ GClueLocation * diff --git a/meson.build b/meson.build index 8aa5c3139a4fd8e3100e4700be5316c7ae89475f..c561572419c94d46049b92195434672a04196fbf 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('geoclue', 'c', version: '2.6.0', meson_version : '>= 0.47.2') +project('geoclue', 'c', version: '2.7.2', meson_version : '>= 0.60.0') gclue_version = meson.project_version() ver_arr = gclue_version.split('.') @@ -27,10 +27,11 @@ conf.set_quoted('PACKAGE_TARNAME', 'geoclue') conf.set_quoted('PACKAGE_STRING', 'geoclue ' + gclue_version) conf.set_quoted('PACKAGE_URL', 'https://gitlab.freedesktop.org/geoclue/geoclue/wikis/home') conf.set_quoted('PACKAGE_BUGREPORT', 'https://gitlab.freedesktop.org/geoclue/geoclue/issues/new') -conf.set_quoted('TEST_SRCDIR', meson.source_root() + '/data/') +conf.set_quoted('TEST_SRCDIR', meson.project_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.set_quoted('DEFAULT_WIFI_URL', get_option('default-wifi-url')) +conf.set_quoted('DEFAULT_WIFI_SUBMIT_URL', get_option('default-wifi-submit-url')) 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')) @@ -43,9 +44,9 @@ configinc = include_directories('.') gnome = import('gnome') cc = meson.get_compiler('c') -base_deps = [ dependency('glib-2.0', version: '>= 2.44.0'), - dependency('gio-2.0', version: '>= 2.44.0'), - dependency('gio-unix-2.0', version: '>= 2.44.0') ] +base_deps = [ dependency('glib-2.0', version: '>= 2.74.0'), + dependency('gio-2.0', version: '>= 2.74.0'), + dependency('gio-unix-2.0', version: '>= 2.74.0') ] libm = cc.find_library('m', required: false) if libm.found() base_deps += [ libm ] diff --git a/meson_options.txt b/meson_options.txt index 5b8c42d2f6b9dcdf38814e69f30e094876835b65..4d55a63c64be64775db0e3a1ca2d93de6d647e1e 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -40,6 +40,9 @@ 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') +option('default-wifi-url', + type: 'string', + description: 'Default URL for Ichnaea-compatible WiFi geolocation') +option('default-wifi-submit-url', + type: 'string', + description: 'Default URL for Ichnaea-compatible WiFi location submission') diff --git a/src/gclue-3g-tower.h b/src/gclue-3g-tower.h index 43332454caba55b46f001c9d112c0f2b40ecbf85..f6b69ff4a20f503071ce477cfb17b5b1b9dc3732 100644 --- a/src/gclue-3g-tower.h +++ b/src/gclue-3g-tower.h @@ -26,9 +26,12 @@ G_BEGIN_DECLS typedef enum { GCLUE_TOWER_TEC_UNKNOWN = 0, - GCLUE_TOWER_TEC_3G = 1, - GCLUE_TOWER_TEC_4G = 2, + GCLUE_TOWER_TEC_2G = 1, + GCLUE_TOWER_TEC_3G = 2, + GCLUE_TOWER_TEC_4G = 3, + GCLUE_TOWER_TEC_NO_FIX = 99, } GClueTowerTec; +# define GCLUE_TOWER_TEC_MAX_VALID GCLUE_TOWER_TEC_4G typedef struct _GClue3GTower GClue3GTower; diff --git a/src/gclue-3g.c b/src/gclue-3g.c index bbcb791e9dcf40a031c0870a6b4f7db44288069f..225b70681b74595b9e4cf95a26bc3dc72be915f5 100644 --- a/src/gclue-3g.c +++ b/src/gclue-3g.c @@ -24,9 +24,11 @@ #include <libsoup/soup.h> #include <string.h> #include "gclue-3g.h" +#include "gclue-3g-tower.h" #include "gclue-modem-manager.h" #include "gclue-location.h" #include "gclue-mozilla.h" +#include "gclue-wifi.h" /** * SECTION:gclue-3g @@ -35,14 +37,24 @@ * Contains functions to get the geolocation based on 3GPP cell towers. **/ +/* Should be slightly less than MAX_LOCATION_AGE in gclue-locator.c, so we don't + * get replaced by a less accurate WiFi location while still connected to a tower. + * Technically, this can only happen on the NEIGHBORHOOD accuracy level (since at + * this level WiFi does scrambling), but it won't hurt on higher ones, too. + * In seconds. + */ +#define LOCATION_3GPP_TIMEOUT (25 * 60) + +static unsigned int gclue_3g_running; + struct _GClue3GPrivate { + GClueMozilla *mozilla; GClueModem *modem; GCancellable *cancellable; gulong threeg_notify_id; - - GClue3GTower *tower; + guint location_3gpp_timeout_id; }; G_DEFINE_TYPE_WITH_CODE (GClue3G, @@ -56,6 +68,7 @@ static GClueLocationSourceStopResult gclue_3g_stop (GClueLocationSource *source); static SoupMessage * gclue_3g_create_query (GClueWebSource *web, + const char **query_data_description, GError **error); static SoupMessage * gclue_3g_create_submit_query (GClueWebSource *web, @@ -64,24 +77,21 @@ gclue_3g_create_submit_query (GClueWebSource *web, static GClueAccuracyLevel gclue_3g_get_available_accuracy_level (GClueWebSource *web, gboolean available); -static GClueLocation * -gclue_3g_parse_response (GClueWebSource *web, - const char *xml, - GError **error); static void on_3g_enabled (GObject *source_object, GAsyncResult *result, gpointer user_data) { - GClue3G *source = GCLUE_3G (user_data); - GError *error = NULL; + g_autoptr(GError) error = NULL; - if (!gclue_modem_enable_3g_finish (source->priv->modem, + if (!gclue_modem_enable_3g_finish (GCLUE_MODEM (source_object), result, &error)) { - g_warning ("Failed to enable 3GPP: %s", error->message); - g_error_free (error); + if (error && !g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { + g_warning ("Failed to enable 3GPP: %s", error->message); + } } } @@ -92,23 +102,30 @@ on_is_3g_available_notify (GObject *gobject, { GClue3G *source = GCLUE_3G (user_data); GClue3GPrivate *priv = source->priv; + gboolean available_3g; + + available_3g = gclue_modem_get_is_3g_available (priv->modem); + g_debug ("3G available notify %d", (int)available_3g); gclue_web_source_refresh (GCLUE_WEB_SOURCE (source)); if (gclue_location_source_get_active (GCLUE_LOCATION_SOURCE (source)) && - gclue_modem_get_is_3g_available (priv->modem)) + available_3g) gclue_modem_enable_3g (priv->modem, priv->cancellable, on_3g_enabled, source); } -static GClueLocation * -gclue_3g_parse_response (GClueWebSource *web, - const char *content, - GError **error) +static void cancel_location_3gpp_timeout (GClue3G *g3g) { - return gclue_mozilla_parse_response (content, error); + GClue3GPrivate *priv = g3g->priv; + + if (!priv->location_3gpp_timeout_id) + return; + + g_source_remove (priv->location_3gpp_timeout_id); + priv->location_3gpp_timeout_id = 0; } static void @@ -125,7 +142,10 @@ gclue_3g_finalize (GObject *g3g) priv->threeg_notify_id); priv->threeg_notify_id = 0; + cancel_location_3gpp_timeout (source); + g_clear_object (&priv->modem); + g_clear_object (&priv->mozilla); g_clear_object (&priv->cancellable); } @@ -142,7 +162,6 @@ gclue_3g_class_init (GClue3GClass *klass) source_class->stop = gclue_3g_stop; web_class->create_query = gclue_3g_create_query; web_class->create_submit_query = gclue_3g_create_submit_query; - web_class->parse_response = gclue_3g_parse_response; web_class->get_available_accuracy_level = gclue_3g_get_available_accuracy_level; } @@ -151,18 +170,26 @@ static void gclue_3g_init (GClue3G *source) { GClue3GPrivate *priv; + GClueWebSource *web_source = GCLUE_WEB_SOURCE (source); source->priv = gclue_3g_get_instance_private (source); priv = source->priv; priv->cancellable = g_cancellable_new (); + priv->mozilla = gclue_mozilla_get_singleton (); + gclue_web_source_set_locate_url (web_source, + gclue_mozilla_get_locate_url (priv->mozilla)); + gclue_web_source_set_submit_url (web_source, + gclue_mozilla_get_submit_url (priv->mozilla)); + priv->modem = gclue_modem_manager_get_singleton (); priv->threeg_notify_id = g_signal_connect (priv->modem, "notify::is-3g-available", G_CALLBACK (on_is_3g_available_notify), source); + priv->location_3gpp_timeout_id = 0; } static void @@ -177,36 +204,53 @@ on_3g_destroyed (gpointer data, /** * gclue_3g_new: * - * Get the #GClue3G singleton. + * Get the #GClue3G singleton, for the specified max accuracy level @level. * * Returns: (transfer full): a new ref to #GClue3G. Use g_object_unref() * when done. **/ GClue3G * -gclue_3g_get_singleton (void) +gclue_3g_get_singleton (GClueAccuracyLevel level) { - static GClue3G *source = NULL; - - if (source == NULL) { - source = g_object_new (GCLUE_TYPE_3G, - "compute-movement", FALSE, - NULL); - g_object_weak_ref (G_OBJECT (source), + static GClue3G *source[] = { NULL, NULL }; + int i; + + g_return_val_if_fail (level >= GCLUE_ACCURACY_LEVEL_CITY, NULL); + + i = gclue_wifi_should_skip_bsss (level) ? 0 : 1; + if (source[i] == NULL) { + source[i] = g_object_new (GCLUE_TYPE_3G, + "accuracy-level", level, + "compute-movement", FALSE, + NULL); + g_object_weak_ref (G_OBJECT (source[i]), on_3g_destroyed, - &source); + &source[i]); } else - g_object_ref (source); + g_object_ref (source[i]); + + return source[i]; +} + +static gboolean +g3g_should_skip_bsss (GClue3G *g3g) +{ + GClueAccuracyLevel level; - return source; + g_object_get (G_OBJECT (g3g), "accuracy-level", &level, NULL); + return gclue_wifi_should_skip_bsss (level); } static SoupMessage * gclue_3g_create_query (GClueWebSource *web, + const char **query_data_description, GError **error) { - GClue3GPrivate *priv = GCLUE_3G (web)->priv; + GClue3G *g3g = GCLUE_3G (web); + GClue3GPrivate *priv = g3g->priv; + gboolean skip_bss; - if (priv->tower == NULL) { + if (!gclue_mozilla_has_tower (priv->mozilla)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, @@ -214,7 +258,13 @@ gclue_3g_create_query (GClueWebSource *web, return NULL; /* Not initialized yet */ } - return gclue_mozilla_create_query (NULL, priv->tower, error); + skip_bss = g3g_should_skip_bsss (g3g); + if (skip_bss) { + g_debug ("Will skip BSSs in query due to our accuracy level"); + } + + return gclue_mozilla_create_query (priv->mozilla, FALSE, skip_bss, + query_data_description, error); } static SoupMessage * @@ -224,7 +274,7 @@ gclue_3g_create_submit_query (GClueWebSource *web, { GClue3GPrivate *priv = GCLUE_3G (web)->priv; - if (priv->tower == NULL) { + if (!gclue_mozilla_has_tower (priv->mozilla)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, @@ -232,9 +282,8 @@ gclue_3g_create_submit_query (GClueWebSource *web, return NULL; /* Not initialized yet */ } - return gclue_mozilla_create_submit_query (location, - NULL, - priv->tower, + return gclue_mozilla_create_submit_query (priv->mozilla, + location, error); } @@ -249,6 +298,41 @@ gclue_3g_get_available_accuracy_level (GClueWebSource *web, return GCLUE_ACCURACY_LEVEL_NONE; } +gboolean gclue_3g_should_skip_tower (GClueAccuracyLevel level) +{ + return level < GCLUE_ACCURACY_LEVEL_NEIGHBORHOOD; +} + +static gboolean +on_location_3gpp_timeout (gpointer user_data) +{ + GClue3G *g3g = GCLUE_3G (user_data); + GClue3GPrivate *priv = g3g->priv; + + if (!gclue_mozilla_has_tower (priv->mozilla)) { + g_debug ("3GPP location timeout, but no tower"); + priv->location_3gpp_timeout_id = 0; + return G_SOURCE_REMOVE; + } + + g_debug ("3GPP location timeout, re-sending existing location"); + gclue_web_source_refresh (GCLUE_WEB_SOURCE (g3g)); + + return G_SOURCE_CONTINUE; +} + +static void set_location_3gpp_timeout (GClue3G *g3g) +{ + GClue3GPrivate *priv = g3g->priv; + + g_debug ("Scheduling new 3GPP location timeout"); + + cancel_location_3gpp_timeout (g3g); + priv->location_3gpp_timeout_id = g_timeout_add_seconds (LOCATION_3GPP_TIMEOUT, + on_location_3gpp_timeout, + g3g); +} + static void on_fix_3g (GClueModem *modem, const gchar *opc, @@ -257,15 +341,26 @@ on_fix_3g (GClueModem *modem, GClueTowerTec tec, gpointer user_data) { - GClue3GPrivate *priv = GCLUE_3G (user_data)->priv; - - if (priv->tower == NULL) - priv->tower = g_slice_new0 (GClue3GTower); - 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; + GClue3G *g3g = GCLUE_3G (user_data); + GClue3GPrivate *priv = g3g->priv; + + g_debug ("3GPP %s fix available", + tec == GCLUE_TOWER_TEC_NO_FIX ? "no" : "new"); + + if (tec != GCLUE_TOWER_TEC_NO_FIX) { + GClue3GTower tower; + + g_strlcpy (tower.opc, opc, + GCLUE_3G_TOWER_OPERATOR_CODE_STR_LEN + 1); + tower.lac = lac; + tower.cell_id = cell_id; + tower.tec = tec; + set_location_3gpp_timeout (g3g); + gclue_mozilla_set_tower (priv->mozilla, &tower); + } else { + cancel_location_3gpp_timeout (g3g); + gclue_mozilla_set_tower (priv->mozilla, NULL); + } gclue_web_source_refresh (GCLUE_WEB_SOURCE (user_data)); } @@ -286,16 +381,17 @@ gclue_3g_start (GClueLocationSource *source) if (base_result != GCLUE_LOCATION_SOURCE_START_RESULT_OK) return base_result; - if (priv->tower != NULL) { - g_slice_free (GClue3GTower, priv->tower); - priv->tower = NULL; + if (gclue_3g_running == 0) { + g_debug ("First 3GPP source starting up"); } + gclue_3g_running++; g_signal_connect (priv->modem, "fix-3g", G_CALLBACK (on_fix_3g), source); + /* Emits fix-3g signal even if the location hasn't actually changed to prime us */ if (gclue_modem_get_is_3g_available (priv->modem)) gclue_modem_enable_3g (priv->modem, priv->cancellable, @@ -307,30 +403,42 @@ gclue_3g_start (GClueLocationSource *source) static GClueLocationSourceStopResult gclue_3g_stop (GClueLocationSource *source) { - GClue3GPrivate *priv = GCLUE_3G (source)->priv; + GClue3G *g3g = GCLUE_3G (source); + GClue3GPrivate *priv = g3g->priv; GClueLocationSourceClass *base_class; - GError *error = NULL; + g_autoptr(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); base_result = base_class->stop (source); - if (base_result == GCLUE_LOCATION_SOURCE_STOP_RESULT_STILL_USED) + if (base_result != GCLUE_LOCATION_SOURCE_STOP_RESULT_OK) return base_result; g_signal_handlers_disconnect_by_func (G_OBJECT (priv->modem), G_CALLBACK (on_fix_3g), source); + cancel_location_3gpp_timeout (g3g); + + g_assert (gclue_3g_running > 0); + gclue_3g_running--; + if (gclue_3g_running > 0) { + return base_result; + } + + g_debug ("Last 3GPP source stopping, disabling location gathering and invalidating existing tower"); + if (gclue_modem_get_is_3g_available (priv->modem)) if (!gclue_modem_disable_3g (priv->modem, priv->cancellable, &error)) { g_warning ("Failed to disable 3GPP: %s", error->message); - g_error_free (error); } + gclue_mozilla_set_tower (priv->mozilla, NULL); + return base_result; } diff --git a/src/gclue-3g.h b/src/gclue-3g.h index bec215b57394e0cc3ae9a581f303888da141ba73..0eacf88652ce2b2947a33090f95a9e4ae3ad6891 100644 --- a/src/gclue-3g.h +++ b/src/gclue-3g.h @@ -22,6 +22,8 @@ #ifndef GCLUE_3G_H #define GCLUE_3G_H +#include "config.h" + #include <glib.h> #include <gio/gio.h> #include "gclue-web-source.h" @@ -62,7 +64,13 @@ struct _GClue3GClass { GClueWebSourceClass parent_class; }; -GClue3G * gclue_3g_get_singleton (void); +GClue3G * gclue_3g_get_singleton (GClueAccuracyLevel level); + +#if GCLUE_USE_3G_SOURCE +gboolean gclue_3g_should_skip_tower (GClueAccuracyLevel level); +#else +static inline gboolean gclue_3g_should_skip_tower (GClueAccuracyLevel level) { return TRUE; } +#endif G_END_DECLS diff --git a/src/gclue-cdma.c b/src/gclue-cdma.c index a35c07ad1efb0c9826f03e8419600583696440a8..75bd5a0a087c72c14b2914d1f0fae4c19f3993c8 100644 --- a/src/gclue-cdma.c +++ b/src/gclue-cdma.c @@ -79,14 +79,15 @@ on_cdma_enabled (GObject *source_object, GAsyncResult *result, gpointer user_data) { - GClueCDMA *source = GCLUE_CDMA (user_data); - GError *error = NULL; + g_autoptr(GError) error = NULL; - if (!gclue_modem_enable_cdma_finish (source->priv->modem, + if (!gclue_modem_enable_cdma_finish (GCLUE_MODEM (source_object), result, &error)) { - g_warning ("Failed to enable CDMA: %s", error->message); - g_error_free (error); + if (error && !g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { + g_warning ("Failed to enable CDMA: %s", error->message); + } } } @@ -199,7 +200,8 @@ on_fix_cdma (GClueModem *modem, location = gclue_location_new (latitude, longitude, - 1000); /* Assume 1 km accuracy */ + 1000, /* Assume 1 km accuracy */ + "CDMA"); gclue_location_source_set_location (GCLUE_LOCATION_SOURCE (user_data), location); @@ -241,7 +243,7 @@ gclue_cdma_stop (GClueLocationSource *source) { GClueCDMAPrivate *priv = GCLUE_CDMA (source)->priv; GClueLocationSourceClass *base_class; - GError *error = NULL; + g_autoptr(GError) error = NULL; GClueLocationSourceStopResult base_result; g_return_val_if_fail (GCLUE_IS_LOCATION_SOURCE (source), FALSE); @@ -261,7 +263,6 @@ gclue_cdma_stop (GClueLocationSource *source) &error)) { g_warning ("Failed to disable CDMA: %s", error->message); - g_error_free (error); } return base_result; diff --git a/src/gclue-client-info.c b/src/gclue-client-info.c index 7fc2ac7bbc6958fcc2671a07f0f4b3691ac3e644..623aeb1426b0f1c89648d8bcc21fb22c97613ec4 100644 --- a/src/gclue-client-info.c +++ b/src/gclue-client-info.c @@ -233,9 +233,9 @@ 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; + 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:"); @@ -356,15 +356,18 @@ on_dbus_proxy_ready (GObject *source_object, GTask *task = G_TASK (user_data); gpointer *info = g_task_get_source_object (task); GClueClientInfoPrivate *priv = GCLUE_CLIENT_INFO (info)->priv; + GDBusProxy *dbus_proxy; GError *error = NULL; - priv->dbus_proxy = g_dbus_proxy_new_for_bus_finish (res, &error); - if (priv->dbus_proxy == NULL) { + dbus_proxy = g_dbus_proxy_new_for_bus_finish (res, &error); + if (dbus_proxy == NULL) { g_task_return_error (task, error); g_object_unref (task); return; } + priv->dbus_proxy = dbus_proxy; + g_dbus_proxy_call (priv->dbus_proxy, "GetConnectionUnixUser", g_variant_new ("(s)", priv->bus_name), diff --git a/src/gclue-compass.c b/src/gclue-compass.c index f425aac4eb5150b70c5f9f9a8bfd5972fd6c708d..84b80d111b8512fce6fcd8e2e863e1f31ff5e531 100644 --- a/src/gclue-compass.c +++ b/src/gclue-compass.c @@ -79,14 +79,13 @@ gclue_compass_finalize (GObject *object) g_clear_object (&priv->cancellable); if (priv->proxy != NULL) { - GError *error = NULL; + g_autoptr(GError) error = NULL; if (!compass_call_release_compass_sync (priv->proxy, NULL, &error)) { g_warning ("Failed to release compass: %s", error->message); - g_error_free (error); } g_debug ("IIO compass released"); g_object_unref (priv->proxy); @@ -141,15 +140,16 @@ on_compass_claimed (GObject *source_object, { GClueCompass *compass; Compass *proxy = COMPASS (source_object); - GError *error = NULL; + g_autoptr(GError) error = NULL; if (!compass_call_claim_compass_finish (proxy, res, &error)) { - if (error->code != G_IO_ERROR_CANCELLED) + if (error && !g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { g_debug ("Failed to claim IIO proxy compass: %s", error->message); - g_error_free (error); - g_object_unref (proxy); + } + g_object_unref (proxy); return; } g_debug ("IIO compass claimed"); @@ -173,21 +173,22 @@ on_compass_proxy_ready (GObject *source_object, { GClueCompass *compass; Compass *proxy; - GError *error = NULL; + g_autoptr(GError) error = NULL; proxy = compass_proxy_new_for_bus_finish (res, &error); if (proxy == NULL) { - if (error->code != G_IO_ERROR_CANCELLED) + if (error && !g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { g_debug ("Failed to connect to IIO compass proxy: %s", error->message); - g_error_free (error); + } return; } compass = GCLUE_COMPASS (user_data); - compass_call_claim_compass (proxy, + compass_call_claim_compass (proxy, compass->priv->cancellable, on_compass_claimed, compass); diff --git a/src/gclue-config.c b/src/gclue-config.c index 8c1cc3de3c5e13f75dc4c34f3ed03cb3d6504875..acd600915ede170886def6fef342bb3407d223cd 100644 --- a/src/gclue-config.c +++ b/src/gclue-config.c @@ -22,10 +22,12 @@ #include <glib/gi18n.h> #include <config.h> +#include <string.h> #include "gclue-config.h" #define CONFIG_FILE_PATH SYSCONFDIR "/geoclue/geoclue.conf" +#define CONFIG_D_DIRECTORY SYSCONFDIR "/geoclue/conf.d/" /* This class will be responsible for fetching configuration. */ @@ -44,6 +46,7 @@ struct _GClueConfigPrivate gboolean enable_modem_gps_source; gboolean enable_wifi_source; gboolean enable_compass; + gboolean enable_static_source; char *wifi_submit_url; char *wifi_submit_nick; char *nmea_socket; @@ -101,22 +104,99 @@ gclue_config_class_init (GClueConfigClass *klass) object_class->finalize = gclue_config_finalize; } -static void -load_agent_config (GClueConfig *config) +static gboolean +load_boolean_value (GClueConfig *config, + const gchar *group_name, + const gchar *key, + gboolean *value_storage) { GClueConfigPrivate *priv = config->priv; - GError *error = NULL; - priv->agents = g_key_file_get_string_list (priv->key_file, - "agent", - "whitelist", - &priv->num_agents, - &error); - if (error != NULL) { - g_critical ("Failed to read 'agent/whitelist' key: %s", - error->message); - g_error_free (error); + g_return_val_if_fail (value_storage != NULL, FALSE); + + if (g_key_file_has_key (priv->key_file, group_name, key, NULL)) { + g_autoptr(GError) error = NULL; + gboolean value = + g_key_file_get_boolean (priv->key_file, + group_name, key, + &error); + if (error == NULL) { + *value_storage = value; + return TRUE; + } else + g_warning ("Failed to get config \"%s/%s\": %s", + group_name, key, error->message); } + + return FALSE; +} + +static gboolean +load_string_value (GClueConfig *config, + const gchar *group_name, + const gchar *key, + gchar **value_storage) +{ + GClueConfigPrivate *priv = config->priv; + + g_return_val_if_fail (value_storage != NULL, FALSE); + + if (g_key_file_has_key (priv->key_file, group_name, key, NULL)) { + g_autoptr(GError) error = NULL; + g_autofree gchar *value = + g_key_file_get_string (priv->key_file, + group_name, key, + &error); + if (error == NULL) { + g_clear_pointer (value_storage, g_free); + *value_storage = g_steal_pointer (&value); + return TRUE; + } else + g_warning ("Failed to get config \"%s/%s\": %s", + group_name, key, error->message); + } + + return FALSE; +} + +static gboolean +load_string_list_value (GClueConfig *config, + const gchar *group_name, + const gchar *key, + GStrv *value_storage, + gsize *length_storage) +{ + GClueConfigPrivate *priv = config->priv; + + g_return_val_if_fail (value_storage != NULL, FALSE); + g_return_val_if_fail (length_storage != NULL, FALSE); + + if (g_key_file_has_key (priv->key_file, group_name, key, NULL)) { + g_autoptr(GError) error = NULL; + gsize length = 0; + g_auto(GStrv) value = + g_key_file_get_string_list (priv->key_file, + group_name, key, + &length, &error); + if (error == NULL) { + g_clear_pointer (value_storage, g_strfreev); + *value_storage = g_steal_pointer (&value); + *length_storage = length; + return TRUE; + } else + g_warning ("Failed to get config \"%s/%s\": %s", + group_name, key, error->message); + } + + return FALSE; +} + +static void +load_agent_config (GClueConfig *config) +{ + load_string_list_value (config, "agent", "whitelist", + &config->priv->agents, + &config->priv->num_agents); } static void @@ -124,221 +204,193 @@ load_app_configs (GClueConfig *config) { const char *known_groups[] = { "agent", "wifi", "3g", "cdma", "modem-gps", "network-nmea", "compass", - NULL }; + "static-source", NULL }; GClueConfigPrivate *priv = config->priv; gsize num_groups = 0, i; - char **groups; + g_auto(GStrv) groups = NULL; groups = g_key_file_get_groups (priv->key_file, &num_groups); if (num_groups == 0) return; for (i = 0; i < num_groups; i++) { - AppConfig *app_config; - int* users; + AppConfig *app_config = NULL; + g_autofree int *users = NULL; + GList *node; gsize num_users = 0, j; gboolean allowed, system; gboolean ignore = FALSE; - GError *error = NULL; + gboolean new_app_config = TRUE; + gboolean has_allowed = FALSE; + gboolean has_system = FALSE; + gboolean has_users = FALSE; + g_autoptr(GError) error = NULL; for (j = 0; known_groups[j] != NULL; j++) if (strcmp (groups[i], known_groups[j]) == 0) { ignore = TRUE; - continue; + break; } if (ignore) continue; + /* Check if entry is new or is overwritten */ + for (node = priv->app_configs; node != NULL; node = node->next) { + if (strcmp (((AppConfig *) node->data)->id, groups[i]) == 0) { + app_config = (AppConfig *) node->data; + new_app_config = FALSE; + + break; + } + } + allowed = g_key_file_get_boolean (priv->key_file, groups[i], "allowed", &error); - if (error != NULL) + has_allowed = (error == NULL); + if (error != NULL && new_app_config) goto error_out; + g_clear_error (&error); system = g_key_file_get_boolean (priv->key_file, groups[i], "system", &error); - if (error != NULL) + has_system = (error == NULL); + if (error != NULL && new_app_config) goto error_out; + g_clear_error (&error); users = g_key_file_get_integer_list (priv->key_file, groups[i], "users", &num_users, &error); - if (error != NULL) + has_users = (error == NULL); + if (error != NULL && new_app_config) goto error_out; + g_clear_error (&error); + + + /* New app config, without erroring out above */ + if (new_app_config) { + app_config = g_slice_new0 (AppConfig); + priv->app_configs = g_list_prepend (priv->app_configs, app_config); + app_config->id = g_strdup (groups[i]); + } - app_config = g_slice_new0 (AppConfig); - app_config->id = g_strdup (groups[i]); - app_config->allowed = allowed; - app_config->system = system; - app_config->users = users; - app_config->num_users = num_users; + /* New app configs will have all of them, overwrites only some */ + if (has_allowed) + app_config->allowed = allowed; - priv->app_configs = g_list_prepend (priv->app_configs, app_config); + if (has_system) + app_config->system = system; + + if (has_users) { + g_free (app_config->users); + app_config->users = g_steal_pointer (&users); + app_config->num_users = num_users; + } continue; error_out: g_warning ("Failed to load configuration for app '%s': %s", groups[i], error->message); - g_error_free (error); } - - g_strfreev (groups); } -static gboolean -load_enable_source_config (GClueConfig *config, - const char *source_name) -{ - GClueConfigPrivate *priv = config->priv; - GError *error = NULL; - gboolean enable; - - enable = g_key_file_get_boolean (priv->key_file, - source_name, - "enable", - &error); - if (error != NULL) { - g_debug ("Failed to get config %s/enable:" - " %s", - source_name, - error->message); - g_error_free (error); - - /* Source should be enabled by default */ - return TRUE; - } - - return enable; -} - -#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) { GClueConfigPrivate *priv = config->priv; - GError *error = NULL; + g_autofree gchar *wifi_submit_nick = NULL; - priv->enable_wifi_source = load_enable_source_config (config, "wifi"); + load_boolean_value (config, "wifi", "enable", + &priv->enable_wifi_source); - priv->wifi_url = g_key_file_get_string (priv->key_file, - "wifi", - "url", - &error); - if (error != NULL) { - g_debug ("Failed to get config \"wifi/url\": %s", - error->message); - g_clear_error (&error); - priv->wifi_url = g_strdup (DEFAULT_WIFI_URL); - } + load_string_value (config, "wifi", "url", &priv->wifi_url); - priv->wifi_submit = g_key_file_get_boolean (priv->key_file, - "wifi", - "submit-data", - &error); - if (error != NULL) { - g_debug ("Failed to get config \"wifi/submit-data\": %s", - error->message); - g_error_free (error); + load_boolean_value (config, "wifi", "submit-data", &priv->wifi_submit); - return; - } - - priv->wifi_submit_url = g_key_file_get_string (priv->key_file, - "wifi", - "submission-url", - &error); - if (error != NULL) { - g_debug ("Failed to get config \"wifi/submission-url\": %s", - error->message); - g_clear_error (&error); - priv->wifi_submit_url = g_strdup (DEFAULT_WIFI_SUBMIT_URL); - } + load_string_value (config, "wifi", "submission-url", + &priv->wifi_submit_url); - priv->wifi_submit_nick = g_key_file_get_string (priv->key_file, - "wifi", - "submission-nick", - &error); - if (error != NULL) { - 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); + if (load_string_value (config, "wifi", "submission-nick", + &wifi_submit_nick)) { + /* Nickname must either be empty or 2 to 32 characters long */ + size_t nick_length = strlen (wifi_submit_nick); + if (nick_length != 1 && nick_length <= 32) { + g_clear_pointer (&priv->wifi_submit_nick, g_free); + priv->wifi_submit_nick = + g_steal_pointer (&wifi_submit_nick); + } else + g_warning ("\"wifi/submission-nick\" must be empty " + "or between 2 to 32 characters long"); } } static void load_3g_config (GClueConfig *config) { - config->priv->enable_3g_source = - load_enable_source_config (config, "3g"); + load_boolean_value (config, "3g", "enable", + &config->priv->enable_3g_source); } static void load_cdma_config (GClueConfig *config) { - config->priv->enable_cdma_source = - load_enable_source_config (config, "cdma"); + load_boolean_value (config, "cdma", "enable", + &config->priv->enable_cdma_source); } static void load_modem_gps_config (GClueConfig *config) { - config->priv->enable_modem_gps_source = - load_enable_source_config (config, "modem-gps"); + load_boolean_value (config, "modem-gps", "enable", + &config->priv->enable_modem_gps_source); } 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); - } + load_boolean_value (config, "network-nmea", "enable", + &config->priv->enable_nmea_source); + load_string_value (config, "network-nmea", "nmea-socket", + &config->priv->nmea_socket); } static void load_compass_config (GClueConfig *config) { - config->priv->enable_compass = - load_enable_source_config (config, "compass"); + load_boolean_value (config, "compass", "enable", + &config->priv->enable_compass); } static void -gclue_config_init (GClueConfig *config) +load_static_source_config (GClueConfig *config) { - GError *error = NULL; + load_boolean_value (config, "static-source", "enable", + &config->priv->enable_static_source); +} - config->priv = gclue_config_get_instance_private(config); - config->priv->key_file = g_key_file_new (); +static void +load_config_file (GClueConfig *config, const char *path) { + g_autoptr(GError) error = NULL; + + g_debug ("Loading config: %s", path); g_key_file_load_from_file (config->priv->key_file, - CONFIG_FILE_PATH, + path, 0, &error); if (error != NULL) { g_critical ("Failed to load configuration file '%s': %s", - CONFIG_FILE_PATH, error->message); - g_error_free (error); - + path, error->message); return; } @@ -350,6 +402,201 @@ gclue_config_init (GClueConfig *config) load_modem_gps_config (config); load_network_nmea_config (config); load_compass_config (config); + load_static_source_config (config); +} + +static void +files_element_clear (void *element) +{ + gchar **file_name = element; + g_free (*file_name); +} + +static gint +sort_files (gconstpointer a, gconstpointer b) +{ + char *str_a = *(char **)a; + char *str_b = *(char **)b; + + return g_strcmp0 (str_a, str_b); +} + +static gboolean +string_present (const gchar *str) +{ + return (str && str[0]); +} + +static const gchar * +string_or_none (const gchar *str) +{ + return (string_present (str) ? str : "none"); +} + +static const gchar * +enabled_disabled (gboolean value) +{ + return (value ? "enabled" : "disabled"); +} + +static char * +redact_api_key (char *url) +{ + char *match; + + if (!string_present (url)) + return NULL; + + match = g_strrstr (url, "key="); + if (match && match > url && (*(match - 1) == '?' || *(match - 1) == '&') + && *(match + 4) != '\0') { + GString *s = g_string_new (url); + g_string_replace (s, match + 4, "<redacted>", 1); + return g_string_free (s, FALSE); + } else { + return g_strdup (url); + } +} + +static void +gclue_config_print (GClueConfig *config) +{ + GClueConfigPrivate *priv = config->priv; + GList *node; + AppConfig *app_config = NULL; + gsize i; + + g_debug ("GeoClue configuration:"); + if (priv->num_agents > 0) { + g_debug ("Allowed agents:"); + for (i = 0; i < priv->num_agents; i++) + g_debug ("\t%s", priv->agents[i]); + } else + g_debug ("Allowed agents: none"); + g_debug ("Network NMEA source: %s", + enabled_disabled (priv->enable_nmea_source)); + g_debug ("\tNetwork NMEA socket: %s", + string_or_none (priv->nmea_socket)); + g_debug ("3G source: %s", + enabled_disabled (priv->enable_3g_source)); + g_debug ("CDMA source: %s", + enabled_disabled (priv->enable_cdma_source)); + g_debug ("Modem GPS source: %s", + enabled_disabled (priv->enable_modem_gps_source)); + g_debug ("WiFi source: %s", + enabled_disabled (priv->enable_wifi_source)); + { + g_autofree char *redacted_locate_url = + redact_api_key (priv->wifi_url); + g_debug ("\tWiFi locate URL: %s", + string_or_none (redacted_locate_url)); + } + { + g_autofree char *redacted_submit_url = + redact_api_key (priv->wifi_submit_url); + g_debug ("\tWiFi submit URL: %s", + string_or_none (redacted_submit_url)); + } + g_debug ("\tWiFi submit data: %s", + enabled_disabled (priv->wifi_submit)); + g_debug ("\tWiFi submission nickname: %s", + string_or_none (priv->wifi_submit_nick)); + g_debug ("Static source: %s", + enabled_disabled (priv->enable_static_source)); + g_debug ("Compass: %s", + enabled_disabled (priv->enable_compass)); + g_debug ("Application configs:"); + for (node = priv->app_configs; node != NULL; node = node->next) { + app_config = (AppConfig *) node->data; + g_debug ("\tID: %s", app_config->id); + g_debug ("\t\tAllowed: %s", app_config->allowed? "yes": "no"); + g_debug ("\t\tSystem: %s", app_config->system? "yes": "no"); + if (app_config->num_users > 0) { + g_debug ("\t\tUsers:"); + for (i = 0; i < app_config->num_users; i++) + g_debug ("\t\t\t%d", app_config->users[i]); + } else + g_debug ("\t\tUsers: all"); + } +} + +static void +gclue_config_init (GClueConfig *config) +{ + GClueConfigPrivate *priv = gclue_config_get_instance_private (config); + g_autoptr(GDir) dir = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(GArray) files = NULL; + char *name; + gsize i; + + config->priv = priv; + + /* Sources should be enabled by default */ + priv->enable_nmea_source = TRUE; + priv->enable_3g_source = TRUE; + priv->enable_cdma_source = TRUE; + priv->enable_modem_gps_source = TRUE; + priv->enable_wifi_source = TRUE; + priv->enable_compass = TRUE; + priv->enable_static_source = TRUE; + + /* Default strings */ + priv->wifi_url = g_strdup (DEFAULT_WIFI_URL); + priv->wifi_submit_url = g_strdup (DEFAULT_WIFI_SUBMIT_URL); + priv->wifi_submit_nick = g_strdup (DEFAULT_WIFI_SUBMIT_NICK); + + /* Load config file from default path, log all missing parameters */ + priv->key_file = g_key_file_new (); + load_config_file (config, CONFIG_FILE_PATH); + + /* + * Apply config overwrites from conf.d style config files, + * files are sorted alphabetically, example: '90-config.conf' + * will overwrite '50-config.conf'. + */ + dir = g_dir_open (CONFIG_D_DIRECTORY, 0, &error); + + if (error != NULL) { + if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) { + g_warning ("Failed to open %s: %s", + CONFIG_D_DIRECTORY, error->message); + } + goto out; + } + + files = g_array_new (FALSE, FALSE, sizeof(char *)); + g_array_set_clear_func (files, files_element_clear); + + while ((name = g_strdup (g_dir_read_name (dir)))) { + if (g_str_has_suffix (name, ".conf")) + g_array_append_val (files, name); + } + + g_array_sort (files, sort_files); + + for (i = 0; i < files->len; i++) { + g_autofree char *path = NULL; + + path = g_build_filename (CONFIG_D_DIRECTORY, + g_array_index (files, char *, i), + NULL); + load_config_file (config, path); + } +out: + if (!string_present (priv->wifi_url) && + (priv->enable_wifi_source || priv->enable_3g_source)) { + g_warning ("\"wifi/url\" is not set, " + "disabling WiFi and 3G sources"); + priv->enable_wifi_source = FALSE; + priv->enable_3g_source = FALSE; + } + if (!string_present (priv->wifi_submit_url) && priv->wifi_submit) { + g_warning ("\"wifi/submission-url\" is not set, " + "disabling WiFi/3G submissions"); + priv->wifi_submit = FALSE; + } + gclue_config_print (config); } GClueConfig * @@ -479,7 +726,7 @@ void gclue_config_set_wifi_submit_nick (GClueConfig *config, const char *nick) { - + g_clear_pointer (&config->priv->wifi_submit_nick, g_free); config->priv->wifi_submit_nick = g_strdup (nick); } @@ -528,9 +775,10 @@ gclue_config_get_enable_nmea_source (GClueConfig *config) void gclue_config_set_nmea_socket (GClueConfig *config, - const char *nmea_socket) + const char *nmea_socket) { - config->priv->nmea_socket = g_strdup(nmea_socket); + g_clear_pointer (&config->priv->nmea_socket, g_free); + config->priv->nmea_socket = g_strdup (nmea_socket); } gboolean @@ -538,3 +786,9 @@ gclue_config_get_enable_compass (GClueConfig *config) { return config->priv->enable_compass; } + +gboolean +gclue_config_get_enable_static_source (GClueConfig *config) +{ + return config->priv->enable_static_source; +} diff --git a/src/gclue-config.h b/src/gclue-config.h index 1231d56bec8e2f97dae49179240a49e12908076a..db66f86593ff57d9df725224fa44d917e58306c9 100644 --- a/src/gclue-config.h +++ b/src/gclue-config.h @@ -92,6 +92,8 @@ gboolean gclue_config_get_enable_modem_gps_source (GClueConfig *config); gboolean gclue_config_get_enable_nmea_source (GClueConfig *config); gboolean gclue_config_get_enable_compass (GClueConfig *config); +gboolean gclue_config_get_enable_static_source + (GClueConfig *config); G_END_DECLS diff --git a/src/gclue-location-source.c b/src/gclue-location-source.c index 5d7347d4b35d5c6306f755cb4bb92543243ff6c9..568b21429c611f36e17c79fa54d8bb429467834e 100644 --- a/src/gclue-location-source.c +++ b/src/gclue-location-source.c @@ -52,6 +52,7 @@ struct _GClueLocationSourcePrivate gboolean compute_movement; gboolean scramble_location; + gboolean priority_source; #if GCLUE_USE_COMPASS GClueCompass *compass; @@ -74,6 +75,7 @@ enum PROP_AVAILABLE_ACCURACY_LEVEL, PROP_COMPUTE_MOVEMENT, PROP_SCRAMBLE_LOCATION, + PROP_PRIORITY_SOURCE, LAST_PROP }; @@ -156,6 +158,10 @@ gclue_location_source_get_property (GObject *object, g_value_set_boolean (value, source->priv->scramble_location); break; + case PROP_PRIORITY_SOURCE: + g_value_set_boolean (value, source->priv->priority_source); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -190,6 +196,10 @@ gclue_location_source_set_property (GObject *object, source->priv->scramble_location = g_value_get_boolean (value); break; + case PROP_PRIORITY_SOURCE: + source->priv->priority_source = g_value_get_boolean (value); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -281,6 +291,18 @@ gclue_location_source_class_init (GClueLocationSourceClass *klass) g_object_class_install_property (object_class, PROP_SCRAMBLE_LOCATION, gParamSpecs[PROP_SCRAMBLE_LOCATION]); + + gParamSpecs[PROP_PRIORITY_SOURCE] = + g_param_spec_boolean ("priority-source", + "PrioritySource", + "Whether source is priority or not", + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, + PROP_PRIORITY_SOURCE, + gParamSpecs[PROP_PRIORITY_SOURCE]); + } static void @@ -289,6 +311,7 @@ gclue_location_source_init (GClueLocationSource *source) source->priv = gclue_location_source_get_instance_private (source); source->priv->compute_movement = TRUE; source->priv->time_threshold = gclue_min_uint_new (); + source->priv->priority_source = FALSE; } static GClueLocationSourceStartResult @@ -419,27 +442,36 @@ gclue_location_source_set_location (GClueLocationSource *source, priv->location = gclue_location_duplicate (location); if (priv->scramble_location) { - gdouble latitude, distance, accuracy; + gdouble latitude, distance, accuracy, scramble_range; latitude = gclue_location_get_latitude (priv->location); accuracy = gclue_location_get_accuracy (priv->location); + scramble_range = GCLUE_LOCATION_ACCURACY_NEIGHBORHOOD; + if (accuracy >= scramble_range) { + /* If the location source is already pretty inaccurate + * do just a limited range scrambling to be sure. + */ + scramble_range /= 3; + } + /* Randomization is needed to stop apps from calculationg the * actual location. */ - distance = (gdouble) g_random_int_range (1, 3); + distance = g_random_double_range (0, scramble_range); + distance /= 1000; if (g_random_boolean ()) latitude += distance * LATITUDE_IN_KM; else latitude -= distance * LATITUDE_IN_KM; - accuracy += 3000; + accuracy += scramble_range; g_object_set (G_OBJECT (priv->location), "latitude", latitude, "accuracy", accuracy, NULL); - g_debug ("location scrambled"); + g_debug ("%s location scrambled", G_OBJECT_TYPE_NAME (source)); } speed = gclue_location_get_speed (location); @@ -489,6 +521,21 @@ gclue_location_source_get_active (GClueLocationSource *source) return (source->priv->active_counter > 0); } +/** + * gclue_location_source_get_priority_source: + * @source: a #GClueLocationSource + * + * Returns: Returns: TRUE if source (e.g. GPS) has priority over other + * sources, FALSE otherwise. + **/ +gboolean +gclue_location_source_get_priority_source (GClueLocationSource *source) +{ + g_return_val_if_fail (GCLUE_IS_LOCATION_SOURCE (source), FALSE); + + return source->priv->priority_source; +} + /** * gclue_location_source_get_available_accuracy_level: * @source: a #GClueLocationSource diff --git a/src/gclue-location-source.h b/src/gclue-location-source.h index 620f601954361826d34a42d52de3df77624b1f9f..2860c3aef2dd13dd9b6ac924d2c82dc7a6dc6b8e 100644 --- a/src/gclue-location-source.h +++ b/src/gclue-location-source.h @@ -71,6 +71,8 @@ struct _GClueLocationSourceClass GClueLocationSourceStopResult (*stop) (GClueLocationSource *source); }; +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GClueLocationSource, g_object_unref) + GType gclue_location_source_get_type (void) G_GNUC_CONST; void gclue_location_source_start (GClueLocationSource *source); @@ -82,6 +84,8 @@ void gclue_location_source_set_location GClueLocation *location); gboolean gclue_location_source_get_active (GClueLocationSource *source); +gboolean gclue_location_source_get_priority_source + (GClueLocationSource *source); GClueAccuracyLevel gclue_location_source_get_available_accuracy_level (GClueLocationSource *source); diff --git a/src/gclue-location.c b/src/gclue-location.c index b0e5e0d0367d0354cfe0018ac21c45cd591a0ecf..da2a10ae883cd3046ce0f67dfcaebe3a8b748ef6 100644 --- a/src/gclue-location.c +++ b/src/gclue-location.c @@ -29,9 +29,11 @@ #include <string.h> #include <stdlib.h> -#define TIME_DIFF_THRESHOLD 60000000 /* 60 seconds */ +#define TIME_DIFF_THRESHOLD (60 * G_USEC_PER_SEC) /* 60 seconds */ #define EARTH_RADIUS_KM 6372.795 #define KNOTS_IN_METERS_PER_SECOND 0.51444 +#define RMC_TIME_DIFF_THRESHOLD 5 /* 5 seconds */ +#define RMC_DEFAULT_ACCURACY 5 /* 5 meters */ struct _GClueLocationPrivate { char *description; @@ -425,7 +427,7 @@ parse_coordinate_string (const char *coordinate, const char *direction) { gdouble minutes, degrees, out; - gchar *degrees_str; + g_autofree gchar *degrees_str = NULL; gchar *dot_str; gint dot_offset; @@ -450,7 +452,6 @@ parse_coordinate_string (const char *coordinate, degrees_str = g_strndup (coordinate, dot_offset - 2); degrees = g_ascii_strtod (degrees_str, NULL); - g_free (degrees_str); minutes = g_ascii_strtod (dot_str - 2, NULL); @@ -480,65 +481,53 @@ parse_altitude_string (const char *altitude, return g_ascii_strtod (altitude, NULL); } +/* Return a timestamp derived from the NMEA timestamp and system date as + * seconds since epoch. + * If system time cannot be retrieved, return 0. + * If timestamp parsing fails, return system time. + * If the parsed time is in the future when compared to the system time, + * return the parsed time yesterday. + */ static gint64 parse_nmea_timestamp (const char *nmea_ts) { - char parts[3][3]; - int i, hours, minutes, seconds; - GDateTime *now, *ts = NULL; - guint64 ret; + g_autoptr(GDateTime) now = NULL; + g_autoptr(GDateTime) midnight = NULL; + g_autoptr(GDateTime) ts = NULL; + GTimeSpan timespan; 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 */ - g_warning ("Failed to parse NMEA timestamp '%s'", - nmea_ts); + if (now == NULL) { + g_warning ("Failed to get the current UTC time"); + return 0; + } - goto parse_error; + if (!nmea_ts || !*nmea_ts) { /* Empty timestamp, no warning */ + return g_date_time_to_unix (now); } - for (i = 0; i < 3; i++) { - memmove (parts[i], nmea_ts + (i * 2), 2); - parts[i][2] = '\0'; + timespan = gclue_nmea_timestamp_to_timespan (nmea_ts); + if (timespan < 0) { + g_warning ("Failed to parse NMEA timestamp '%s'", nmea_ts); + return g_date_time_to_unix (now); } - hours = atoi (parts[0]); - minutes = atoi (parts[1]); - seconds = atoi (parts[2]); - - ts = g_date_time_new_utc (g_date_time_get_year (now), - g_date_time_get_month (now), - g_date_time_get_day_of_month (now), - hours, - minutes, - seconds); - if (ts == NULL) - goto parse_error; + + midnight = g_date_time_new_utc (g_date_time_get_year (now), + g_date_time_get_month (now), + g_date_time_get_day_of_month (now), + 0, + 0, + 0); + ts = g_date_time_add (midnight, timespan); if (g_date_time_difference (ts, now) > TIME_DIFF_THRESHOLD) { g_debug ("NMEA timestamp '%s' in future. Assuming yesterday's.", nmea_ts); - g_date_time_unref (ts); - - ts = g_date_time_new_utc (g_date_time_get_year (now), - g_date_time_get_month (now), - g_date_time_get_day_of_month (now) - 1, - hours, - minutes, - seconds); + g_clear_pointer (&ts, g_date_time_unref); + ts = g_date_time_add (midnight, timespan - G_TIME_SPAN_DAY); } - ret = g_date_time_to_unix (ts); - g_date_time_unref (ts); -parse_error: - g_date_time_unref (now); - - return ret; + return g_date_time_to_unix (ts); } /** @@ -546,6 +535,7 @@ parse_error: * @latitude: a valid latitude * @longitude: a valid longitude * @accuracy: accuracy of location in meters + * @description: a description for the location * * Creates a new #GClueLocation object. * @@ -554,12 +544,14 @@ parse_error: GClueLocation * gclue_location_new (gdouble latitude, gdouble longitude, - gdouble accuracy) + gdouble accuracy, + const char *description) { return g_object_new (GCLUE_TYPE_LOCATION, "latitude", latitude, "longitude", longitude, "accuracy", accuracy, + "description", description, NULL); } @@ -601,21 +593,23 @@ gclue_location_new_full (gdouble latitude, } static GClueLocation * -gclue_location_create_from_gga (const char *gga, GError **error) +gclue_location_create_from_gga (const char *gga) { - GClueLocation *location = NULL; + GClueLocation *location; gdouble latitude, longitude, accuracy, altitude; gdouble hdop; /* Horizontal Dilution Of Precision */ guint64 timestamp; - char **parts; + g_auto(GStrv) parts = NULL; parts = g_strsplit (gga, ",", -1); if (g_strv_length (parts) < 14) { - g_set_error_literal (error, - G_IO_ERROR, - G_IO_ERROR_INVALID_ARGUMENT, - "Invalid NMEA GGA sentence"); - goto out; + g_warning ("Invalid NMEA GGA sentence."); + return NULL; + } + + if (g_ascii_strtoll (parts[6], NULL, 10) == 0) { + /* No fix, ignore. */ + return NULL; } /* For syntax of GGA sentences: @@ -625,11 +619,8 @@ gclue_location_create_from_gga (const char *gga, GError **error) latitude = parse_coordinate_string (parts[2], parts[3]); longitude = parse_coordinate_string (parts[4], parts[5]); if (latitude == INVALID_COORDINATE || longitude == INVALID_COORDINATE) { - g_set_error_literal (error, - G_IO_ERROR, - G_IO_ERROR_INVALID_ARGUMENT, - "Invalid NMEA GGA sentence"); - goto out; + g_warning ("Invalid coordinate on NMEA GGA sentence."); + return NULL; } altitude = parse_altitude_string (parts[9], parts[10]); @@ -642,32 +633,42 @@ gclue_location_create_from_gga (const char *gga, GError **error) "longitude", longitude, "accuracy", accuracy, "timestamp", timestamp, + "description", "GPS GGA", NULL); if (altitude != GCLUE_LOCATION_ALTITUDE_UNKNOWN) g_object_set (location, "altitude", altitude, NULL); -out: - g_strfreev (parts); return location; } static GClueLocation * gclue_location_create_from_rmc (const char *rmc, - GClueLocation *prev_location, - GError **error) + GClueLocation *prev_location) { - GClueLocation *location = NULL; - char **parts = g_strsplit (rmc, ",", -1); + GClueLocation *location; + g_auto(GStrv) parts = NULL; + gdouble accuracy; + gdouble altitude; + + parts = g_strsplit (rmc, ",", -1); + if (g_strv_length (parts) < 12) { + g_warning ("Invalid NMEA RMC sentence."); + return NULL; + } - if (g_strv_length (parts) < 13) - goto error; + /* RMC sentence is invalid */ + if (g_strcmp0 (parts[2], "A") != 0) { + return NULL; + } 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; + if (lat == INVALID_COORDINATE || lon == INVALID_COORDINATE) { + g_warning ("Invalid coordinate on NMEA RMC sentence."); + return NULL; + } gdouble speed = GCLUE_LOCATION_SPEED_UNKNOWN; if (parts[7][0] != '\0') @@ -683,33 +684,34 @@ gclue_location_create_from_rmc (const char *rmc, heading = GCLUE_LOCATION_HEADING_UNKNOWN; } + accuracy = RMC_DEFAULT_ACCURACY; + altitude = GCLUE_LOCATION_ALTITUDE_UNKNOWN; + if (prev_location != NULL) { + guint64 prev_loc_timestamp; + + prev_loc_timestamp = gclue_location_get_timestamp (prev_location); + + /* Sentence is older then previous location, reject */ + if (timestamp < prev_loc_timestamp) + return NULL; + + if (timestamp - prev_loc_timestamp < RMC_TIME_DIFF_THRESHOLD) { + accuracy = gclue_location_get_accuracy (prev_location); + altitude = gclue_location_get_altitude (prev_location); + } + } + location = g_object_new (GCLUE_TYPE_LOCATION, "latitude", lat, "longitude", lon, "timestamp", timestamp, "speed", speed, "heading", heading, + "description", "GPS RMC", + "accuracy", accuracy, + "altitude", altitude, 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; } @@ -717,7 +719,6 @@ out: * 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. @@ -728,8 +729,7 @@ out: **/ GClueLocation * gclue_location_create_from_nmeas (const char *nmeas[], - GClueLocation *prev_location, - GError **error) + GClueLocation *prev_location) { GClueLocation *gga_loc = NULL; GClueLocation *rmc_loc = NULL; @@ -737,10 +737,10 @@ gclue_location_create_from_nmeas (const char *nmeas[], for (iter = nmeas; *iter != NULL; iter++) { if (!gga_loc && gclue_nmea_type_is (*iter, "GGA")) - gga_loc = gclue_location_create_from_gga (*iter, NULL); + gga_loc = gclue_location_create_from_gga (*iter); if (!rmc_loc && gclue_nmea_type_is (*iter, "RMC")) rmc_loc = gclue_location_create_from_rmc - (*iter, prev_location, NULL); + (*iter, prev_location); if (gga_loc && rmc_loc) break; } @@ -750,6 +750,7 @@ gclue_location_create_from_nmeas (const char *nmeas[], (gga_loc, gclue_location_get_speed(rmc_loc)); gclue_location_set_heading (gga_loc, gclue_location_get_heading(rmc_loc)); + g_object_set (gga_loc, "description", "GPS GGA+RMC", NULL); g_object_unref (rmc_loc); return gga_loc; @@ -759,10 +760,7 @@ gclue_location_create_from_nmeas (const char *nmeas[], 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"); + g_debug ("Valid NMEA GGA or RMC sentence not found"); return NULL; } @@ -770,7 +768,7 @@ gclue_location_create_from_nmeas (const char *nmeas[], * gclue_location_duplicate: * @location: the #GClueLocation instance to duplicate. * - * Creates a new copy of @location object. + * Creates a new copy of @location object (with the same timestamp). * * Returns: a new #GClueLocation object. Use g_object_unref() when done. **/ @@ -788,6 +786,32 @@ gclue_location_duplicate (GClueLocation *location) "timestamp", location->priv->timestamp, "speed", location->priv->speed, "heading", location->priv->heading, + "description", location->priv->description, + NULL); +} + +/** + * gclue_location_duplicate_fresh: + * @location: the #GClueLocation instance to duplicate. + * + * Creates a new copy of @location object with a refreshed timestamp. + * + * Returns: a new #GClueLocation object. Use g_object_unref() when done. + **/ +GClueLocation * +gclue_location_duplicate_fresh (GClueLocation *location) +{ + g_return_val_if_fail (GCLUE_IS_LOCATION (location), NULL); + + return g_object_new + (GCLUE_TYPE_LOCATION, + "latitude", location->priv->latitude, + "longitude", location->priv->longitude, + "accuracy", location->priv->accuracy, + "altitude", location->priv->altitude, + "speed", location->priv->speed, + "heading", location->priv->heading, + "description", location->priv->description, NULL); } diff --git a/src/gclue-location.h b/src/gclue-location.h index 0db57ac95068d5fdcf7200b5edd2f51701d58512..40f2d6a9a04f9fb071de74660aa9f4d9ea3aa78c 100644 --- a/src/gclue-location.h +++ b/src/gclue-location.h @@ -76,6 +76,13 @@ GType gclue_location_get_type (void); */ #define GCLUE_LOCATION_ACCURACY_UNKNOWN -1 +/** + * GCLUE_LOCATION_ACCURACY_EXACT: + * + * Constant representing exact-level accuracy. + */ +#define GCLUE_LOCATION_ACCURACY_EXACT 50 /* 50 m */ + /** * GCLUE_LOCATION_ACCURACY_STREET: * @@ -83,6 +90,13 @@ GType gclue_location_get_type (void); */ #define GCLUE_LOCATION_ACCURACY_STREET 1000 /* 1 km */ +/** + * GCLUE_LOCATION_ACCURACY_NEIGHBORHOOD: + * + * Constant representing neighborhood-level accuracy. + */ +#define GCLUE_LOCATION_ACCURACY_NEIGHBORHOOD 3000 /* 3 km */ + /** * GCLUE_LOCATION_ACCURACY_CITY: * @@ -94,6 +108,8 @@ GType gclue_location_get_type (void); * GCLUE_LOCATION_ACCURACY_REGION: * * Constant representing region-level accuracy. + * + * Currently unused. */ #define GCLUE_LOCATION_ACCURACY_REGION 50000 /* 50 km */ @@ -108,6 +124,8 @@ GType gclue_location_get_type (void); * GCLUE_LOCATION_ACCURACY_CONTINENT: * * Constant representing continent-level accuracy. + * + * Currently unused. */ #define GCLUE_LOCATION_ACCURACY_CONTINENT 3000000 /* 3000 km */ @@ -127,7 +145,8 @@ GType gclue_location_get_type (void); GClueLocation *gclue_location_new (gdouble latitude, gdouble longitude, - gdouble accuracy); + gdouble accuracy, + const char *description); GClueLocation *gclue_location_new_full (gdouble latitude, @@ -141,11 +160,12 @@ GClueLocation *gclue_location_new_full GClueLocation *gclue_location_create_from_nmeas (const char *nmeas[], - GClueLocation *prev_location, - GError **error); + GClueLocation *prev_location); GClueLocation *gclue_location_duplicate (GClueLocation *location); +GClueLocation *gclue_location_duplicate_fresh + (GClueLocation *location); void gclue_location_set_description (GClueLocation *loc, diff --git a/src/gclue-locator.c b/src/gclue-locator.c index 150d82963fe1b4cfedb6c6709d9d4dcb9c2aa093..d6ed56069ebd8d3cc278cc7c798587c89d41b4c7 100644 --- a/src/gclue-locator.c +++ b/src/gclue-locator.c @@ -2,6 +2,7 @@ /* gclue-locator.c * * Copyright 2013 Red Hat, Inc. + * Copyright © 2022,2023 Oracle and/or its affiliates. * * 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 @@ -18,6 +19,7 @@ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * Authors: Zeeshan Ali (Khattak) <zeeshanak@gnome.org> + * Maciej S. Szmigiero <maciej.szmigiero@oracle.com> */ #include "config.h" @@ -26,6 +28,7 @@ #include "gclue-locator.h" +#include "gclue-static-source.h" #include "gclue-wifi.h" #include "gclue-config.h" @@ -51,7 +54,7 @@ static GClueLocationSourceStartResult gclue_locator_start (GClueLocationSource *source); -static GClueLocationSourceStopResult +static GClueLocationSourceStopResult gclue_locator_stop (GClueLocationSource *source); struct _GClueLocatorPrivate @@ -60,6 +63,8 @@ struct _GClueLocatorPrivate GList *active_sources; GClueAccuracyLevel accuracy_level; + gboolean priority_source_lock; + guint64 priority_source_lock_timestamp; }; G_DEFINE_TYPE_WITH_CODE (GClueLocator, @@ -78,18 +83,32 @@ static GParamSpec *gParamSpecs[LAST_PROP]; #define MAX_SPEED 500 /* Meters per second */ #define MAX_LOCATION_AGE (30 * 60) /* Seconds. */ +#define MAX_PRIORITY_SOURCE_AGE 30 /* Seconds. */ +#define PRIORITY_ACCURACY_THRESHOLD 20 /* Meters */ static void set_location (GClueLocator *locator, - GClueLocation *location) + GClueLocationSource *source) { GClueLocation *cur_location; + GClueLocation *location; + const char *src_name = NULL; + gboolean update_priority_source = FALSE; + + location = gclue_location_source_get_location (source); + src_name = G_OBJECT_TYPE_NAME (source); + + if (gclue_location_get_accuracy (location) == + GCLUE_LOCATION_ACCURACY_UNKNOWN) { + /* If we do not know the accuracy, discard the update */ + g_debug ("Discarding %s location with unknown accuracy", + src_name); + return; + } cur_location = gclue_location_source_get_location (GCLUE_LOCATION_SOURCE (locator)); - g_debug ("New location available"); - if (cur_location != NULL) { guint64 cur_timestamp, new_timestamp; double dist, speed; @@ -97,7 +116,8 @@ set_location (GClueLocator *locator, 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."); + g_debug ("New %s location older than current, ignoring.", + src_name); return; } @@ -117,20 +137,46 @@ set_location (GClueLocator *locator, speed = G_MAXDOUBLE; } - if ((dist <= gclue_location_get_accuracy (location) || - speed > MAX_SPEED) && - gclue_location_get_accuracy (location) > - gclue_location_get_accuracy (cur_location)) { + update_priority_source = gclue_location_source_get_priority_source (source) && + (gclue_location_get_accuracy (location) < PRIORITY_ACCURACY_THRESHOLD); + + if (update_priority_source) { + if (!locator->priv->priority_source_lock) + g_debug ("Enabling Priority Source Lock"); + locator->priv->priority_source_lock = TRUE; + locator->priv->priority_source_lock_timestamp = new_timestamp; + } else if (locator->priv->priority_source_lock && + (new_timestamp - locator->priv->priority_source_lock_timestamp) >= MAX_PRIORITY_SOURCE_AGE) { + g_debug ("Priority Source Lock no longer active"); + locator->priv->priority_source_lock = FALSE; + } + + if (locator->priv->priority_source_lock && + !gclue_location_source_get_priority_source (source)) { + g_debug ("Priority Source Lock (age %u s) active, ignoring new %s location", + (guint) (new_timestamp - locator->priv->priority_source_lock_timestamp), + src_name); + return; + } + + if (update_priority_source) { + /* A priority source is updating, let it though */ + g_debug ("Priority Source Lock Active"); + } else 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 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"); + g_debug ("Ignoring less accurate new %s location", src_name); return; } } + g_debug ("New location available from %s", src_name); gclue_location_source_set_location (GCLUE_LOCATION_SOURCE (locator), location); } @@ -180,10 +226,8 @@ on_location_changed (GObject *gobject, { GClueLocator *locator = GCLUE_LOCATOR (user_data); GClueLocationSource *source = GCLUE_LOCATION_SOURCE (gobject); - GClueLocation *location; - location = gclue_location_source_get_location (source); - set_location (locator, location); + set_location (locator, source); } static gboolean @@ -206,7 +250,7 @@ start_source (GClueLocator *locator, location = gclue_location_source_get_location (src); if (gclue_location_source_get_active (src) && location != NULL) - set_location (locator, location); + set_location (locator, src); gclue_location_source_start (src); } @@ -356,7 +400,7 @@ gclue_locator_constructed (GObject *object) GClueLocator *locator = GCLUE_LOCATOR (object); GClueLocationSource *submit_source = NULL; GClueConfig *gconfig = gclue_config_get_singleton (); - GClueWifi *wifi; + GClueWifi *wifi = NULL; GList *node; GClueMinUINT *threshold; @@ -364,7 +408,7 @@ gclue_locator_constructed (GObject *object) #if GCLUE_USE_3G_SOURCE if (gclue_config_get_enable_3g_source (gconfig)) { - GClue3G *source = gclue_3g_get_singleton (); + GClue3G *source = gclue_3g_get_singleton (locator->priv->accuracy_level); locator->priv->sources = g_list_append (locator->priv->sources, source); } @@ -376,18 +420,28 @@ gclue_locator_constructed (GObject *object) cdma); } #endif - if (gclue_config_get_enable_wifi_source (gconfig)) + if (gclue_config_get_enable_wifi_source (gconfig)) { wifi = gclue_wifi_get_singleton (locator->priv->accuracy_level); - else - /* City-level accuracy will give us GeoIP-only source */ - wifi = gclue_wifi_get_singleton (GCLUE_ACCURACY_LEVEL_CITY); - locator->priv->sources = g_list_append (locator->priv->sources, wifi); + } else { + if (gclue_config_get_enable_static_source (gconfig)) { + g_debug ("Disabling GeoIP-only source since static source is enabled"); + } else { + /* City-level accuracy will give us GeoIP-only source */ + wifi = gclue_wifi_get_singleton (GCLUE_ACCURACY_LEVEL_CITY); + } + } + if (wifi) { + locator->priv->sources = g_list_append (locator->priv->sources, + wifi); + } #if GCLUE_USE_MODEM_GPS_SOURCE if (gclue_config_get_enable_modem_gps_source (gconfig)) { GClueModemGPS *gps = gclue_modem_gps_get_singleton (); locator->priv->sources = g_list_append (locator->priv->sources, gps); - submit_source = GCLUE_LOCATION_SOURCE (gps); + if (!submit_source) { + submit_source = GCLUE_LOCATION_SOURCE (gps); + } } #endif #if GCLUE_USE_NMEA_SOURCE @@ -395,9 +449,22 @@ gclue_locator_constructed (GObject *object) GClueNMEASource *nmea = gclue_nmea_source_get_singleton (); locator->priv->sources = g_list_append (locator->priv->sources, nmea); + if (!submit_source) { + submit_source = GCLUE_LOCATION_SOURCE (nmea); + } + } #endif + if (gclue_config_get_enable_static_source (gconfig)) { + GClueStaticSource *static_source; + + static_source = gclue_static_source_get_singleton + (locator->priv->accuracy_level); + locator->priv->sources = g_list_append (locator->priv->sources, + static_source); + } + for (node = locator->priv->sources; node != NULL; node = node->next) { g_signal_connect (G_OBJECT (node->data), "notify::available-accuracy-level", @@ -449,6 +516,8 @@ static void gclue_locator_init (GClueLocator *locator) { locator->priv = gclue_locator_get_instance_private (locator); + locator->priv->priority_source_lock = FALSE; + locator->priv->priority_source_lock_timestamp = 0; } static GClueLocationSourceStartResult diff --git a/src/gclue-main.c b/src/gclue-main.c index d14cadc38fad66caa26dab6a7f2c4af732f2ebc3..9b32170b1fc4ae847a1a9eb8a1911cd97800a681 100644 --- a/src/gclue-main.c +++ b/src/gclue-main.c @@ -66,7 +66,7 @@ static GOptionEntry entries[] = 0, G_OPTION_ARG_STRING, &submit_nick, - N_("Nickname to submit network data under (2-32 characters)"), + N_("Nickname to submit network data under (empty or 2 to 32 characters)"), "NICK" }, { "nmea-socket", 'u', @@ -123,12 +123,11 @@ on_bus_acquired (GDBusConnection *connection, const gchar *name, gpointer user_data) { - GError *error = NULL; + g_autoptr(GError) error = NULL; manager = gclue_service_manager_new (connection, &error); if (manager == NULL) { g_critical ("Failed to register server: %s", error->message); - g_error_free (error); exit (-2); } @@ -159,7 +158,7 @@ int main (int argc, char **argv) { guint owner_id; - GError *error = NULL; + g_autoptr(GError) error = NULL; GOptionContext *context; GClueConfig *config; diff --git a/src/gclue-modem-gps.c b/src/gclue-modem-gps.c index ce4967ad4cafa67d65fc2d322937f0e5579b3d10..5b348d212eacb8717b41003ca71997a71b5f663f 100644 --- a/src/gclue-modem-gps.c +++ b/src/gclue-modem-gps.c @@ -80,14 +80,15 @@ on_gps_enabled (GObject *source_object, GAsyncResult *result, gpointer user_data) { - GClueModemGPS *source = GCLUE_MODEM_GPS (user_data); - GError *error = NULL; + g_autoptr(GError) error = NULL; - if (!gclue_modem_enable_gps_finish (source->priv->modem, + if (!gclue_modem_enable_gps_finish (GCLUE_MODEM (source_object), result, &error)) { - g_warning ("Failed to enable GPS: %s", error->message); - g_error_free (error); + if (error && !g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { + g_warning ("Failed to enable GPS: %s", error->message); + } } } @@ -168,10 +169,10 @@ gclue_modem_gps_init (GClueModemGPS *source) source); threshold = gclue_location_source_get_time_threshold (GCLUE_LOCATION_SOURCE (source)); - g_signal_connect (threshold, - "notify::value", - G_CALLBACK (on_time_threshold_changed), - source); + g_signal_connect_object (threshold, + "notify::value", + G_CALLBACK (on_time_threshold_changed), + source, 0); } static void @@ -197,7 +198,9 @@ gclue_modem_gps_get_singleton (void) static GClueModemGPS *source = NULL; if (source == NULL) { - source = g_object_new (GCLUE_TYPE_MODEM_GPS, NULL); + source = g_object_new (GCLUE_TYPE_MODEM_GPS, + "priority-source", TRUE, + NULL); g_object_weak_ref (G_OBJECT (source), on_modem_gps_destroyed, &source); @@ -215,22 +218,13 @@ on_fix_gps (GClueModem *modem, GClueLocationSource *source = GCLUE_LOCATION_SOURCE (user_data); GClueLocation *prev_location; g_autoptr(GClueLocation) location = NULL; - GError *error = NULL; prev_location = gclue_location_source_get_location (source); - location = gclue_location_create_from_nmeas (nmeas, - prev_location, - &error); + location = gclue_location_create_from_nmeas (nmeas, prev_location); - if (error != NULL) { - g_warning ("Error: %s", error->message); - g_clear_error (&error); - - return; + if (location) { + gclue_location_source_set_location (source, location); } - - gclue_location_source_set_location (source, - location); } static GClueLocationSourceStartResult @@ -268,7 +262,7 @@ gclue_modem_gps_stop (GClueLocationSource *source) { GClueModemGPSPrivate *priv = GCLUE_MODEM_GPS (source)->priv; GClueLocationSourceClass *base_class; - GError *error = NULL; + g_autoptr(GError) error = NULL; GClueLocationSourceStopResult base_result; g_return_val_if_fail (GCLUE_IS_LOCATION_SOURCE (source), FALSE); @@ -288,7 +282,6 @@ gclue_modem_gps_stop (GClueLocationSource *source) &error)) { g_warning ("Failed to disable GPS: %s", error->message); - g_error_free (error); } return base_result; diff --git a/src/gclue-modem-manager.c b/src/gclue-modem-manager.c index 9a8cf57ca165238b036577bc5829013773c44f4c..e6ee8238fe4b91347eaa3e5d533bbd37757c45b9 100644 --- a/src/gclue-modem-manager.c +++ b/src/gclue-modem-manager.c @@ -41,15 +41,20 @@ gclue_modem_interface_init (GClueModemInterface *iface); struct _GClueModemManagerPrivate { MMManager *manager; + + GHashTable *modems_not_enabled; + MMObject *mm_object; MMModem *modem; MMModemLocation *modem_location; MMLocation3gpp *location_3gpp; + gboolean location_3gpp_ignore_previous; MMLocationGpsNmea *location_nmea; GCancellable *cancellable; MMModemLocationSource caps; /* Caps we set or are going to set */ + GClueTowerTec tec; guint time_threshold; }; @@ -145,6 +150,7 @@ gclue_modem_manager_finalize (GObject *gmodem) g_clear_object (&priv->mm_object); g_clear_object (&priv->modem); g_clear_object (&priv->modem_location); + g_clear_pointer (&priv->modems_not_enabled, g_hash_table_unref); } static void @@ -286,7 +292,8 @@ static gboolean is_location_3gpp_same (GClueModemManager *manager, const gchar *new_opc, gulong new_lac, - gulong new_cell_id) + gulong new_cell_id, + GClueTowerTec new_tec) { GClueModemManagerPrivate *priv = manager->priv; const gchar *opc; @@ -295,7 +302,7 @@ is_location_3gpp_same (GClueModemManager *manager, gchar opc_buf[GCLUE_3G_TOWER_OPERATOR_CODE_STR_LEN + 1]; #endif - if (priv->location_3gpp == NULL) + if (priv->location_3gpp == NULL || priv->location_3gpp_ignore_previous) return FALSE; #if MM_CHECK_VERSION(1, 18, 0) @@ -306,11 +313,10 @@ is_location_3gpp_same (GClueModemManager *manager, #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. + // Use the tracking area code in place of the + // location area code for LTE. // https://ichnaea.readthedocs.io/en/latest/api/geolocate.html#cell-tower-fields - if (lac == 0x0 || lac == 0xFFFE) { + if (priv->tec == GCLUE_TOWER_TEC_4G) { lac = mm_location_3gpp_get_tracking_area_code(priv->location_3gpp); } @@ -318,7 +324,20 @@ is_location_3gpp_same (GClueModemManager *manager, return (g_strcmp0 (opc, new_opc) == 0 && lac == new_lac && - cell_id == new_cell_id); + cell_id == new_cell_id && + priv->tec == new_tec); +} + +static void clear_3gpp_location (GClueModemManager *manager) +{ + GClueModemManagerPrivate *priv = manager->priv; + + if (!priv->location_3gpp && !priv->location_3gpp_ignore_previous) { + return; + } + + g_clear_object (&priv->location_3gpp); + g_signal_emit (manager, signals[FIX_3G], 0, NULL, 0, 0, GCLUE_TOWER_TEC_NO_FIX); } static void @@ -326,30 +345,40 @@ on_get_3gpp_ready (GObject *source_object, GAsyncResult *res, gpointer user_data) { - GClueModemManager *manager = GCLUE_MODEM_MANAGER (user_data); - GClueModemManagerPrivate *priv = manager->priv; + GClueModemManager *manager; + GClueModemManagerPrivate *priv; MMModemLocation *modem_location = MM_MODEM_LOCATION (source_object); + MMModemAccessTechnology modem_access_tec; g_autoptr(MMLocation3gpp) location_3gpp = NULL; - GError *error = NULL; const gchar *opc; gulong lac, cell_id; - GClueTowerTec tec = GCLUE_TOWER_TEC_3G; + GClueTowerTec tec; #if !MM_CHECK_VERSION(1, 18, 0) + g_autoptr(GError) error = NULL; gchar opc_buf[GCLUE_3G_TOWER_OPERATOR_CODE_STR_LEN + 1]; -#endif location_3gpp = mm_modem_location_get_3gpp_finish (modem_location, res, &error); if (error != NULL) { - g_warning ("Failed to get location from 3GPP: %s", - error->message); - g_error_free (error); + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_warning ("Failed to get location from 3GPP: %s", + error->message); + } + return; } +#else + location_3gpp = mm_modem_location_get_signaled_3gpp (modem_location); +#endif + + manager = GCLUE_MODEM_MANAGER (user_data); + priv = manager->priv; if (location_3gpp == NULL) { g_debug ("No 3GPP"); + clear_3gpp_location (manager); + priv->location_3gpp_ignore_previous = FALSE; return; } @@ -364,46 +393,80 @@ on_get_3gpp_ready (GObject *source_object, 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) { + cell_id = mm_location_3gpp_get_cell_id (location_3gpp); + + modem_access_tec = mm_modem_get_access_technologies(priv->modem); + + if (modem_access_tec == MM_MODEM_ACCESS_TECHNOLOGY_GSM || + modem_access_tec == MM_MODEM_ACCESS_TECHNOLOGY_GPRS || + modem_access_tec == MM_MODEM_ACCESS_TECHNOLOGY_EDGE) { + tec = GCLUE_TOWER_TEC_2G; + } else if (modem_access_tec == MM_MODEM_ACCESS_TECHNOLOGY_UMTS || + modem_access_tec == MM_MODEM_ACCESS_TECHNOLOGY_HSDPA || + modem_access_tec == MM_MODEM_ACCESS_TECHNOLOGY_HSUPA || + modem_access_tec == MM_MODEM_ACCESS_TECHNOLOGY_HSPA || + modem_access_tec == MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS) { + tec = GCLUE_TOWER_TEC_3G; + } else if (modem_access_tec == MM_MODEM_ACCESS_TECHNOLOGY_LTE) { lac = mm_location_3gpp_get_tracking_area_code(location_3gpp); tec = GCLUE_TOWER_TEC_4G; + } else { + tec = GCLUE_TOWER_TEC_UNKNOWN; } - cell_id = mm_location_3gpp_get_cell_id (location_3gpp); - - if (is_location_3gpp_same (manager, opc, lac, cell_id)) { + if (is_location_3gpp_same (manager, opc, lac, cell_id, tec)) { g_debug ("New 3GPP location is same as last one"); return; } g_clear_object (&priv->location_3gpp); priv->location_3gpp = g_steal_pointer (&location_3gpp); + priv->location_3gpp_ignore_previous = FALSE; + priv->tec = tec; g_signal_emit (manager, signals[FIX_3G], 0, opc, lac, cell_id, tec); } +static void +on_location_changed_get_3gpp (GObject *modem_object, + GClueModemManager *manager) +{ +#if MM_CHECK_VERSION(1, 18, 0) + on_get_3gpp_ready(modem_object, NULL, manager); +#else + mm_modem_location_get_3gpp (MM_MODEM_LOCATION (modem_object), + manager->priv->cancellable, + on_get_3gpp_ready, + manager); +#endif +} + static void on_get_cdma_ready (GObject *source_object, GAsyncResult *res, gpointer user_data) { - GClueModemManager *manager = GCLUE_MODEM_MANAGER (user_data); + GClueModemManager *manager; MMModemLocation *modem_location = MM_MODEM_LOCATION (source_object); g_autoptr(MMLocationCdmaBs) location_cdma = NULL; - GError *error = NULL; +#if !MM_CHECK_VERSION(1, 18, 0) + g_autoptr(GError) error = NULL; location_cdma = mm_modem_location_get_cdma_bs_finish (modem_location, res, &error); if (error != NULL) { - g_warning ("Failed to get location from 3GPP: %s", - error->message); - g_error_free (error); + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_warning ("Failed to get location from CDMA: %s", + error->message); + } + return; } +#else + location_cdma = mm_modem_location_get_signaled_cdma_bs (modem_location); +#endif + + manager = GCLUE_MODEM_MANAGER (user_data); if (location_cdma == NULL) { g_debug ("No CDMA"); @@ -417,6 +480,20 @@ on_get_cdma_ready (GObject *source_object, mm_location_cdma_bs_get_longitude (location_cdma)); } +static void +on_location_changed_get_cdma (GObject *modem_object, + GClueModemManager *manager) +{ +#if MM_CHECK_VERSION(1, 18, 0) + on_get_cdma_ready(modem_object, NULL, manager); +#else + mm_modem_location_get_cdma_bs (MM_MODEM_LOCATION (modem_object), + manager->priv->cancellable, + on_get_cdma_ready, + manager); +#endif +} + static gboolean is_location_gga_same (GClueModemManager *manager, const char *new_gga) @@ -436,24 +513,33 @@ on_get_gps_nmea_ready (GObject *source_object, GAsyncResult *res, gpointer user_data) { - GClueModemManager *manager = GCLUE_MODEM_MANAGER (user_data); - GClueModemManagerPrivate *priv = manager->priv; + GClueModemManager *manager; + GClueModemManagerPrivate *priv; MMModemLocation *modem_location = MM_MODEM_LOCATION (source_object); g_autoptr(MMLocationGpsNmea) location_nmea = NULL; static const gchar *sentences[3]; const gchar *gga, *rmc; gint i = 0; - GError *error = NULL; +#if !MM_CHECK_VERSION(1, 18, 0) + g_autoptr(GError) error = NULL; location_nmea = mm_modem_location_get_gps_nmea_finish (modem_location, res, &error); if (error != NULL) { - g_warning ("Failed to get location from NMEA information: %s", - error->message); - g_error_free (error); + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_warning ("Failed to get location from NMEA information: %s", + error->message); + } + return; } +#else + location_nmea = mm_modem_location_get_signaled_gps_nmea (modem_location); +#endif + + manager = GCLUE_MODEM_MANAGER (user_data); + priv = manager->priv; if (location_nmea == NULL) { g_debug ("No NMEA"); @@ -485,29 +571,33 @@ on_get_gps_nmea_ready (GObject *source_object, priv->location_nmea = g_steal_pointer (&location_nmea); } +static void +on_location_changed_get_gps_nmea (GObject *modem_object, + GClueModemManager *manager) +{ +#if MM_CHECK_VERSION(1, 18, 0) + on_get_gps_nmea_ready(modem_object, NULL, manager); +#else + mm_modem_location_get_gps_nmea (MM_MODEM_LOCATION (modem_object), + manager->priv->cancellable, + on_get_gps_nmea_ready, + manager); +#endif +} + static void on_location_changed (GObject *modem_object, GParamSpec *pspec, gpointer user_data) { - MMModemLocation *modem_location = MM_MODEM_LOCATION (modem_object); GClueModemManager *manager = GCLUE_MODEM_MANAGER (user_data); if ((manager->priv->caps & MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI) != 0) - mm_modem_location_get_3gpp (modem_location, - manager->priv->cancellable, - on_get_3gpp_ready, - manager); + on_location_changed_get_3gpp (modem_object, manager); if ((manager->priv->caps & MM_MODEM_LOCATION_SOURCE_CDMA_BS) != 0) - mm_modem_location_get_cdma_bs (modem_location, - manager->priv->cancellable, - on_get_cdma_ready, - manager); + on_location_changed_get_cdma (modem_object, manager); if ((manager->priv->caps & MM_MODEM_LOCATION_SOURCE_GPS_NMEA) != 0) - mm_modem_location_get_gps_nmea (modem_location, - manager->priv->cancellable, - on_get_gps_nmea_ready, - manager); + on_location_changed_get_gps_nmea (modem_object, manager); } static void @@ -516,8 +606,9 @@ on_modem_location_setup (GObject *modem_object, gpointer user_data) { GTask *task = G_TASK (user_data); - GClueModemManager *manager; - GClueModemManagerPrivate *priv; + GClueModemManager *manager = GCLUE_MODEM_MANAGER + (g_task_get_source_object (task)); + GClueModemManagerPrivate *priv = manager->priv; GError *error = NULL; if (!mm_modem_location_setup_finish (MM_MODEM_LOCATION (modem_object), @@ -527,10 +618,11 @@ on_modem_location_setup (GObject *modem_object, goto out; } - manager = GCLUE_MODEM_MANAGER (g_task_get_source_object (task)); - priv = manager->priv; + g_debug ("Modem '%s' setup.", mm_object_get_path (priv->mm_object)); + /* Make sure that we actually emit that signal */ + priv->location_3gpp_ignore_previous = TRUE; on_location_changed (modem_object, NULL, manager); g_task_return_boolean (task, TRUE); @@ -551,8 +643,6 @@ enable_caps (GClueModemManager *manager, priv->caps |= caps; task = g_task_new (manager, cancellable, callback, user_data); - priv = GCLUE_MODEM_MANAGER (g_task_get_source_object (task))->priv; - caps = mm_modem_location_get_enabled (priv->modem_location) | priv->caps; mm_modem_location_setup (priv->modem_location, caps, @@ -611,9 +701,149 @@ modem_has_caps (GClueModemManager *manager, } static void -on_mm_object_added (GDBusObjectManager *object_manager, - GDBusObject *object, - gpointer user_data); +on_enable_agps_ready (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GClueModemManager *manager; + g_autoptr(GError) error = NULL; + + g_return_if_fail (GCLUE_IS_MODEM_MANAGER (source)); + manager = GCLUE_MODEM_MANAGER (source); + + if (!enable_caps_finish (manager, result, &error)) { + g_warning ("Failed to enable assisted GPS: %s", error->message); + /* Clear AGPS caps so that subsequent calls to enable_caps do not fail */ + manager->priv->caps &= ~(MM_MODEM_LOCATION_SOURCE_AGPS_MSB | MM_MODEM_LOCATION_SOURCE_AGPS_MSA); + } +} + +static void +enable_agps (GClueModemManager *manager) +{ + MMModemLocationSource assistance_caps = MM_MODEM_LOCATION_SOURCE_NONE; + + g_return_if_fail ( + gclue_modem_manager_get_is_gps_available (GCLUE_MODEM (manager))); + + if (manager->priv->modem_location + && mm_modem_location_get_supl_server (manager->priv->modem_location) != NULL) { + MMModemLocationSource caps; + + caps = mm_modem_location_get_capabilities (manager->priv->modem_location); + /* Prefer MSB assistance */ + if (caps & MM_MODEM_LOCATION_SOURCE_AGPS_MSB) { + assistance_caps |= MM_MODEM_LOCATION_SOURCE_AGPS_MSB; + g_debug ("Enabing MSB assisted GPS"); + } else if (caps & MM_MODEM_LOCATION_SOURCE_AGPS_MSA) { + assistance_caps |= MM_MODEM_LOCATION_SOURCE_AGPS_MSA; + g_debug ("Enabling MSA assisted GPS"); + } + } + if (assistance_caps == MM_MODEM_LOCATION_SOURCE_NONE) { + g_debug ("Assisted GPS not available"); + return; + } + + enable_caps (manager, + assistance_caps, + manager->priv->cancellable, + on_enable_agps_ready, + manager); +} + +static void +disconnect_modem_location (GClueModemManager *manager) +{ + GClueModemManagerPrivate *priv = manager->priv; + + if (!priv->modem_location) { + return; + } + + g_signal_handlers_disconnect_by_func (G_OBJECT (priv->modem_location), + G_CALLBACK (on_location_changed), + manager); + + g_clear_object (&priv->modem_location); +} + +static void +on_gps_refresh_rate_set (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GError) error = NULL; + + mm_modem_location_set_gps_refresh_rate_finish + (MM_MODEM_LOCATION (source_object), res, &error); + if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_warning ("Failed to set GPS refresh rate: %s", + error->message); + /* TODO: try selecting better modem if GPS is unsupported? */ + } +} + +static gboolean +try_modem_location (GClueModemManager *manager, + MMObject *mm_object) +{ + const char *path = mm_object_get_path (mm_object); + g_autoptr(MMModemLocation) modem_location = NULL; + + modem_location = mm_object_get_modem_location (mm_object); + if (modem_location == NULL) { + g_debug ("Modem '%s' does not have location capabilities", path); + return FALSE; + } + + /* TODO: check that modem actually has some usable capabilities, like GNSS */ + g_debug ("Modem '%s' has location capabilities", path); + + g_assert (!manager->priv->modem_location); + manager->priv->modem_location = g_object_ref (modem_location); + + mm_modem_location_set_gps_refresh_rate (manager->priv->modem_location, + manager->priv->time_threshold, + manager->priv->cancellable, + on_gps_refresh_rate_set, + NULL); + + g_signal_connect (G_OBJECT (manager->priv->modem_location), + "notify::location", + G_CALLBACK (on_location_changed), + manager); + + return TRUE; +} + +static void +try_modem (GClueModemManager *manager, + MMObject *mm_object, + MMModem *mm_modem, + gboolean modem_is_enabled) +{ + const char *path = mm_object_get_path (mm_object); + + if (!try_modem_location (manager, + mm_object)) { + return; + } + + manager->priv->mm_object = g_object_ref (mm_object); + manager->priv->modem = g_object_ref (mm_modem); + + /* Has to be done after setting up manager->priv objects */ + g_object_notify_by_pspec (G_OBJECT (manager), gParamSpecs[PROP_IS_GPS_AVAILABLE]); + + if (modem_is_enabled) { + g_object_notify_by_pspec (G_OBJECT (manager), gParamSpecs[PROP_IS_3G_AVAILABLE]); + g_object_notify_by_pspec (G_OBJECT (manager), gParamSpecs[PROP_IS_CDMA_AVAILABLE]); + enable_agps (manager); + } else { + g_debug ("3G or CDMA are not available on non-enabled modem '%s'", path); + } +} static void on_mm_modem_state_notify (GObject *gobject, @@ -625,46 +855,43 @@ on_mm_modem_state_notify (GObject *gobject, GClueModemManagerPrivate *priv = manager->priv; GDBusObjectManager *obj_manager = G_DBUS_OBJECT_MANAGER (priv->manager); const char *path = mm_modem_get_path (mm_modem); - GDBusObject *object; - - if (priv->mm_object != NULL) { - // In the meantime another modem with location caps was found. - g_signal_handlers_disconnect_by_func (mm_modem, - on_mm_modem_state_notify, - user_data); - g_object_unref (gobject); - - return; - } + g_autoptr(MMObject) mm_object = NULL; if (mm_modem_get_state (mm_modem) < MM_MODEM_STATE_ENABLED) return; - g_debug ("Modem '%s' now enabled", path); - g_signal_handlers_disconnect_by_func (mm_modem, on_mm_modem_state_notify, user_data); - object = g_dbus_object_manager_get_object (obj_manager, path); - on_mm_object_added (obj_manager, object, user_data); - g_object_unref (mm_modem); -} + if (priv->modem != NULL && priv->modem != mm_modem) { + g_debug ("Ignoring enabled modem '%s' as already have another one", + path); + return; + } -static void -on_gps_refresh_rate_set (GObject *source_object, - GAsyncResult *res, - gpointer user_data) -{ - gboolean ret; - GError *error = NULL; + g_debug ("Modem '%s' now enabled", path); - ret = mm_modem_location_set_gps_refresh_rate_finish - (MM_MODEM_LOCATION (source_object), res, &error); - if (!ret) { - g_warning ("Failed to set GPS refresh rate: %s", - error->message); - g_error_free (error); + mm_object = MM_OBJECT (g_dbus_object_manager_get_object (obj_manager, path)); + g_assert (mm_object); + + if (priv->mm_object == NULL) { + try_modem (manager, mm_object, mm_modem, TRUE); + } else { + /* MM re-initializes the location interface so we have to re-connect */ + disconnect_modem_location (manager); + if (!try_modem_location (manager, mm_object)) { + /* Notify that sadly GPS is no longer available */ + g_object_notify_by_pspec (G_OBJECT (manager), gParamSpecs[PROP_IS_GPS_AVAILABLE]); + /* TODO: try next modem */ + return; + } + + g_debug ("Enabling 3G and CDMA location on modem '%s'", path); + g_assert (manager->priv->modem_location); + g_object_notify_by_pspec (G_OBJECT (manager), gParamSpecs[PROP_IS_3G_AVAILABLE]); + g_object_notify_by_pspec (G_OBJECT (manager), gParamSpecs[PROP_IS_CDMA_AVAILABLE]); + enable_agps (manager); } } @@ -675,52 +902,38 @@ on_mm_object_added (GDBusObjectManager *object_manager, { MMObject *mm_object = MM_OBJECT (object); GClueModemManager *manager = GCLUE_MODEM_MANAGER (user_data); - MMModem *mm_modem; - MMModemLocation *modem_location; + g_autoptr(MMModem) mm_modem = NULL; + const char *path = mm_object_get_path (mm_object); + gboolean modem_is_enabled; - if (manager->priv->mm_object != NULL) + if (manager->priv->mm_object != NULL) { + g_debug ("New modem '%s' but already have one", path); return; + } + + if (g_hash_table_lookup (manager->priv->modems_not_enabled, path)) { + g_warning ("New modem '%s' notification for an existing non-enabled modem", + path); + return; + } - g_debug ("New modem '%s'", mm_object_get_path (mm_object)); + g_debug ("New modem '%s'", path); mm_modem = mm_object_get_modem (mm_object); - if (mm_modem_get_state (mm_modem) < MM_MODEM_STATE_ENABLED) { - g_debug ("Modem '%s' not enabled", - mm_object_get_path (mm_object)); + modem_is_enabled = mm_modem_get_state (mm_modem) >= MM_MODEM_STATE_ENABLED; + if (!modem_is_enabled) { + g_debug ("Modem '%s' not enabled", path); + + g_hash_table_insert (manager->priv->modems_not_enabled, + g_strdup (path), g_object_ref (mm_modem)); g_signal_connect_object (mm_modem, "notify::state", G_CALLBACK (on_mm_modem_state_notify), manager, 0); - - return; } - modem_location = mm_object_peek_modem_location (mm_object); - if (modem_location == NULL) - return; - - g_debug ("Modem '%s' has location capabilities", - mm_object_get_path (mm_object)); - - manager->priv->mm_object = g_object_ref (mm_object); - manager->priv->modem = mm_modem; - manager->priv->modem_location = mm_object_get_modem_location (mm_object); - - mm_modem_location_set_gps_refresh_rate (manager->priv->modem_location, - manager->priv->time_threshold, - manager->priv->cancellable, - on_gps_refresh_rate_set, - NULL); - - g_signal_connect (G_OBJECT (manager->priv->modem_location), - "notify::location", - G_CALLBACK (on_location_changed), - manager); - - g_object_notify_by_pspec (G_OBJECT (manager), gParamSpecs[PROP_IS_3G_AVAILABLE]); - g_object_notify_by_pspec (G_OBJECT (manager), gParamSpecs[PROP_IS_CDMA_AVAILABLE]); - g_object_notify_by_pspec (G_OBJECT (manager), gParamSpecs[PROP_IS_GPS_AVAILABLE]); + try_modem (manager, mm_object, mm_modem, modem_is_enabled); } static void @@ -731,21 +944,34 @@ on_mm_object_removed (GDBusObjectManager *object_manager, MMObject *mm_object = MM_OBJECT (object); GClueModemManager *manager = GCLUE_MODEM_MANAGER (user_data); GClueModemManagerPrivate *priv = manager->priv; + const char *path = mm_object_get_path (priv->mm_object); + + g_hash_table_remove (manager->priv->modems_not_enabled, path); - if (priv->mm_object == NULL || priv->mm_object != mm_object) + if (priv->mm_object == NULL || priv->mm_object != mm_object) { + g_debug ("Unused modem '%s' removed.", path); return; - g_debug ("Modem '%s' removed.", mm_object_get_path (priv->mm_object)); + } + g_debug ("Modem '%s' removed.", path); - g_signal_handlers_disconnect_by_func (G_OBJECT (priv->modem_location), - G_CALLBACK (on_location_changed), + clear_3gpp_location (manager); + + disconnect_modem_location (manager); + + g_signal_handlers_disconnect_by_func (G_OBJECT (priv->modem), + on_mm_modem_state_notify, user_data); + g_clear_object (&priv->mm_object); g_clear_object (&priv->modem); - g_clear_object (&priv->modem_location); + + priv->caps = 0; g_object_notify_by_pspec (G_OBJECT (manager), gParamSpecs[PROP_IS_3G_AVAILABLE]); g_object_notify_by_pspec (G_OBJECT (manager), gParamSpecs[PROP_IS_CDMA_AVAILABLE]); g_object_notify_by_pspec (G_OBJECT (manager), gParamSpecs[PROP_IS_GPS_AVAILABLE]); + + /* TODO: try next modem */ } static void @@ -753,19 +979,27 @@ on_manager_new_ready (GObject *modem_object, GAsyncResult *res, gpointer user_data) { - GClueModemManagerPrivate *priv = GCLUE_MODEM_MANAGER (user_data)->priv; + MMManager *mmmanager; + GClueModemManager *manager; + GClueModemManagerPrivate *priv; GList *objects, *node; - GError *error = NULL; - - priv->manager = mm_manager_new_finish (res, &error); - if (priv->manager == NULL) { - g_warning ("Failed to connect to ModemManager: %s", - error->message); - g_error_free (error); + g_autoptr(GError) error = NULL; + + mmmanager = mm_manager_new_finish (res, &error); + if (mmmanager == NULL) { + if (error && !g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { + g_warning ("Failed to connect to ModemManager: %s", + error->message); + } return; } + manager = GCLUE_MODEM_MANAGER (user_data); + priv = manager->priv; + priv->manager = mmmanager; + objects = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (priv->manager)); for (node = objects; node != NULL; node = node->next) { @@ -779,15 +1013,15 @@ on_manager_new_ready (GObject *modem_object, } g_list_free_full (objects, g_object_unref); - g_signal_connect (G_OBJECT (priv->manager), - "object-added", - G_CALLBACK (on_mm_object_added), - user_data); + g_signal_connect_object (G_OBJECT (priv->manager), + "object-added", + G_CALLBACK (on_mm_object_added), + manager, 0); - g_signal_connect (G_OBJECT (priv->manager), - "object-removed", - G_CALLBACK (on_mm_object_removed), - user_data); + g_signal_connect_object (G_OBJECT (priv->manager), + "object-removed", + G_CALLBACK (on_mm_object_removed), + manager, 0); } static void @@ -795,19 +1029,22 @@ on_bus_get_ready (GObject *modem_object, GAsyncResult *res, gpointer user_data) { - GClueModemManagerPrivate *priv = GCLUE_MODEM_MANAGER (user_data)->priv; - GDBusConnection *connection; - GError *error = NULL; + GClueModemManagerPrivate *priv; + g_autoptr(GDBusConnection) connection = NULL; + g_autoptr(GError) error = NULL; connection = g_bus_get_finish (res, &error); if (connection == NULL) { - g_warning ("Failed to connect to system D-Bus: %s", - error->message); - g_error_free (error); + if (error && !g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { + g_warning ("Failed to connect to system D-Bus: %s", + error->message); + } return; } + priv = GCLUE_MODEM_MANAGER (user_data)->priv; mm_manager_new (connection, 0, priv->cancellable, @@ -834,6 +1071,10 @@ static void gclue_modem_manager_init (GClueModemManager *manager) { manager->priv = gclue_modem_manager_get_instance_private (manager); + manager->priv->modems_not_enabled = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); } static void @@ -872,18 +1113,22 @@ static gboolean gclue_modem_manager_get_is_3g_available (GClueModem *modem) { g_return_val_if_fail (GCLUE_IS_MODEM_MANAGER (modem), FALSE); + GClueModemManager *manager = GCLUE_MODEM_MANAGER (modem); - return modem_has_caps (GCLUE_MODEM_MANAGER (modem), - MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI); + return manager->priv->modem != NULL + && mm_modem_get_state (manager->priv->modem) >= MM_MODEM_STATE_ENABLED + && modem_has_caps (manager, MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI); } static gboolean gclue_modem_manager_get_is_cdma_available (GClueModem *modem) { g_return_val_if_fail (GCLUE_IS_MODEM_MANAGER (modem), FALSE); + GClueModemManager *manager = GCLUE_MODEM_MANAGER (modem); - return modem_has_caps (GCLUE_MODEM_MANAGER (modem), - MM_MODEM_LOCATION_SOURCE_CDMA_BS); + return manager->priv->modem != NULL + && mm_modem_get_state (manager->priv->modem) >= MM_MODEM_STATE_ENABLED + && modem_has_caps (manager, MM_MODEM_LOCATION_SOURCE_CDMA_BS); } static gboolean @@ -992,29 +1237,11 @@ 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 | assistance_caps, + MM_MODEM_LOCATION_SOURCE_GPS_NMEA, cancellable, callback, user_data); @@ -1043,7 +1270,7 @@ gclue_modem_manager_disable_3g (GClueModem *modem, g_return_val_if_fail (gclue_modem_manager_get_is_3g_available (modem), FALSE); manager = GCLUE_MODEM_MANAGER (modem); - g_clear_object (&manager->priv->location_3gpp); + clear_3gpp_location (manager); g_debug ("Clearing 3GPP location caps from modem"); return clear_caps (manager, MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI, @@ -1062,7 +1289,7 @@ gclue_modem_manager_disable_cdma (GClueModem *modem, g_return_val_if_fail (gclue_modem_manager_get_is_cdma_available (modem), FALSE); manager = GCLUE_MODEM_MANAGER (modem); - g_clear_object (&manager->priv->location_3gpp); + clear_3gpp_location (manager); g_debug ("Clearing CDMA location caps from modem"); return clear_caps (manager, MM_MODEM_LOCATION_SOURCE_CDMA_BS, @@ -1077,22 +1304,15 @@ 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 | assistance_caps, + MM_MODEM_LOCATION_SOURCE_GPS_NMEA, cancellable, error); } diff --git a/src/gclue-mozilla.c b/src/gclue-mozilla.c index 8e602c13983ed60c63898a88bab6ce02e67dbcd6..9e8feb1db562975568f1205aa33c2fe94fe9b107 100644 --- a/src/gclue-mozilla.c +++ b/src/gclue-mozilla.c @@ -25,8 +25,10 @@ #include <string.h> #include <config.h> #include "gclue-mozilla.h" +#include "gclue-3g-tower.h" #include "gclue-config.h" #include "gclue-error.h" +#include "gclue-wifi.h" /** * SECTION:gclue-mozilla @@ -40,6 +42,22 @@ * its easy to switch to Google's API. **/ +struct _GClueMozillaPrivate +{ + GClueWifi *wifi; + + GClue3GTower tower; + gboolean tower_valid; + gboolean tower_submitted; + + gboolean bss_submitted; +}; + +G_DEFINE_TYPE_WITH_CODE (GClueMozilla, + gclue_mozilla, + G_TYPE_OBJECT, + G_ADD_PRIVATE (GClueMozilla)) + #define BSSID_LEN 6 #define BSSID_STR_LEN 17 #define MAX_SSID_LEN 32 @@ -102,12 +120,10 @@ get_bssid_from_bss (WPABSS *bss, char *bssid) return TRUE; } -static const char * -get_url (void) +const char * +gclue_mozilla_get_locate_url (GClueMozilla *mozilla) { - GClueConfig *config; - - config = gclue_config_get_singleton (); + GClueConfig *config = gclue_config_get_singleton (); return gclue_config_get_wifi_url (config); } @@ -136,25 +152,58 @@ error: return FALSE; } +static gboolean +towertec_to_radiotype (GClueTowerTec tec, + const char **radiotype_p) +{ + switch (tec) { + case GCLUE_TOWER_TEC_2G: + *radiotype_p = "gsm"; + break; + case GCLUE_TOWER_TEC_3G: + *radiotype_p = "wcdma"; + break; + case GCLUE_TOWER_TEC_4G: + *radiotype_p = "lte"; + break; + default: + *radiotype_p = NULL; + return FALSE; + } + + return TRUE; +} + +#define USER_AGENT (PACKAGE_NAME "/" PACKAGE_VERSION) + SoupMessage * -gclue_mozilla_create_query (GList *bss_list, /* As in Access Points */ - GClue3GTower *tower, +gclue_mozilla_create_query (GClueMozilla *mozilla, + gboolean skip_tower, + gboolean skip_bss, + const char **query_data_description, GError **error) { + gboolean has_tower = FALSE, has_bss = FALSE; SoupMessage *ret = NULL; + SoupMessageHeaders *request_headers; JsonBuilder *builder; + g_autoptr(GList) bss_list = NULL; JsonGenerator *generator; JsonNode *root_node; char *data; gsize data_len; - const char *uri; + const char *uri, *radiotype; guint n_non_ignored_bsss; GList *iter; gint64 mcc, mnc; + g_autoptr(GBytes) body = NULL; builder = json_builder_new (); json_builder_begin_object (builder); + if (mozilla->priv->wifi && !skip_bss) { + bss_list = gclue_wifi_get_bss_list (mozilla->priv->wifi); + } /* We send pure geoip query using empty object if both bss_list and * tower are NULL. * @@ -172,11 +221,11 @@ gclue_mozilla_create_query (GList *bss_list, /* As in Access Points */ n_non_ignored_bsss++; } - if (tower != NULL && - operator_code_to_mcc_mnc (tower->opc, &mcc, &mnc)) { - + if (mozilla->priv->tower_valid && !skip_tower && + towertec_to_radiotype (mozilla->priv->tower.tec, &radiotype) && + operator_code_to_mcc_mnc (mozilla->priv->tower.opc, &mcc, &mnc)) { json_builder_set_member_name (builder, "radioType"); - json_builder_add_string_value (builder, "gsm"); + json_builder_add_string_value (builder, radiotype); json_builder_set_member_name (builder, "cellTowers"); json_builder_begin_array (builder); @@ -184,21 +233,21 @@ gclue_mozilla_create_query (GList *bss_list, /* As in Access Points */ json_builder_begin_object (builder); json_builder_set_member_name (builder, "cellId"); - json_builder_add_int_value (builder, tower->cell_id); + json_builder_add_int_value (builder, mozilla->priv->tower.cell_id); json_builder_set_member_name (builder, "mobileCountryCode"); json_builder_add_int_value (builder, mcc); json_builder_set_member_name (builder, "mobileNetworkCode"); 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_add_int_value (builder, mozilla->priv->tower.lac); + json_builder_set_member_name (builder, "radioType"); + json_builder_add_string_value (builder, radiotype); json_builder_end_object (builder); json_builder_end_array (builder); + + has_tower = TRUE; } if (n_non_ignored_bsss >= 2) { @@ -209,11 +258,13 @@ gclue_mozilla_create_query (GList *bss_list, /* As in Access Points */ WPABSS *bss = WPA_BSS (iter->data); char mac[BSSID_STR_LEN + 1] = { 0 }; gint16 strength_dbm; + guint age_ms; if (gclue_mozilla_should_ignore_bss (bss)) continue; json_builder_begin_object (builder); + json_builder_set_member_name (builder, "macAddress"); get_bssid_from_bss (bss, mac); json_builder_add_string_value (builder, mac); @@ -221,7 +272,13 @@ gclue_mozilla_create_query (GList *bss_list, /* As in Access Points */ json_builder_set_member_name (builder, "signalStrength"); strength_dbm = wpa_bss_get_signal (bss); json_builder_add_int_value (builder, strength_dbm); + + json_builder_set_member_name (builder, "age"); + age_ms = 1000 * wpa_bss_get_age (bss); + json_builder_add_int_value (builder, age_ms); + json_builder_end_object (builder); + has_bss = TRUE; } json_builder_end_array (builder); } @@ -236,15 +293,26 @@ gclue_mozilla_create_query (GList *bss_list, /* As in Access Points */ g_object_unref (builder); g_object_unref (generator); - uri = get_url (); + uri = gclue_mozilla_get_locate_url (mozilla); ret = soup_message_new ("POST", uri); - soup_message_set_request (ret, - "application/json", - SOUP_MEMORY_TAKE, - data, - data_len); + request_headers = soup_message_get_request_headers (ret); + soup_message_headers_append (request_headers, "User-Agent", USER_AGENT); + 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); + if (query_data_description) { + if (has_tower && has_bss) { + *query_data_description = "3GPP + WiFi"; + } else if (has_tower) { + *query_data_description = "3GPP"; + } else if (has_bss) { + *query_data_description = "WiFi"; + } else { + *query_data_description = "GeoIP"; + } + } + return ret; } @@ -252,28 +320,32 @@ static gboolean parse_server_error (JsonObject *object, GError **error) { JsonObject *error_obj; - int code; const char *message; if (!json_object_has_member (object, "error")) return FALSE; error_obj = json_object_get_object_member (object, "error"); - code = json_object_get_int_member (error_obj, "code"); - message = json_object_get_string_member (error_obj, "message"); + if (json_object_has_member (error_obj, "message")) { + message = json_object_get_string_member (error_obj, "message"); + } else { + message = "Unknown error"; + } - g_set_error_literal (error, G_IO_ERROR, code, message); + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, message); return TRUE; } GClueLocation * gclue_mozilla_parse_response (const char *json, + const char *location_description, GError **error) { - JsonParser *parser; + g_autoptr(JsonParser) parser = NULL; JsonNode *node; JsonObject *object, *loc_object; + g_autofree char *desc_new = NULL; GClueLocation *location; gdouble latitude, longitude, accuracy; @@ -288,54 +360,78 @@ gclue_mozilla_parse_response (const char *json, if (parse_server_error (object, error)) return NULL; + if (json_object_has_member (object, "fallback")) { + const char *fallback; + + fallback = json_object_get_string_member (object, "fallback"); + if (fallback && strlen (fallback)) { + desc_new = g_strdup_printf ("%s fallback (from %s data)", + fallback, location_description); + location_description = desc_new; + } + } + loc_object = json_object_get_object_member (object, "location"); latitude = json_object_get_double_member (loc_object, "lat"); longitude = json_object_get_double_member (loc_object, "lng"); accuracy = json_object_get_double_member (object, "accuracy"); - location = gclue_location_new (latitude, longitude, accuracy); - - g_object_unref (parser); + location = gclue_location_new (latitude, longitude, accuracy, + location_description); return location; } -static const char * -get_submit_config (const char **nick) +const char * +gclue_mozilla_get_submit_url (GClueMozilla *mozilla) { - GClueConfig *config; + GClueConfig *config = gclue_config_get_singleton (); - config = gclue_config_get_singleton (); - if (!gclue_config_get_wifi_submit_data (config)) + if (gclue_config_get_wifi_submit_data (config)) + return gclue_config_get_wifi_submit_url (config); + else return NULL; - - *nick = gclue_config_get_wifi_submit_nick (config); - - return gclue_config_get_wifi_submit_url (config); } SoupMessage * -gclue_mozilla_create_submit_query (GClueLocation *location, - GList *bss_list, /* As in Access Points */ - GClue3GTower *tower, +gclue_mozilla_create_submit_query (GClueMozilla *mozilla, + GClueLocation *location, GError **error) { SoupMessage *ret = NULL; + SoupMessageHeaders *request_headers; JsonBuilder *builder; JsonGenerator *generator; JsonNode *root_node; - char *data, *timestr; - const char *url, *nick; + char *data; + g_autoptr(GList) bss_list = NULL; + const char *url, *nick, *radiotype; gsize data_len; GList *iter; - gdouble lat, lon, accuracy, altitude; - GDateTime *datetime; + gdouble lat, lon, accuracy, altitude, speed; + guint64 time_ms; gint64 mcc, mnc; + GClueConfig *config; + g_autoptr(GBytes) body = NULL; + + if (mozilla->priv->bss_submitted && + (!mozilla->priv->tower_valid || + mozilla->priv->tower_submitted)) + { + g_debug ("Already created submit req for this data (bss submitted %d; tower: valid %d submitted %d)", + (int)mozilla->priv->bss_submitted, + (int)mozilla->priv->tower_valid, + (int)mozilla->priv->tower_submitted); + goto out; + } - url = get_submit_config (&nick); + + url = gclue_mozilla_get_submit_url (mozilla); if (url == NULL) goto out; + config = gclue_config_get_singleton (); + nick = gclue_config_get_wifi_submit_nick (config); builder = json_builder_new (); json_builder_begin_object (builder); @@ -345,12 +441,19 @@ gclue_mozilla_create_submit_query (GClueLocation *location, json_builder_begin_object (builder); + json_builder_set_member_name (builder, "timestamp"); + time_ms = 1000 * gclue_location_get_timestamp (location); + json_builder_add_int_value (builder, time_ms); + + json_builder_set_member_name (builder, "position"); + json_builder_begin_object (builder); + lat = gclue_location_get_latitude (location); - json_builder_set_member_name (builder, "lat"); + json_builder_set_member_name (builder, "latitude"); json_builder_add_double_value (builder, lat); lon = gclue_location_get_longitude (location); - json_builder_set_member_name (builder, "lon"); + json_builder_set_member_name (builder, "longitude"); json_builder_add_double_value (builder, lon); accuracy = gclue_location_get_accuracy (location); @@ -365,22 +468,19 @@ gclue_mozilla_create_submit_query (GClueLocation *location, json_builder_add_double_value (builder, altitude); } - 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, timestr); - g_free (timestr); - g_date_time_unref (datetime); + speed = gclue_location_get_speed (location); + if (speed != GCLUE_LOCATION_SPEED_UNKNOWN) { + json_builder_set_member_name (builder, "speed"); + json_builder_add_double_value (builder, speed); + } - json_builder_set_member_name (builder, "radioType"); - json_builder_add_string_value (builder, "gsm"); + json_builder_end_object (builder); /* position */ + if (mozilla->priv->wifi) { + bss_list = gclue_wifi_get_bss_list (mozilla->priv->wifi); + } if (bss_list != NULL) { - json_builder_set_member_name (builder, "wifi"); + json_builder_set_member_name (builder, "wifiAccessPoints"); json_builder_begin_array (builder); for (iter = bss_list; iter != NULL; iter = iter->next) { @@ -388,50 +488,57 @@ gclue_mozilla_create_submit_query (GClueLocation *location, char mac[BSSID_STR_LEN + 1] = { 0 }; gint16 strength_dbm; guint16 frequency; + guint age_ms; if (gclue_mozilla_should_ignore_bss (bss)) continue; json_builder_begin_object (builder); - json_builder_set_member_name (builder, "key"); + + json_builder_set_member_name (builder, "macAddress"); get_bssid_from_bss (bss, mac); json_builder_add_string_value (builder, mac); - json_builder_set_member_name (builder, "signal"); + json_builder_set_member_name (builder, "signalStrength"); strength_dbm = wpa_bss_get_signal (bss); json_builder_add_int_value (builder, strength_dbm); json_builder_set_member_name (builder, "frequency"); frequency = wpa_bss_get_frequency (bss); json_builder_add_int_value (builder, frequency); + + json_builder_set_member_name (builder, "age"); + age_ms = 1000 * wpa_bss_get_age (bss); + json_builder_add_int_value (builder, age_ms); + json_builder_end_object (builder); } - json_builder_end_array (builder); /* wifi */ + json_builder_end_array (builder); /* wifiAccessPoints */ } - if (tower != NULL && - operator_code_to_mcc_mnc (tower->opc, &mcc, &mnc)) { - - json_builder_set_member_name (builder, "cell"); + if (mozilla->priv->tower_valid && + towertec_to_radiotype (mozilla->priv->tower.tec, &radiotype) && + operator_code_to_mcc_mnc (mozilla->priv->tower.opc, &mcc, &mnc)) { + json_builder_set_member_name (builder, "cellTowers"); json_builder_begin_array (builder); json_builder_begin_object (builder); - json_builder_set_member_name (builder, "radio"); - json_builder_add_string_value (builder, "gsm"); - 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_set_member_name (builder, "radioType"); + json_builder_add_string_value (builder, radiotype); + json_builder_set_member_name (builder, "cellId"); + json_builder_add_int_value (builder, mozilla->priv->tower.cell_id); + json_builder_set_member_name (builder, "mobileCountryCode"); json_builder_add_int_value (builder, mcc); - json_builder_set_member_name (builder, "mnc"); + json_builder_set_member_name (builder, "mobileNetworkCode"); json_builder_add_int_value (builder, mnc); - json_builder_set_member_name (builder, "lac"); - json_builder_add_int_value (builder, tower->lac); + json_builder_set_member_name (builder, "locationAreaCode"); + json_builder_add_int_value (builder, mozilla->priv->tower.lac); json_builder_end_object (builder); - json_builder_end_array (builder); /* cell */ + json_builder_end_array (builder); /* cellTowers */ } json_builder_end_object (builder); @@ -448,17 +555,19 @@ 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); + soup_message_headers_append (request_headers, "User-Agent", USER_AGENT); 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); + mozilla->priv->bss_submitted = TRUE; + mozilla->priv->tower_submitted = TRUE; + out: return ret; } @@ -485,3 +594,134 @@ gclue_mozilla_should_ignore_bss (WPABSS *bss) return FALSE; } + +static void +gclue_mozilla_finalize (GObject *object) +{ + GClueMozilla *mozilla = GCLUE_MOZILLA (object); + + g_clear_weak_pointer (&mozilla->priv->wifi); + + G_OBJECT_CLASS (gclue_mozilla_parent_class)->finalize (object); +} + +static void +gclue_mozilla_init (GClueMozilla *mozilla) +{ + mozilla->priv = gclue_mozilla_get_instance_private (mozilla); + mozilla->priv->wifi = NULL; + mozilla->priv->tower_valid = FALSE; + mozilla->priv->bss_submitted = FALSE; +} + +static void +gclue_mozilla_class_init (GClueMozillaClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gclue_mozilla_finalize; +} + +GClueMozilla * +gclue_mozilla_get_singleton (void) +{ + static GClueMozilla *mozilla = NULL; + + if (!mozilla) { + mozilla = g_object_new (GCLUE_TYPE_MOZILLA, NULL); + g_object_add_weak_pointer (G_OBJECT (mozilla), (gpointer) &mozilla); + } else + g_object_ref (mozilla); + + return mozilla; +} + +void +gclue_mozilla_set_wifi (GClueMozilla *mozilla, + GClueWifi *wifi) +{ + g_return_if_fail (GCLUE_IS_MOZILLA (mozilla)); + + if (mozilla->priv->wifi == wifi) + return; + + g_clear_weak_pointer (&mozilla->priv->wifi); + + if (!wifi) { + return; + } + + mozilla->priv->wifi = wifi; + g_object_add_weak_pointer (G_OBJECT (mozilla->priv->wifi), + (gpointer) &mozilla->priv->wifi); +} + +gboolean +gclue_mozilla_test_set_wifi (GClueMozilla *mozilla, + GClueWifi *old, GClueWifi *new) +{ + if (mozilla->priv->wifi != old) + return FALSE; + + gclue_mozilla_set_wifi (mozilla, new); + return TRUE; +} + +void +gclue_mozilla_set_bss_dirty (GClueMozilla *mozilla) +{ + g_return_if_fail (GCLUE_IS_MOZILLA (mozilla)); + + mozilla->priv->bss_submitted = FALSE; +} + +static gboolean gclue_mozilla_tower_identical (const GClue3GTower *t1, + const GClue3GTower *t2) +{ + return g_strcmp0 (t1->opc, t2->opc) == 0 && t1->lac == t2->lac && + t1->cell_id == t2->cell_id && t1->tec == t2->tec; +} + +void +gclue_mozilla_set_tower (GClueMozilla *mozilla, + const GClue3GTower *tower) +{ + g_return_if_fail (GCLUE_IS_MOZILLA (mozilla)); + + if (!tower || + !(tower->tec > GCLUE_TOWER_TEC_UNKNOWN + && tower->tec <= GCLUE_TOWER_TEC_MAX_VALID)) { + mozilla->priv->tower_valid = FALSE; + return; + } + + if (mozilla->priv->tower_valid && + mozilla->priv->tower_submitted) { + mozilla->priv->tower_submitted = + gclue_mozilla_tower_identical (&mozilla->priv->tower, + tower); + } else + mozilla->priv->tower_submitted = FALSE; + + mozilla->priv->tower = *tower; + mozilla->priv->tower_valid = TRUE; +} + +gboolean +gclue_mozilla_has_tower (GClueMozilla *mozilla) +{ + g_return_val_if_fail (GCLUE_IS_MOZILLA (mozilla), FALSE); + + return mozilla->priv->tower_valid; +} + +GClue3GTower * +gclue_mozilla_get_tower (GClueMozilla *mozilla) +{ + g_return_val_if_fail (GCLUE_IS_MOZILLA (mozilla), NULL); + + if (!mozilla->priv->tower_valid) + return NULL; + + return &mozilla->priv->tower; +} diff --git a/src/gclue-mozilla.h b/src/gclue-mozilla.h index db859e532da11a3b04240a27ff4cfaeb45c40103..a8010b04668f840ef25e2dfc57144cc67c4ded53 100644 --- a/src/gclue-mozilla.h +++ b/src/gclue-mozilla.h @@ -30,21 +30,72 @@ G_BEGIN_DECLS +#define GCLUE_TYPE_MOZILLA (gclue_mozilla_get_type()) +#define GCLUE_MOZILLA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCLUE_TYPE_MOZILLA, GClueMozilla)) +#define GCLUE_MOZILLA_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCLUE_TYPE_MOZILLA, GClueMozilla const)) +#define GCLUE_MOZILLA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GCLUE_TYPE_MOZILLA, GClueMozillaClass)) +#define GCLUE_IS_MOZILLA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GCLUE_TYPE_MOZILLA)) +#define GCLUE_IS_MOZILLA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GCLUE_TYPE_MOZILLA)) +#define GCLUE_MOZILLA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GCLUE_TYPE_MOZILLA, GClueMozillaClass)) + +typedef struct _GClueMozilla GClueMozilla; +typedef struct _GClueMozillaClass GClueMozillaClass; +typedef struct _GClueMozillaPrivate GClueMozillaPrivate; + +struct _GClueMozilla +{ + GObject parent; + + /*< private >*/ + GClueMozillaPrivate *priv; +}; + +struct _GClueMozillaClass +{ + GObjectClass parent_class; +}; + +struct GClueWifi; +typedef struct _GClueWifi GClueWifi; + +GType gclue_mozilla_get_type (void) G_GNUC_CONST; + +GClueMozilla *gclue_mozilla_get_singleton (void); + +void gclue_mozilla_set_wifi (GClueMozilla *mozilla, + GClueWifi *wifi); +gboolean +gclue_mozilla_test_set_wifi (GClueMozilla *mozilla, + GClueWifi *old, GClueWifi *new); +void gclue_mozilla_set_bss_dirty (GClueMozilla *mozilla); + +void gclue_mozilla_set_tower (GClueMozilla *mozilla, + const GClue3GTower *tower); +gboolean +gclue_mozilla_has_tower (GClueMozilla *mozilla); +GClue3GTower * +gclue_mozilla_get_tower (GClueMozilla *mozilla); + SoupMessage * -gclue_mozilla_create_query (GList *bss_list, /* As in Access Points */ - GClue3GTower *tower, +gclue_mozilla_create_query (GClueMozilla *mozilla, + gboolean skip_tower, + gboolean skip_bss, + const char **query_data_description, GError **error); GClueLocation * gclue_mozilla_parse_response (const char *json, + const char *location_description, GError **error); SoupMessage * -gclue_mozilla_create_submit_query (GClueLocation *location, - GList *bss_list, /* As in Access Points */ - GClue3GTower *tower, +gclue_mozilla_create_submit_query (GClueMozilla *mozilla, + GClueLocation *location, GError **error); gboolean gclue_mozilla_should_ignore_bss (WPABSS *bss); +const char *gclue_mozilla_get_locate_url (GClueMozilla *mozilla); +const char *gclue_mozilla_get_submit_url (GClueMozilla *mozilla); + G_END_DECLS #endif /* GCLUE_MOZILLA_H */ diff --git a/src/gclue-nmea-source.c b/src/gclue-nmea-source.c index 68dbf298aacab3db3c2a0bd8a2a413d64e79c959..13ada66c56362b9039f9c8e2c99e56a4cd24d889 100644 --- a/src/gclue-nmea-source.c +++ b/src/gclue-nmea-source.c @@ -22,11 +22,13 @@ */ #include <stdlib.h> +#include <string.h> #include <glib.h> #include "gclue-config.h" +#include "gclue-location.h" #include "gclue-nmea-utils.h" #include "gclue-nmea-source.h" -#include "gclue-location.h" +#include "gclue-utils.h" #include "config.h" #include "gclue-enum-types.h" @@ -37,21 +39,35 @@ #include <avahi-glib/glib-watch.h> #include <gio/gunixsocketaddress.h> +/* Once we run out of NMEA services to try how long to wait + * until retrying all of them. + * In seconds. + */ +#define SERVICE_UNBREAK_TIME 5 + typedef struct AvahiServiceInfo AvahiServiceInfo; struct _GClueNMEASourcePrivate { GSocketConnection *connection; + GDataInputStream *input_stream; GSocketClient *client; GCancellable *cancellable; + AvahiGLibPoll *glib_poll; + AvahiClient *avahi_client; AvahiServiceInfo *active_service; - /* List of all services but only the most accurate one is used. */ - GList *all_services; + /* List of services to try but only the most accurate one is used. */ + GList *try_services; + + /* List of known-broken services. */ + GList *broken_services; + + guint accuracy_refresh_source, unbreak_timer; }; G_DEFINE_TYPE_WITH_CODE (GClueNMEASource, @@ -65,16 +81,15 @@ static GClueLocationSourceStopResult gclue_nmea_source_stop (GClueLocationSource *source); static void -connect_to_service (GClueNMEASource *source); -static void -disconnect_from_service (GClueNMEASource *source); +try_connect_to_service (GClueNMEASource *source); struct AvahiServiceInfo { char *identifier; char *host_name; + gboolean is_socket; guint16 port; GClueAccuracyLevel accuracy; - guint64 timestamp; + gint64 timestamp_add; }; static void @@ -99,7 +114,7 @@ avahi_service_new (const char *identifier, service->host_name = g_strdup (host_name); service->port = port; service->accuracy = accuracy; - service->timestamp = g_get_real_time () / G_USEC_PER_SEC; + service->timestamp_add = g_get_monotonic_time (); return service; } @@ -122,16 +137,41 @@ compare_avahi_service_by_accuracy_n_time (gconstpointer a, { AvahiServiceInfo *first, *second; gint diff; + gint64 tdiff; first = (AvahiServiceInfo *) a; second = (AvahiServiceInfo *) b; diff = second->accuracy - first->accuracy; + if (diff) + return diff; + + g_assert (first->timestamp_add >= 0); + g_assert (second->timestamp_add >= 0); + tdiff = first->timestamp_add - second->timestamp_add; + if (tdiff < 0) + return -1; + else if (tdiff > 0) + return 1; + else + return 0; +} + +static void +disconnect_from_service (GClueNMEASource *source) +{ + GClueNMEASourcePrivate *priv = source->priv; - if (diff == 0) - return first->timestamp - second->timestamp; + if (!priv->active_service) + return; - return diff; + g_cancellable_cancel (priv->cancellable); + + g_clear_object (&priv->input_stream); + g_clear_object (&priv->connection); + g_clear_object (&priv->client); + g_clear_object (&priv->cancellable); + priv->active_service = NULL; } static gboolean @@ -145,9 +185,9 @@ reconnection_required (GClueNMEASource *source) * 2. a more accurate service than one currently in use, is now * available. */ - return (priv->active_service != NULL && - (priv->all_services == NULL || - priv->active_service != priv->all_services->data)); + return priv->active_service == NULL || + priv->try_services == NULL || + priv->active_service != priv->try_services->data; } static void @@ -157,25 +197,35 @@ reconnect_service (GClueNMEASource *source) return; disconnect_from_service (source); - connect_to_service (source); + try_connect_to_service (source); } -static void -refresh_accuracy_level (GClueNMEASource *source) +static GClueAccuracyLevel get_head_accuracy (GList *list) +{ + AvahiServiceInfo *service; + + if (!list) + return GCLUE_ACCURACY_LEVEL_NONE; + + service = (AvahiServiceInfo *) list->data; + return service->accuracy; +} + +static gboolean +on_refresh_accuracy_level (gpointer user_data) { - GClueAccuracyLevel new, existing; + GClueNMEASource *source = GCLUE_NMEA_SOURCE (user_data); + GClueNMEASourcePrivate *priv = source->priv; + GClueAccuracyLevel new_try, new_broken, new, existing; + + priv->accuracy_refresh_source = 0; existing = gclue_location_source_get_available_accuracy_level (GCLUE_LOCATION_SOURCE (source)); - if (source->priv->all_services != NULL) { - AvahiServiceInfo *service; - - service = (AvahiServiceInfo *) source->priv->all_services->data; - new = service->accuracy; - } else { - new = GCLUE_ACCURACY_LEVEL_NONE; - } + new_try = get_head_accuracy (priv->try_services); + new_broken = get_head_accuracy (priv->broken_services); + new = MAX (new_try, new_broken); if (new != existing) { g_debug ("Available accuracy level from %s: %u", @@ -184,6 +234,109 @@ refresh_accuracy_level (GClueNMEASource *source) "available-accuracy-level", new, NULL); } + + return G_SOURCE_REMOVE; +} + +static void +refresh_accuracy_level (GClueNMEASource *source) +{ + GClueNMEASourcePrivate *priv = source->priv; + + if (priv->accuracy_refresh_source) { + return; + } + + g_debug ("Scheduling NMEA accuracy level refresh"); + priv->accuracy_refresh_source = g_idle_add (on_refresh_accuracy_level, + source); +} + +static gboolean +on_service_unbreak_time (gpointer source) +{ + GClueNMEASourcePrivate *priv = GCLUE_NMEA_SOURCE (source)->priv; + + priv->unbreak_timer = 0; + + if (!priv->try_services && priv->broken_services) { + g_debug ("Unbreaking existing NMEA services"); + + priv->try_services = priv->broken_services; + priv->broken_services = NULL; + + reconnect_service (source); + } + + return G_SOURCE_REMOVE; +} + +static void +check_unbreak_timer (GClueNMEASource *source) +{ + GClueNMEASourcePrivate *priv = source->priv; + + if (priv->try_services || !priv->broken_services) { + if (priv->unbreak_timer) { + g_debug ("Removing unnecessary NMEA unbreaking timer"); + + g_source_remove (priv->unbreak_timer); + priv->unbreak_timer = 0; + } + + return; + } + + if (priv->unbreak_timer) { + return; + } + + g_debug ("Scheduling NMEA unbreaking timer"); + priv->unbreak_timer = g_timeout_add_seconds (SERVICE_UNBREAK_TIME, + on_service_unbreak_time, + source); +} + +static void +service_lists_changed (GClueNMEASource *source) +{ + check_unbreak_timer (source); + reconnect_service (source); + refresh_accuracy_level (source); +} + +static gboolean +check_service_exists (GClueNMEASource *source, + const char *name) +{ + GClueNMEASourcePrivate *priv = source->priv; + AvahiServiceInfo *service; + GList *item; + gboolean ret = FALSE; + + /* only `name` is required here */ + service = avahi_service_new (name, + NULL, + 0, + GCLUE_ACCURACY_LEVEL_NONE); + + item = g_list_find_custom (priv->try_services, + service, + compare_avahi_service_by_identifier); + if (item) { + ret = TRUE; + } else { + item = g_list_find_custom (priv->broken_services, + service, + compare_avahi_service_by_identifier); + if (item) { + ret = TRUE; + } + } + + g_clear_pointer (&service, avahi_service_free); + + return ret; } static void @@ -191,6 +344,7 @@ add_new_service (GClueNMEASource *source, const char *name, const char *host_name, uint16_t port, + gboolean is_socket, AvahiStringList *txt) { GClueAccuracyLevel accuracy = GCLUE_ACCURACY_LEVEL_NONE; @@ -201,10 +355,15 @@ add_new_service (GClueNMEASource *source, GEnumClass *enum_class; GEnumValue *enum_value; - if (port == 0) { - accuracy = GCLUE_ACCURACY_LEVEL_EXACT; + if (check_service_exists (source, name)) { + g_debug ("NMEA service %s already exists", name); + return; + } + + if (!txt) { + accuracy = GCLUE_ACCURACY_LEVEL_EXACT; - goto CREATE_SERVICE; + goto CREATE_SERVICE; } node = avahi_string_list_find (txt, "accuracy"); @@ -242,43 +401,72 @@ add_new_service (GClueNMEASource *source, CREATE_SERVICE: service = avahi_service_new (name, host_name, port, accuracy); + service->is_socket = is_socket; - source->priv->all_services = g_list_insert_sorted - (source->priv->all_services, + source->priv->try_services = g_list_insert_sorted + (source->priv->try_services, service, compare_avahi_service_by_accuracy_n_time); - refresh_accuracy_level (source); - reconnect_service (source); + n_services = g_list_length (source->priv->try_services); + g_debug ("No. of _nmea-0183._tcp services %u", n_services); - n_services = g_list_length (source->priv->all_services); + service_lists_changed (source); +} - g_debug ("No. of _nmea-0183._tcp services %u", n_services); +static void +add_new_service_avahi (GClueNMEASource *source, + const char *name, + const char *host_name, + uint16_t port, + AvahiStringList *txt) +{ + add_new_service (source, name, host_name, port, FALSE, txt); } static void -remove_service (GClueNMEASource *source, - AvahiServiceInfo *service) +add_new_service_socket (GClueNMEASource *source, + const char *name, + const char *socket_path) { - guint n_services = 0; + add_new_service (source, name, socket_path, 0, TRUE, NULL); +} - avahi_service_free (service); - source->priv->all_services = g_list_remove - (source->priv->all_services, service); +static void +service_broken (GClueNMEASource *source) +{ + GClueNMEASourcePrivate *priv = source->priv; + AvahiServiceInfo *service = priv->active_service; - n_services = g_list_length (source->priv->all_services); + g_assert (service); - g_debug ("No. of _nmea-0183._tcp services %u", - n_services); + disconnect_from_service (source); - refresh_accuracy_level (source); - reconnect_service (source); + priv->try_services = g_list_remove (priv->try_services, + service); + priv->broken_services = g_list_insert_sorted + (priv->broken_services, + service, + compare_avahi_service_by_accuracy_n_time); + + service_lists_changed (source); +} + +static void +remove_service_from_list (GList **list, + GList *item) +{ + AvahiServiceInfo *service = item->data; + + *list = g_list_delete_link (*list, item); + avahi_service_free (service); } static void remove_service_by_name (GClueNMEASource *source, const char *name) { + GClueNMEASourcePrivate *priv = source->priv; AvahiServiceInfo *service; GList *item; @@ -288,15 +476,31 @@ remove_service_by_name (GClueNMEASource *source, 0, GCLUE_ACCURACY_LEVEL_NONE); - item = g_list_find_custom (source->priv->all_services, + item = g_list_find_custom (priv->try_services, service, compare_avahi_service_by_identifier); - avahi_service_free (service); + if (item) { + if (item->data == priv->active_service) { + g_debug ("Active NMEA service removed, disconnecting."); + disconnect_from_service (source); + } - if (item == NULL) - return; + remove_service_from_list (&priv->try_services, + item); + } else { + item = g_list_find_custom (priv->broken_services, + service, + compare_avahi_service_by_identifier); + if (item) { + g_assert (item->data != priv->active_service); + remove_service_from_list (&priv->broken_services, + item); + } + } - remove_service (source, item->data); + g_clear_pointer (&service, avahi_service_free); + + service_lists_changed (source); } static void @@ -337,15 +541,18 @@ resolve_callback (AvahiServiceResolver *service_resolver, } case AVAHI_RESOLVER_FOUND: - g_debug ("Service %s:%u resolved", + g_debug ("Service '%s' of type '%s' in domain '%s' resolved to %s:%u", + name, + type, + domain, host_name, - port); + (unsigned int)port); - add_new_service (GCLUE_NMEA_SOURCE (user_data), - name, - host_name, - port, - txt); + add_new_service_avahi (GCLUE_NMEA_SOURCE (user_data), + name, + host_name, + port, + txt); break; } @@ -358,12 +565,8 @@ client_callback (AvahiClient *avahi_client, AvahiClientState state, void *user_data) { - GClueNMEASourcePrivate *priv = GCLUE_NMEA_SOURCE (user_data)->priv; - g_return_if_fail (avahi_client != NULL); - priv->avahi_client = avahi_client; - if (state == AVAHI_CLIENT_FAILURE) { const char *errorstr = avahi_strerror (avahi_client_errno (avahi_client)); @@ -448,53 +651,97 @@ browse_callback (AvahiServiceBrowser *service_browser, } } +#define NMEA_LINE_END "\r\n" +#define NMEA_LINE_END_CTR (sizeof (NMEA_LINE_END) - 1) + +static void nmea_skip_delim (GBufferedInputStream *stream, + GCancellable *cancellable) +{ + const char *buf; + gsize buf_size; + size_t delim_skip; + g_autoptr(GError) error = NULL; + + buf = (const char *) g_buffered_input_stream_peek_buffer (stream, + &buf_size); + + delim_skip = strnspn (buf, NMEA_LINE_END, buf_size); + for (size_t ctr = 0; ctr < delim_skip; ctr++) { + if (g_buffered_input_stream_read_byte (stream, cancellable, &error) < 0) { + if (error && !g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { + g_warning ("Failed to skip %zu / %zu NMEA delimiter: %s", + ctr, delim_skip, error->message); + } + break; + } + } +} + +static gboolean nmea_check_delim (GBufferedInputStream *stream) +{ + const char *buf; + gsize buf_size; + + buf = (const char *) g_buffered_input_stream_peek_buffer (stream, + &buf_size); + + return strnpbrk (buf, NMEA_LINE_END, buf_size) != NULL; +} + #define NMEA_STR_LEN 128 static void on_read_nmea_sentence (GObject *object, GAsyncResult *result, gpointer user_data) { - GClueNMEASource *source = GCLUE_NMEA_SOURCE (user_data); + GClueNMEASource *source = NULL; GDataInputStream *data_input_stream = G_DATA_INPUT_STREAM (object); - GError *error = NULL; + g_autoptr(GError) error = NULL; GClueLocation *prev_location; g_autoptr(GClueLocation) location = NULL; gsize data_size = 0 ; - char *message; + g_autofree char *message = NULL; gint i; - static const gchar *sentences[3] = { 0 }; - static gchar gga[NMEA_STR_LEN] = { 0 }; - static gchar rmc[NMEA_STR_LEN] = { 0 }; + const gchar *sentences[3]; + gchar gga[NMEA_STR_LEN]; + gchar rmc[NMEA_STR_LEN]; - - message = g_data_input_stream_read_line_finish (data_input_stream, + message = g_data_input_stream_read_upto_finish (data_input_stream, result, &data_size, &error); + gga[0] = '\0'; + rmc[0] = '\0'; + do { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + if (!source) + source = GCLUE_NMEA_SOURCE (user_data); + 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) + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED)) { + g_debug ("NMEA socket closed."); + } else { g_warning ("Error when receiving message: %s", error->message); - g_error_free (error); + } + service_broken (source); + return; } 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. + g_debug ("NMEA empty read"); + /* GLib has a bug where g_data_input_stream_read_upto_finish + * returns NULL when reading a line with only stop chars. + * Convert this NULL to a zero-length message. See: + * https://gitlab.gnome.org/GNOME/glib/-/issues/655 */ - remove_service (source, source->priv->active_service); + message = g_strdup (""); + } - gga[0] = '\0'; - rmc[0] = '\0'; - return; } g_debug ("Network source sent: \"%s\"", message); @@ -502,16 +749,17 @@ on_read_nmea_sentence (GObject *object, 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 ("Ignoring NMEA sentence, as it's neither GGA or RMC: %s", message); } - 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); + nmea_skip_delim (G_BUFFERED_INPUT_STREAM (data_input_stream), + source->priv->cancellable); + + if (nmea_check_delim (G_BUFFERED_INPUT_STREAM (data_input_stream))) { + g_clear_pointer (&message, g_free); + message = g_data_input_stream_read_upto + (data_input_stream, + NMEA_LINE_END, NMEA_LINE_END_CTR, + &data_size, NULL, &error); } else { break; } @@ -524,25 +772,20 @@ on_read_nmea_sentence (GObject *object, sentences[i++] = rmc; sentences[i] = NULL; - 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); - g_clear_error (&error); - } else { - gclue_location_source_set_location - (GCLUE_LOCATION_SOURCE (source), location); + if (i > 0) { + prev_location = gclue_location_source_get_location + (GCLUE_LOCATION_SOURCE (source)); + location = gclue_location_create_from_nmeas (sentences, + prev_location); + if (location) { + gclue_location_source_set_location + (GCLUE_LOCATION_SOURCE (source), location); + } } - gga[0] = '\0'; - rmc[0] = '\0'; - sentences[0] = NULL; - - g_data_input_stream_read_line_async (data_input_stream, + g_data_input_stream_read_upto_async (data_input_stream, + NMEA_LINE_END, + NMEA_LINE_END_CTR, G_PRIORITY_DEFAULT, source->priv->cancellable, on_read_nmea_sentence, @@ -554,30 +797,41 @@ on_connection_to_location_server (GObject *object, GAsyncResult *result, gpointer user_data) { - GClueNMEASource *source = GCLUE_NMEA_SOURCE (user_data); GSocketClient *client = G_SOCKET_CLIENT (object); - GError *error = NULL; - GDataInputStream *data_input_stream; - GInputStream *input_stream; + GClueNMEASource *source; + g_autoptr(GSocketConnection) connection = NULL; + g_autoptr(GError) error = NULL; - source->priv->connection = g_socket_client_connect_to_host_finish + connection = g_socket_client_connect_to_host_finish (client, result, &error); - if (error != NULL) { - if (error->code != G_IO_ERROR_CANCELLED) - g_warning ("Failed to connect to NMEA service: %s", error->message); - g_clear_error (&error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + return; + } + source = GCLUE_NMEA_SOURCE (user_data); + + if (error != NULL) { + g_warning ("Failed to connect to NMEA service: %s", error->message); + service_broken (source); return; } - input_stream = g_io_stream_get_input_stream - (G_IO_STREAM (source->priv->connection)); - data_input_stream = g_data_input_stream_new (input_stream); + g_assert (connection); + g_debug ("NMEA service connected."); - g_data_input_stream_read_line_async (data_input_stream, + g_assert (!source->priv->connection); + source->priv->connection = g_steal_pointer (&connection); + + g_assert (!source->priv->input_stream); + source->priv->input_stream = g_data_input_stream_new + (g_io_stream_get_input_stream (G_IO_STREAM (source->priv->connection))); + + g_data_input_stream_read_upto_async (source->priv->input_stream, + NMEA_LINE_END, + NMEA_LINE_END_CTR, G_PRIORITY_DEFAULT, source->priv->cancellable, on_read_nmea_sentence, @@ -585,77 +839,126 @@ on_connection_to_location_server (GObject *object, } static void -connect_to_service (GClueNMEASource *source) +try_connect_to_service (GClueNMEASource *source) { GClueNMEASourcePrivate *priv = source->priv; - GSocketAddress *addr; - GSocketConnectable *connectable; - if (priv->all_services == NULL) + if (!gclue_location_source_get_active (GCLUE_LOCATION_SOURCE (source))) { + g_warn_if_fail (!priv->active_service); + + return; + } + + if (priv->active_service) + return; + + if (priv->try_services == NULL) return; + g_assert (!priv->cancellable); + priv->cancellable = g_cancellable_new (); + + g_assert (!priv->client); priv->client = g_socket_client_new (); - g_cancellable_reset (priv->cancellable); /* The service with the highest accuracy will be stored in the beginning * of the list. */ - priv->active_service = (AvahiServiceInfo *) priv->all_services->data; - - 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->active_service = (AvahiServiceInfo *) priv->try_services->data; + + g_debug ("Trying to connect to NMEA %sservice %s:%u.", + priv->active_service->is_socket ? "socket " : "", + priv->active_service->host_name, + (unsigned int) priv->active_service->port); + + if (!priv->active_service->is_socket) { + 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 { + g_autoptr(GSocketAddress) addr = NULL; + + addr = g_unix_socket_address_new (priv->active_service->host_name); + g_socket_client_connect_async (priv->client, + G_SOCKET_CONNECTABLE (addr), priv->cancellable, on_connection_to_location_server, source); } } -static void -disconnect_from_service (GClueNMEASource *source) +static gboolean +remove_avahi_services_from_list (GClueNMEASource *source, GList **list) { GClueNMEASourcePrivate *priv = source->priv; + gboolean removed_active = FALSE; + GList *l = *list; + + while (l != NULL) { + GList *next = l->next; + AvahiServiceInfo *service = l->data; + + if (!service->is_socket) { + if (service == priv->active_service) { + g_debug ("Active NMEA service was Avahi-provided, disconnecting."); + disconnect_from_service (source); + removed_active = TRUE; + } - g_cancellable_cancel (priv->cancellable); + remove_service_from_list (list, l); + } - if (priv->connection != NULL) { - GError *error = NULL; + l = next; + } + + return removed_active; +} + +static void +disconnect_avahi_client (GClueNMEASource *source) +{ + GClueNMEASourcePrivate *priv = source->priv; - g_io_stream_close (G_IO_STREAM (priv->connection), - NULL, - &error); - if (error != NULL) - g_warning ("Error in closing socket connection: %s", error->message); + remove_avahi_services_from_list (source, &priv->try_services); + if (remove_avahi_services_from_list (source, &priv->broken_services)) { + g_warn_if_reached (); } - g_clear_object (&priv->connection); - g_clear_object (&priv->client); - priv->active_service = NULL; + g_clear_pointer (&priv->avahi_client, avahi_client_free); + + service_lists_changed (source); } static void gclue_nmea_source_finalize (GObject *gnmea) { - GClueNMEASourcePrivate *priv = GCLUE_NMEA_SOURCE (gnmea)->priv; + GClueNMEASource *source = GCLUE_NMEA_SOURCE (gnmea); + GClueNMEASourcePrivate *priv = source->priv; G_OBJECT_CLASS (gclue_nmea_source_parent_class)->finalize (gnmea); - g_clear_object (&priv->connection); - g_clear_object (&priv->client); - g_clear_object (&priv->cancellable); - if (priv->avahi_client) - avahi_client_free (priv->avahi_client); - g_list_free_full (priv->all_services, + disconnect_avahi_client (source); + disconnect_from_service (source); + + if (priv->accuracy_refresh_source) { + g_source_remove (priv->accuracy_refresh_source); + priv->accuracy_refresh_source = 0; + } + + if (priv->unbreak_timer) { + g_source_remove (priv->unbreak_timer); + priv->unbreak_timer = 0; + } + + g_clear_pointer (&priv->glib_poll, avahi_glib_poll_free); + + g_list_free_full (g_steal_pointer (&priv->try_services), + avahi_service_free); + g_list_free_full (g_steal_pointer (&priv->broken_services), avahi_service_free); } @@ -672,41 +975,33 @@ gclue_nmea_source_class_init (GClueNMEASourceClass *klass) } static void -gclue_nmea_source_init (GClueNMEASource *source) +try_connect_avahi_client (GClueNMEASource *source) { - GClueNMEASourcePrivate *priv; AvahiServiceBrowser *service_browser; + GClueNMEASourcePrivate *priv = source->priv; const AvahiPoll *poll_api; - AvahiGLibPoll *glib_poll; - const char *nmea_socket; - GClueConfig *config; int error; - source->priv = gclue_nmea_source_get_instance_private (source); - priv = source->priv; - - glib_poll = avahi_glib_poll_new (NULL, G_PRIORITY_DEFAULT); - poll_api = avahi_glib_poll_get (glib_poll); - - priv->cancellable = g_cancellable_new (); + if (priv->avahi_client) { + AvahiClientState avahi_state; - config = gclue_config_get_singleton (); + avahi_state = avahi_client_get_state (priv->avahi_client); + if (avahi_state != AVAHI_CLIENT_FAILURE) { + return; + } - nmea_socket = gclue_config_get_nmea_socket (config); - if (nmea_socket != NULL) { - add_new_service (source, - "nmea-socket", - nmea_socket, - 0, - NULL); + g_debug ("Avahi client in failure state, trying to reinit."); + disconnect_avahi_client (source); } - avahi_client_new (poll_api, - 0, - client_callback, - source, - &error); + g_assert (priv->glib_poll); + poll_api = avahi_glib_poll_get (priv->glib_poll); + priv->avahi_client = avahi_client_new (poll_api, + 0, + client_callback, + source, + &error); if (priv->avahi_client == NULL) { g_warning ("Failed to connect to avahi service: %s", avahi_strerror (error)); @@ -722,15 +1017,43 @@ gclue_nmea_source_init (GClueNMEASource *source) 0, browse_callback, source); - - if (service_browser == NULL) { const char *errorstr; error = avahi_client_errno (priv->avahi_client); errorstr = avahi_strerror (error); g_warning ("Failed to browse avahi services: %s", errorstr); + goto fail_client; } + + return; + +fail_client: + disconnect_avahi_client (source); +} + +static void +gclue_nmea_source_init (GClueNMEASource *source) +{ + GClueNMEASourcePrivate *priv; + const char *nmea_socket; + GClueConfig *config; + + source->priv = gclue_nmea_source_get_instance_private (source); + priv = source->priv; + + priv->glib_poll = avahi_glib_poll_new (NULL, G_PRIORITY_DEFAULT); + + config = gclue_config_get_singleton (); + + nmea_socket = gclue_config_get_nmea_socket (config); + if (nmea_socket != NULL) { + add_new_service_socket (source, + "nmea-socket", + nmea_socket); + } + + try_connect_avahi_client (source); } /** @@ -747,11 +1070,15 @@ gclue_nmea_source_get_singleton (void) static GClueNMEASource *source = NULL; if (source == NULL) { - source = g_object_new (GCLUE_TYPE_NMEA_SOURCE, NULL); + source = g_object_new (GCLUE_TYPE_NMEA_SOURCE, + "priority-source", TRUE, + NULL); g_object_add_weak_pointer (G_OBJECT (source), (gpointer) &source); - } else + } else { g_object_ref (source); + try_connect_avahi_client (source); + } return source; } @@ -767,10 +1094,11 @@ gclue_nmea_source_start (GClueLocationSource *source) base_class = GCLUE_LOCATION_SOURCE_CLASS (gclue_nmea_source_parent_class); base_result = base_class->start (source); - if (base_result != GCLUE_LOCATION_SOURCE_START_RESULT_OK) + if (base_result == GCLUE_LOCATION_SOURCE_START_RESULT_FAILED) return base_result; - connect_to_service (GCLUE_NMEA_SOURCE (source)); + try_connect_avahi_client (GCLUE_NMEA_SOURCE (source)); + reconnect_service (GCLUE_NMEA_SOURCE (source)); return base_result; } diff --git a/src/gclue-nmea-utils.c b/src/gclue-nmea-utils.c index b38196d8b296c662a76937be57079b233a449454..ed5aa18017ee7c96df11d28b44fbb820551ec8ae 100644 --- a/src/gclue-nmea-utils.c +++ b/src/gclue-nmea-utils.c @@ -36,3 +36,39 @@ gclue_nmea_type_is (const char *msg, const char *nmeatype) g_str_has_prefix (msg+3, nmeatype); } +/** + * gclue_nmea_timestamp_to_timespan + * @timestamp: NMEA timestamp string + * + * Parse the NMEA timestamp string, which is a field in NMEA sentences + * like GGA and RMC. + * + * Returns: a GTimeSpan (gint64) value of microseconds since midnight, + * or -1, if reading fails. + */ +GTimeSpan +gclue_nmea_timestamp_to_timespan (const gchar *timestamp) +{ + gint its, hours, minutes; + gdouble ts, seconds_f; + gchar *endptr; + + if (!timestamp || !*timestamp) + return -1; + + ts = g_ascii_strtod (timestamp, &endptr); + if (endptr != timestamp + g_utf8_strlen(timestamp, 12) || + ts < 0.0 || + ts >= 235960.0) + return -1; + + its = (gint) ts; /* Truncate towards zero */ + hours = its / 10000; + minutes = (its - 10000 * hours) / 100; + seconds_f = ts - 10000 * hours - 100 * minutes; /* Seconds plus fraction */ + + return (GTimeSpan) G_USEC_PER_SEC * (3600 * hours + + 60 * minutes + + seconds_f); +} + diff --git a/src/gclue-nmea-utils.h b/src/gclue-nmea-utils.h index 618dda7eccec848150a27ef36fcef24bf0e1c205..fd51454943289d9a933593f98f636d29d832fca1 100644 --- a/src/gclue-nmea-utils.h +++ b/src/gclue-nmea-utils.h @@ -24,6 +24,7 @@ G_BEGIN_DECLS gboolean gclue_nmea_type_is (const char *msg, const char *nmeatype); +GTimeSpan gclue_nmea_timestamp_to_timespan (const gchar *timestamp); G_END_DECLS diff --git a/src/gclue-service-client.c b/src/gclue-service-client.c index 8f0b3533b900f2ab354d83794dd78471e568a0c5..77a5b8219c0358c7c27230d5bc16baf7e89de55f 100644 --- a/src/gclue-service-client.c +++ b/src/gclue-service-client.c @@ -86,13 +86,13 @@ static char * next_location_path (GClueServiceClient *client) { GClueServiceClientPrivate *priv = client->priv; - char *path, *index_str; + g_autofree char *index_str = NULL; + g_autofree char *path = NULL; index_str = g_strdup_printf ("%u", (priv->locations_updated)++), path = g_strjoin ("/", priv->path, "Location", index_str, NULL); - g_free (index_str); - return path; + return g_steal_pointer (&path); } /* We don't use the gdbus-codegen provided gclue_client_emit_location_updated() @@ -201,9 +201,9 @@ on_locator_location_changed (GObject *gobject, GClueServiceClientPrivate *priv = client->priv; GClueLocationSource *locator = GCLUE_LOCATION_SOURCE (gobject); GClueLocation *new_location; - char *path = NULL; + g_autofree char *path = NULL; const char *prev_path; - GError *error = NULL; + g_autoptr(GError) error = NULL; new_location = gclue_location_source_get_location (locator); if (new_location == NULL) @@ -245,13 +245,10 @@ on_locator_location_changed (GObject *gobject, if (!emit_location_updated (client, prev_path, path, &error)) goto error_out; - goto out; + return; error_out: g_warning ("Failed to update location info: %s", error->message); - g_error_free (error); -out: - g_free (path); } static void @@ -262,10 +259,10 @@ 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, priv->time_threshold); - g_signal_connect (priv->locator, - "notify::location", - G_CALLBACK (on_locator_location_changed), - client); + g_signal_connect_object (priv->locator, + "notify::location", + G_CALLBACK (on_locator_location_changed), + client, 0); gclue_location_source_start (GCLUE_LOCATION_SOURCE (priv->locator)); } @@ -321,7 +318,7 @@ on_agent_props_changed (GDBusProxy *agent_proxy, GVariantIter *iter; GVariant *value; gchar *key; - + if (g_variant_n_children (changed_properties) <= 0) return; @@ -375,7 +372,7 @@ start_data_free (StartData *data) { g_object_unref (data->client); g_object_unref (data->invocation); - g_free(data->desktop_id); + g_free (data->desktop_id); g_slice_free (StartData, data); } diff --git a/src/gclue-service-manager.c b/src/gclue-service-manager.c index ba06c355145ad3530a86dbe2eea7d9befe1f7b7c..56322de6b2ea03d31a4d1ea85d83741e114da596 100644 --- a/src/gclue-service-manager.c +++ b/src/gclue-service-manager.c @@ -21,7 +21,9 @@ */ #include <glib/gi18n.h> +#include <glib-unix.h> #include <config.h> +#include <signal.h> #include "gclue-service-manager.h" #include "gclue-service-client.h" @@ -45,6 +47,7 @@ struct _GClueServiceManagerPrivate guint last_client_id; guint num_clients; + guint unix_signal_source; GClueLocator *locator; }; @@ -77,6 +80,37 @@ typedef struct } OnClientInfoNewReadyData; +static gboolean +log_client_list (gpointer user_data) +{ + GClueServiceManager *manager = GCLUE_SERVICE_MANAGER (user_data); + GList *l; + + g_message ("SIGUSR1 received, printing client list:"); + if (!manager->priv->clients) { + g_message (" (No clients)"); + return G_SOURCE_CONTINUE; + } + g_message (" System Active UID Id"); + for (l = manager->priv->clients; l != NULL; l = l->next) { + GClueDBusClient *dbus_client = GCLUE_DBUS_CLIENT (l->data); + GClueClientInfo *client_info = + gclue_service_client_get_client_info (GCLUE_SERVICE_CLIENT (l->data)); + GClueConfig *config; + guint32 uid; + const char *system, *active, *id; + const char *y = "x"; + const char *n = "-"; + id = gclue_dbus_client_get_desktop_id (dbus_client); + uid = gclue_client_info_get_user_id (client_info); + config = gclue_config_get_singleton (); + system = gclue_config_is_system_component (config, id) ? y : n; + active = gclue_dbus_client_get_active (dbus_client) ? y : n; + g_message (" %s %s %-7d %s", system, active, uid, id); + } + return G_SOURCE_CONTINUE; +} + static void on_client_info_new_ready_data_free (gpointer data) { @@ -180,8 +214,8 @@ complete_get_client (OnClientInfoNewReadyData *data) GClueServiceClient *client; GClueClientInfo *info = data->client_info; GClueAgent *agent_proxy = NULL; - GError *error = NULL; - char *path; + g_autoptr(GError) error = NULL; + g_autofree char *path = NULL; guint32 user_id; /* Disconnect on_peer_vanished_before_completion, if it's there */ @@ -228,15 +262,15 @@ 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_object (client, + "notify::active", + G_CALLBACK (on_client_notify_active), + data->manager, 0); - g_signal_connect (info, - "peer-vanished", - G_CALLBACK (on_peer_vanished), - data->manager); + g_signal_connect_object (info, + "peer-vanished", + G_CALLBACK (on_peer_vanished), + data->manager, 0); client_created: if (data->reuse_client) @@ -255,10 +289,8 @@ error_out: G_DBUS_ERROR_FAILED, error->message); out: - g_clear_error (&error); g_clear_object (&info); on_client_info_new_ready_data_free (data); - g_free (path); return FALSE; } @@ -296,7 +328,7 @@ on_client_info_new_ready (GObject *source_object, GClueServiceManagerPrivate *priv = GCLUE_SERVICE_MANAGER (data->manager)->priv; GClueClientInfo *info = NULL; GClueAgent *agent_proxy; - GError *error = NULL; + g_autoptr(GError) error = NULL; guint32 user_id; info = gclue_client_info_new_finish (res, &error); @@ -305,7 +337,6 @@ on_client_info_new_ready (GObject *source_object, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, error->message); - g_error_free (error); on_client_info_new_ready_data_free (data); return; @@ -434,6 +465,7 @@ static void add_agent_data_free (AddAgentData *data) { g_clear_pointer (&data->desktop_id, g_free); + g_clear_object (&data->manager); g_slice_free (AddAgentData, data); } @@ -459,7 +491,7 @@ on_agent_proxy_ready (GObject *source_object, GClueServiceManagerPrivate *priv = GCLUE_SERVICE_MANAGER (data->manager)->priv; guint32 user_id; GClueAgent *agent; - GError *error = NULL; + g_autoptr(GError) error = NULL; GList *l; agent = gclue_agent_proxy_new_for_bus_finish (res, &error); @@ -470,10 +502,10 @@ on_agent_proxy_ready (GObject *source_object, g_debug ("New agent for user ID '%u'", user_id); g_hash_table_replace (priv->agents, GINT_TO_POINTER (user_id), agent); - g_signal_connect (data->info, - "peer-vanished", - G_CALLBACK (on_agent_vanished), - data->manager); + g_signal_connect_object (data->info, + "peer-vanished", + G_CALLBACK (on_agent_vanished), + data->manager, 0); gclue_dbus_manager_complete_add_agent (data->manager, data->invocation); @@ -498,7 +530,6 @@ error_out: G_DBUS_ERROR_FAILED, error->message); out: - g_clear_error (&error); add_agent_data_free (data); } @@ -510,7 +541,7 @@ on_agent_info_new_ready (GObject *source_object, gpointer user_data) { AddAgentData *data = (AddAgentData *) user_data; - GError *error = NULL; + g_autoptr(GError) error = NULL; GClueConfig *config; const char *xdg_id; @@ -520,7 +551,6 @@ on_agent_info_new_ready (GObject *source_object, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, error->message); - g_error_free (error); add_agent_data_free (data); return; @@ -564,7 +594,7 @@ gclue_service_manager_handle_add_agent (GClueDBusManager *manager, peer = g_dbus_method_invocation_get_sender (invocation); data = g_slice_new0 (AddAgentData); - data->manager = manager; + data->manager = g_object_ref (manager); data->invocation = invocation; data->desktop_id = g_strdup (id); gclue_client_info_new_async (peer, @@ -592,6 +622,7 @@ gclue_service_manager_finalize (GObject *object) on_client_info_new_ready_data_free); priv->clients_waiting_agent = NULL; } + g_clear_handle_id (&priv->unix_signal_source, g_source_remove); /* Chain up to the parent class */ G_OBJECT_CLASS (gclue_service_manager_parent_class)->finalize (object); @@ -654,18 +685,20 @@ on_avail_accuracy_level_changed (GObject *object, static void gclue_service_manager_constructed (GObject *object) { - GClueServiceManagerPrivate *priv = GCLUE_SERVICE_MANAGER (object)->priv; + GClueServiceManager *manager = GCLUE_SERVICE_MANAGER (object); + GClueServiceManagerPrivate *priv = manager->priv; G_OBJECT_CLASS (gclue_service_manager_parent_class)->constructed (object); priv->locator = gclue_locator_new (GCLUE_ACCURACY_LEVEL_EXACT); - g_signal_connect (G_OBJECT (priv->locator), - "notify::available-accuracy-level", - G_CALLBACK (on_avail_accuracy_level_changed), - object); + g_signal_connect_object (G_OBJECT (priv->locator), + "notify::available-accuracy-level", + G_CALLBACK (on_avail_accuracy_level_changed), + object, 0); on_avail_accuracy_level_changed (G_OBJECT (priv->locator), NULL, object); + priv->unix_signal_source = g_unix_signal_add (SIGUSR1, log_client_list, manager); } static void diff --git a/src/gclue-static-source.c b/src/gclue-static-source.c new file mode 100644 index 0000000000000000000000000000000000000000..1c35cea731b0dffc35cef5e8c86d7c1baf74f9a4 --- /dev/null +++ b/src/gclue-static-source.c @@ -0,0 +1,491 @@ +/* vim: set et ts=8 sw=8: */ +/* + * Copyright © 2022,2023 Oracle and/or its affiliates. + * + * 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 <gio/gio.h> +#include "gclue-location.h" +#include "gclue-static-source.h" +#include "config.h" +#include "gclue-enum-types.h" + +#define GEO_FILE_NAME "geolocation" +#define GEO_FILE_PATH SYSCONFDIR "/" GEO_FILE_NAME + +/* Rate limit of geolocation file monitoring. + * In milliseconds. + */ +#define GEO_FILE_MONITOR_RATE_LIMIT 2500 + +struct _GClueStaticSource { + /* <private> */ + GClueLocationSource parent_instance; +}; + +typedef struct { + GClueLocation *location; + guint location_set_timer; + + GFileMonitor *monitor; + + GCancellable *cancellable; + gboolean file_open_quiet; + GFileInputStream *file_stream; + GDataInputStream *data_stream; + enum { L_LAT = 0, L_LON, L_ALT, L_ACCURACY } file_line; + gdouble latitude; + gdouble longitude; + gdouble altitude; +} GClueStaticSourcePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GClueStaticSource, + gclue_static_source, + GCLUE_TYPE_LOCATION_SOURCE) + +static void +file_read_next_line (GClueStaticSource *source); + +static GClueStaticSourcePrivate * +get_priv (GClueStaticSource *source) +{ + return gclue_static_source_get_instance_private (source); +} + +static void +update_accuracy (GClueStaticSource *source) +{ + GClueStaticSourcePrivate *priv = get_priv (source); + GClueAccuracyLevel level_old, level_new; + + if (!priv->location) { + level_new = GCLUE_ACCURACY_LEVEL_NONE; + } else { + gboolean scramble_location; + + g_object_get (G_OBJECT(source), "scramble-location", + &scramble_location, NULL); + if (scramble_location) { + level_new = GCLUE_ACCURACY_LEVEL_CITY; + } else { + level_new = GCLUE_ACCURACY_LEVEL_EXACT; + } + } + + level_old = gclue_location_source_get_available_accuracy_level + (GCLUE_LOCATION_SOURCE (source)); + if (level_new == level_old) + return; + + g_debug ("Available accuracy level from %s: %u", + G_OBJECT_TYPE_NAME (source), level_new); + g_object_set (G_OBJECT (source), + "available-accuracy-level", level_new, + NULL); +} + + +static gboolean +on_location_set_timer (gpointer user_data) +{ + GClueStaticSource *source = GCLUE_STATIC_SOURCE (user_data); + GClueStaticSourcePrivate *priv = get_priv (source); + g_autoptr(GClueLocation) prev_location = NULL; + + priv->location_set_timer = 0; + + g_assert (priv->location); + prev_location = g_steal_pointer (&priv->location); + priv->location = gclue_location_duplicate_fresh (prev_location); + gclue_location_source_set_location + (GCLUE_LOCATION_SOURCE (source), priv->location); + + return G_SOURCE_REMOVE; +} + +static void +location_set_refresh_timer (GClueStaticSource *source) +{ + GClueStaticSourcePrivate *priv = get_priv (source); + + if (!priv->location) { + if (priv->location_set_timer) { + g_debug ("Removing static location set timer due to no location"); + g_clear_handle_id (&priv->location_set_timer, + g_source_remove); + } + + return; + } + + if (priv->location_set_timer) { + return; + } + + g_debug ("Scheduling static location set timer"); + priv->location_set_timer = g_idle_add (on_location_set_timer, + source); +} + +static void +location_updated (GClueStaticSource *source) +{ + /* Update accuracy first so locators can connect or disconnect + * from our source accordingly before getting the new location. + */ + update_accuracy (source); + + location_set_refresh_timer (source); +} + +static void +close_file (GClueStaticSource *source) +{ + GClueStaticSourcePrivate *priv = get_priv (source); + + if (!priv->cancellable) + return; + + g_cancellable_cancel (priv->cancellable); + + g_clear_object (&priv->data_stream); + g_clear_object (&priv->file_stream); + g_clear_object (&priv->cancellable); +} + +static void +close_file_clear_location (GClueStaticSource *source) +{ + GClueStaticSourcePrivate *priv = get_priv (source); + + close_file (source); + + if (!priv->location) + return; + + g_debug ("Static source clearing location"); + g_clear_object (&priv->location); + location_updated (source); +} + +static void +gclue_static_source_finalize (GObject *gstatic) +{ + GClueStaticSource *source = GCLUE_STATIC_SOURCE (gstatic); + GClueStaticSourcePrivate *priv = get_priv (source); + + G_OBJECT_CLASS (gclue_static_source_parent_class)->finalize (gstatic); + + close_file (source); + + g_clear_object (&priv->location); + g_clear_handle_id (&priv->location_set_timer, g_source_remove); + + g_clear_object (&priv->monitor); +} + +static GClueLocationSourceStartResult +gclue_static_source_start (GClueLocationSource *source) +{ + GClueLocationSourceClass *base_class; + GClueLocationSourceStartResult base_result; + + g_return_val_if_fail (GCLUE_IS_STATIC_SOURCE (source), + GCLUE_LOCATION_SOURCE_START_RESULT_FAILED); + + base_class = GCLUE_LOCATION_SOURCE_CLASS (gclue_static_source_parent_class); + base_result = base_class->start (source); + if (base_result != GCLUE_LOCATION_SOURCE_START_RESULT_OK) + return base_result; + + /* Set initial location */ + location_set_refresh_timer (GCLUE_STATIC_SOURCE (source)); + + return base_result; +} + +static void +gclue_static_source_class_init (GClueStaticSourceClass *klass) +{ + GClueLocationSourceClass *source_class = GCLUE_LOCATION_SOURCE_CLASS (klass); + GObjectClass *gstatic_class = G_OBJECT_CLASS (klass); + + gstatic_class->finalize = gclue_static_source_finalize; + + source_class->start = gclue_static_source_start; +} + +static void +on_line_read (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GDataInputStream *data_stream = G_DATA_INPUT_STREAM (object); + GClueStaticSource *source; + GClueStaticSourcePrivate *priv; + g_autoptr(GError) error = NULL; + g_autofree char *line = NULL; + char *comment_start; + gdouble accuracy; + + line = g_data_input_stream_read_line_finish (data_stream, result, + NULL, &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + return; + } + + source = GCLUE_STATIC_SOURCE (user_data); + priv = get_priv (source); + + if (line == NULL) { + if (error != NULL) { + g_warning ("Static source error when reading file: %s", + error->message); + } else { + g_warning ("Static source unexpected EOF reading file (truncated?)"); + } + + close_file_clear_location (source); + return; + } + + comment_start = strchr (line, '#'); + if (comment_start) { + *comment_start = '\0'; + } + + g_strstrip (line); + + if (strlen (line) == 0) { + file_read_next_line (source); + return; + } + + do { + gdouble * const coords[] = { + /* L_LAT */ &priv->latitude, + /* L_LON */ &priv->longitude, + /* L_ALT */ &priv->altitude, + /* L_ACCURACY */ &accuracy, + }; + char *endptr; + + g_assert (priv->file_line >= L_LAT && + priv->file_line <= L_ACCURACY); + + *coords[priv->file_line] = g_ascii_strtod (line, &endptr); + if (errno != 0 || *endptr != '\0') { + g_warning ("Static source invalid line %d '%s'", + priv->file_line, line); + close_file_clear_location (source); + return; + } + } while (FALSE); + + if (priv->file_line < L_ACCURACY) { + priv->file_line++; + file_read_next_line (source); + return; + } + + close_file (source); + + g_debug ("Static source read a new location"); + g_clear_object (&priv->location); + priv->location = gclue_location_new_full (priv->latitude, + priv->longitude, + accuracy, + GCLUE_LOCATION_SPEED_UNKNOWN, + GCLUE_LOCATION_HEADING_UNKNOWN, + priv->altitude, + 0, "Static location"); + g_assert (priv->location); + location_updated (source); +} + +static void +file_read_next_line (GClueStaticSource *source) +{ + GClueStaticSourcePrivate *priv = get_priv (source); + + g_assert (priv->data_stream); + g_data_input_stream_read_line_async (priv->data_stream, + G_PRIORITY_DEFAULT, + priv->cancellable, + on_line_read, source); +} + +static void +on_file_open (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GFile *file = G_FILE (source_object); + GClueStaticSource *source; + GClueStaticSourcePrivate *priv; + g_autoptr(GFileInputStream) file_stream = NULL; + g_autoptr(GError) error = NULL; + + file_stream = g_file_read_finish (file, res, &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + return; + } + + source = GCLUE_STATIC_SOURCE (user_data); + priv = get_priv (source); + + if (error != NULL) { + if (!priv->file_open_quiet) { + g_autofree char *parsename = NULL; + + parsename = g_file_get_parse_name (file); + g_warning ("Static source failed to open '%s': %s", + parsename, error->message); + } + + close_file_clear_location (source); + return; + } + + g_return_if_fail (file_stream != NULL); + g_assert (!priv->file_stream); + priv->file_stream = g_steal_pointer (&file_stream); + + g_assert (!priv->data_stream); + priv->data_stream = g_data_input_stream_new + (G_INPUT_STREAM (priv->file_stream)); + + priv->file_line = L_LAT; + file_read_next_line (source); +} + +static void +open_file (GClueStaticSource *source, GFile *file, gboolean quiet) +{ + GClueStaticSourcePrivate *priv = get_priv (source); + + close_file (source); + + priv->cancellable = g_cancellable_new (); + priv->file_open_quiet = quiet; + g_file_read_async (file, G_PRIORITY_DEFAULT, priv->cancellable, + on_file_open, source); +} + +static void +on_monitor_event (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + GClueStaticSource *source = GCLUE_STATIC_SOURCE (user_data); + g_autofree char *basename = NULL; + + if (event_type != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT && + event_type != G_FILE_MONITOR_EVENT_DELETED) { + return; + } + + g_return_if_fail (file != NULL); + basename = g_file_get_basename (file); + if (basename == NULL || + strcmp (basename, GEO_FILE_NAME) != 0) { + return; + } + + if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { + g_debug ("Static source trying to re-load since " GEO_FILE_PATH " has changed"); + open_file (source, file, FALSE); + } else { /* G_FILE_MONITOR_EVENT_DELETED */ + g_debug ("Static source flushing location since " GEO_FILE_PATH " was deleted"); + close_file_clear_location (source); + } +} + +static void +check_monitor (GClueStaticSource *source) +{ + GClueStaticSourcePrivate *priv = get_priv (source); + g_autoptr(GFile) geo_file = NULL; + g_autoptr(GError) error = NULL; + + if (priv->monitor) + return; + + geo_file = g_file_new_for_path (GEO_FILE_PATH); + priv->monitor = g_file_monitor_file (geo_file, G_FILE_MONITOR_NONE, + NULL, &error); + if (error != NULL) { + g_warning ("Static source failed to monitor '" GEO_FILE_PATH "': %s", + error->message); + g_clear_object (&priv->monitor); + return; + } + + g_assert (priv->monitor); + g_file_monitor_set_rate_limit (priv->monitor, + GEO_FILE_MONITOR_RATE_LIMIT); + g_signal_connect_object (G_OBJECT (priv->monitor), "changed", + G_CALLBACK (on_monitor_event), + source, 0); + + g_debug ("Static source monitoring '" GEO_FILE_PATH "', trying initial load"); + open_file (source, geo_file, TRUE); +} + +static void +gclue_static_source_init (GClueStaticSource *source) +{ + check_monitor (source); +} + +/** + * gclue_static_source_get_singleton: + * + * Get the #GClueStaticSource singleton, for the specified max accuracy + * level @level. + * + * Returns: (transfer full): a new ref to #GClueStaticSource. Use g_object_unref() + * when done. + **/ +GClueStaticSource * +gclue_static_source_get_singleton (GClueAccuracyLevel level) +{ + static GClueStaticSource *source[] = { NULL, NULL }; + gboolean is_exact; + int i; + + g_return_val_if_fail (level >= GCLUE_ACCURACY_LEVEL_CITY, NULL); + is_exact = level == GCLUE_ACCURACY_LEVEL_EXACT; + + i = is_exact ? 0 : 1; + if (source[i] == NULL) { + source[i] = g_object_new (GCLUE_TYPE_STATIC_SOURCE, + "compute-movement", FALSE, + "scramble-location", !is_exact, + NULL); + g_object_add_weak_pointer (G_OBJECT (source[i]), + (gpointer) &source[i]); + } else { + g_object_ref (source[i]); + check_monitor (source[i]); + } + + return source[i]; +} diff --git a/src/gclue-static-source.h b/src/gclue-static-source.h new file mode 100644 index 0000000000000000000000000000000000000000..acf0f62af02a1831dd6eaca92a93fe33b788da2d --- /dev/null +++ b/src/gclue-static-source.h @@ -0,0 +1,40 @@ +/* vim: set et ts=8 sw=8: */ +/* + * Copyright © 2022,2023 Oracle and/or its affiliates. + * + * 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_STATIC_SOURCE_H +#define GCLUE_STATIC_SOURCE_H + +#include <glib.h> +#include "gclue-location-source.h" + +G_BEGIN_DECLS + +#define GCLUE_TYPE_STATIC_SOURCE gclue_static_source_get_type () + +G_DECLARE_FINAL_TYPE (GClueStaticSource, + gclue_static_source, + GCLUE, STATIC_SOURCE, + GClueLocationSource) + +GClueStaticSource *gclue_static_source_get_singleton (GClueAccuracyLevel level); + +G_END_DECLS + +#endif /* GCLUE_STATIC_SOURCE_H */ diff --git a/src/gclue-utils.h b/src/gclue-utils.h new file mode 100644 index 0000000000000000000000000000000000000000..cb8bfb2d2e312f1a6ea8939250138a66d4b22045 --- /dev/null +++ b/src/gclue-utils.h @@ -0,0 +1,61 @@ +/* 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_UTILS_H +#define GCLUE_UTILS_H + +#include <glib.h> +#include <string.h> + +G_BEGIN_DECLS + +#ifndef strnpbrk +inline static const char * +strnpbrk (const char *s, const char *accept, size_t n) +{ + const char *end; + + for (end = s + n; s < end && *s != '\0'; s++) { + if (strchr (accept, *s)) { + return s; + } + } + + return NULL; +} +#endif + +#ifndef strnspn +inline static size_t +strnspn (const char *s, const char *accept, size_t n) +{ + const char *cur, *end; + + for (cur = s, end = s + n; cur < end && *cur != '\0'; cur++) { + if (!strchr (accept, *cur)) { + break; + } + } + + return cur - s; +} +#endif + +G_END_DECLS + +#endif /* GCLUE_UTILS_H */ diff --git a/src/gclue-web-source.c b/src/gclue-web-source.c index bfd70f797229f1748e41b6493d30bc8edd9bd22f..92d94b880eff184e50eedf52d9b3a2202ab94a3f 100644 --- a/src/gclue-web-source.c +++ b/src/gclue-web-source.c @@ -27,6 +27,7 @@ #include "gclue-web-source.h" #include "gclue-error.h" #include "gclue-location.h" +#include "gclue-mozilla.h" /** * SECTION:gclue-web-source @@ -36,32 +37,46 @@ * Baseclass for all sources that solely use a web resource for geolocation. **/ -static gboolean -get_internet_available (void); static void refresh_accuracy_level (GClueWebSource *web); struct _GClueWebSourcePrivate { + GCancellable *cancellable; + + GClueAccuracyLevel accuracy_level; + SoupSession *soup_session; SoupMessage *query; + const char *query_data_description; gulong network_changed_id; gulong connectivity_changed_id; guint64 last_submitted; - gboolean internet_available; + const char *locate_url; + const char *submit_url; + gboolean locate_url_reachable; + gboolean submit_url_reachable; }; +enum +{ + PROP_0, + PROP_ACCURACY_LEVEL, + LAST_PROP +}; +static GParamSpec *gParamSpecs[LAST_PROP]; + G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GClueWebSource, gclue_web_source, 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, @@ -83,12 +98,11 @@ gclue_web_source_real_refresh_async (GClueWebSource *source, return; } - if (!get_internet_available ()) { + if (!source->priv->locate_url_reachable) { g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NETWORK_UNREACHABLE, - "Network unavailable"); + "Cannot reach locate URL"); return; } - g_debug ("Network available"); if (source->priv->query != NULL) { g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_PENDING, @@ -96,53 +110,59 @@ gclue_web_source_real_refresh_async (GClueWebSource *source, return; } - source->priv->query = GCLUE_WEB_SOURCE_GET_CLASS (source)->create_query (source, &local_error); - + source->priv->query = GCLUE_WEB_SOURCE_GET_CLASS (source)->create_query + (source, &source->priv->query_data_description, &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)); + 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); + "Query location SOUP error: %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); + location = gclue_mozilla_parse_response (contents, + web->priv->query_data_description, + &local_error); if (local_error != NULL) { g_task_return_error (task, g_steal_pointer (&local_error)); return; @@ -177,24 +197,18 @@ query_callback (GObject *source_object, 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; + !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED) && + !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_PENDING)) { + g_warning ("Failed to query location: %s", + local_error->message); + } else { + g_debug ("Failed to query location: %s", + local_error->message); + } } } -static gboolean -get_internet_available (void) -{ - GNetworkMonitor *monitor = g_network_monitor_get_default (); - gboolean available; - - available = (g_network_monitor_get_connectivity (monitor) == - G_NETWORK_CONNECTIVITY_FULL); - - return available; -} - static void refresh_accuracy_level (GClueWebSource *web) { @@ -203,7 +217,7 @@ refresh_accuracy_level (GClueWebSource *web) existing = gclue_location_source_get_available_accuracy_level (GCLUE_LOCATION_SOURCE (web)); new = GCLUE_WEB_SOURCE_GET_CLASS (web)->get_available_accuracy_level - (web, web->priv->internet_available); + (web, web->priv->locate_url_reachable); if (new != existing) { g_debug ("Available accuracy level from %s: %u", G_OBJECT_TYPE_NAME (web), new); @@ -213,19 +227,141 @@ refresh_accuracy_level (GClueWebSource *web) } } +static gboolean +get_internet_available (void) +{ + GNetworkMonitor *monitor = g_network_monitor_get_default (); + + return g_network_monitor_get_connectivity (monitor) == + G_NETWORK_CONNECTIVITY_FULL; +} + +static void +locate_url_checked_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GNetworkMonitor *mon = G_NETWORK_MONITOR (source_object); + GClueWebSource *web; + gboolean reachable, last_reachable; + g_autoptr(GError) error = NULL; + + reachable = g_network_monitor_can_reach_finish (mon, result, &error); + if (error && g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { + return; /* WebSource instance is finalized */ + } + + if (!reachable && get_internet_available ()) { + g_debug ("Locate URL not reachable, but Internet is available, overriding"); + reachable = TRUE; + } + + web = GCLUE_WEB_SOURCE (user_data); + last_reachable = web->priv->locate_url_reachable; + web->priv->locate_url_reachable = reachable; + if (last_reachable == reachable) + return; /* We already reacted to network change */ + + g_debug ("Network changed: %s", + reachable ? "Enabling locate URL queries" : + "Disabling locate URL queries"); + if (reachable) { + GCLUE_WEB_SOURCE_GET_CLASS (web)->refresh_async + (web, NULL, query_callback, NULL); + } +} + +static void +submit_url_checked_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GNetworkMonitor *mon = G_NETWORK_MONITOR (source_object); + GClueWebSource *web; + gboolean reachable, last_reachable; + g_autoptr(GError) error = NULL; + + reachable = g_network_monitor_can_reach_finish (mon, result, &error); + if (error && g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { + return; /* WebSource instance is finalized */ + } + + if (!reachable && get_internet_available ()) { + g_debug ("Submit URL not reachable, but Internet is available, overriding"); + reachable = TRUE; + } + + web = GCLUE_WEB_SOURCE (user_data); + last_reachable = web->priv->submit_url_reachable; + web->priv->submit_url_reachable = reachable; + if (last_reachable == reachable) { + return; + } + g_debug ("Network changed: %s", + reachable ? "Enabling submit URL queries" : + "Disabling submit URL queries"); +} + static void -on_network_changed (GNetworkMonitor *monitor G_GNUC_UNUSED, +cancellable_cancel_recreate (GClueWebSource *source) +{ + GClueWebSourcePrivate *priv = source->priv; + + g_cancellable_cancel (priv->cancellable); + + g_clear_object (&priv->cancellable); + priv->cancellable = g_cancellable_new (); +} + +static void +on_network_changed (GNetworkMonitor *unused_monitor G_GNUC_UNUSED, gboolean available G_GNUC_UNUSED, gpointer user_data) { + GNetworkMonitor *monitor = g_network_monitor_get_default (); GClueWebSource *web = GCLUE_WEB_SOURCE (user_data); - 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 */ + g_autoptr(GSocketConnectable) submit_addr = NULL; + g_autoptr(GSocketConnectable) locate_addr = NULL; + + cancellable_cancel_recreate (web); + + if (web->priv->submit_url) { + submit_addr = g_network_address_parse_uri (web->priv->submit_url, + 80, NULL); + if (submit_addr) { + g_network_monitor_can_reach_async (monitor, + submit_addr, + web->priv->cancellable, + submit_url_checked_cb, + web); + } else { + g_warning ("Could not parse submit URL '%s'", + web->priv->submit_url); + web->priv->submit_url_reachable = FALSE; + } + } else { + web->priv->submit_url_reachable = FALSE; + } - GCLUE_WEB_SOURCE_GET_CLASS (web)->refresh_async (web, NULL, query_callback, NULL); + if (web->priv->locate_url) { + locate_addr = g_network_address_parse_uri (web->priv->locate_url, + 80, NULL); + if (locate_addr) { + g_network_monitor_can_reach_async (monitor, + locate_addr, + web->priv->cancellable, + locate_url_checked_cb, + web); + } else { + g_warning ("Could not parse locate URL '%s'", + web->priv->locate_url); + web->priv->locate_url_reachable = FALSE; + } + } else { + web->priv->locate_url_reachable = FALSE; + } } static void @@ -241,6 +377,8 @@ gclue_web_source_finalize (GObject *gsource) { GClueWebSourcePrivate *priv = GCLUE_WEB_SOURCE (gsource)->priv; + g_cancellable_cancel (priv->cancellable); + if (priv->network_changed_id) { g_signal_handler_disconnect (g_network_monitor_get_default (), priv->network_changed_id); @@ -253,15 +391,13 @@ 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; + if (priv->soup_session) { + soup_session_abort (priv->soup_session); + g_clear_object (&priv->soup_session); } - g_clear_object (&priv->soup_session); + g_clear_object (&priv->query); + g_clear_object (&priv->cancellable); G_OBJECT_CLASS (gclue_web_source_parent_class)->finalize (gsource); } @@ -274,10 +410,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_set_proxy_resolver (priv->soup_session, NULL); monitor = g_network_monitor_get_default (); priv->network_changed_id = @@ -295,6 +429,42 @@ gclue_web_source_constructed (GObject *object) object); } +static void +gclue_web_source_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GClueWebSource *web = GCLUE_WEB_SOURCE (object); + + switch (prop_id) { + case PROP_ACCURACY_LEVEL: + g_value_set_enum (value, web->priv->accuracy_level); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gclue_web_source_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GClueWebSource *web = GCLUE_WEB_SOURCE (object); + + switch (prop_id) { + case PROP_ACCURACY_LEVEL: + web->priv->accuracy_level = g_value_get_enum (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + static void gclue_web_source_class_init (GClueWebSourceClass *klass) { @@ -303,14 +473,28 @@ gclue_web_source_class_init (GClueWebSourceClass *klass) klass->refresh_async = gclue_web_source_real_refresh_async; klass->refresh_finish = gclue_web_source_real_refresh_finish; + gsource_class->get_property = gclue_web_source_get_property; + gsource_class->set_property = gclue_web_source_set_property; gsource_class->finalize = gclue_web_source_finalize; gsource_class->constructed = gclue_web_source_constructed; + + gParamSpecs[PROP_ACCURACY_LEVEL] = g_param_spec_enum ("accuracy-level", + "AccuracyLevel", + "Max accuracy level", + GCLUE_TYPE_ACCURACY_LEVEL, + GCLUE_ACCURACY_LEVEL_CITY, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (gsource_class, + PROP_ACCURACY_LEVEL, + gParamSpecs[PROP_ACCURACY_LEVEL]); } static void gclue_web_source_init (GClueWebSource *web) { web->priv = gclue_web_source_get_instance_private (web); + web->priv->cancellable = g_cancellable_new (); } /** @@ -330,25 +514,34 @@ 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, + gpointer user_data) { - 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; + } + + 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'", - str); + g_debug ("Successfully submitted location data to '%s'", uri_str); } #define SUBMISSION_ACCURACY_THRESHOLD 100 @@ -362,22 +555,24 @@ 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; + + if (!web->priv->submit_url_reachable) + return; location = gclue_location_source_get_location (source); if (location == NULL || gclue_location_get_accuracy (location) > SUBMISSION_ACCURACY_THRESHOLD || + gclue_location_get_accuracy (location) == + GCLUE_LOCATION_ACCURACY_UNKNOWN || gclue_location_get_timestamp (location) < web->priv->last_submitted + SUBMISSION_TIME_THRESHOLD) return; web->priv->last_submitted = gclue_location_get_timestamp (location); - if (!get_internet_available ()) - return; - query = GCLUE_WEB_SOURCE_GET_CLASS (web)->create_submit_query (web, location, @@ -386,16 +581,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); } /** @@ -423,3 +619,17 @@ gclue_web_source_set_submit_source (GClueWebSource *web, on_submit_source_location_notify (G_OBJECT (submit_source), NULL, web); } + +void +gclue_web_source_set_locate_url (GClueWebSource *source, + const char *url) +{ + source->priv->locate_url = url; +} + +void +gclue_web_source_set_submit_url (GClueWebSource *source, + const char *url) +{ + source->priv->submit_url = url; +} diff --git a/src/gclue-web-source.h b/src/gclue-web-source.h index ea8ea73a40824f4a9679a3247ca1ebe71e5132b0..05068c6461fa62423d373e7813cf55fad38b511c 100644 --- a/src/gclue-web-source.h +++ b/src/gclue-web-source.h @@ -71,13 +71,11 @@ struct _GClueWebSourceClass { GError **error); SoupMessage * (*create_query) (GClueWebSource *source, + const char **query_data_description, GError **error); SoupMessage * (*create_submit_query) (GClueWebSource *source, GClueLocation *location, GError **error); - GClueLocation * (*parse_response) (GClueWebSource *source, - const char *response, - GError **error); GClueAccuracyLevel (*get_available_accuracy_level) (GClueWebSource *source, gboolean network_available); @@ -86,6 +84,10 @@ struct _GClueWebSourceClass { void gclue_web_source_refresh (GClueWebSource *source); void gclue_web_source_set_submit_source (GClueWebSource *source, GClueLocationSource *submit_source); +void gclue_web_source_set_locate_url (GClueWebSource *source, + const char *url); +void gclue_web_source_set_submit_url (GClueWebSource *source, + const char *url); G_END_DECLS diff --git a/src/gclue-wifi.c b/src/gclue-wifi.c index 210819592ee20748997c09adb7f28de3cd61cf5b..cd20b0dfddc1791af01d9b9e70f612cdbc7e4253 100644 --- a/src/gclue-wifi.c +++ b/src/gclue-wifi.c @@ -24,6 +24,7 @@ #include <string.h> #include <config.h> #include "gclue-wifi.h" +#include "gclue-3g.h" #include "gclue-config.h" #include "gclue-error.h" #include "gclue-mozilla.h" @@ -49,6 +50,12 @@ * full cache (excluding overheads). */ #define CACHE_ENTRY_MAX_AGE_SECONDS (48 * 60 * 60) +/* The signal strength can typically vary by ±5 for a stationary laptop, so + * match cache entries with that tolerance. + * In dBm units. + */ +#define CACHE_ENTRY_MATCH_SIGNAL_WINDOW 10 + /** * SECTION:gclue-wifi * @short_description: WiFi-based geolocation @@ -78,7 +85,58 @@ gclue_wifi_refresh_finish (GClueWebSource *source, static void disconnect_cache_prune_timeout (GClueWifi *wifi); +typedef struct { + GArray *signals; + GClueLocation *location; +} LocationCacheElement; + +static LocationCacheElement * +location_cache_element_new (GArray *signals, + GClueLocation *location) +{ + LocationCacheElement *element; + + element = g_slice_new (LocationCacheElement); + element->signals = signals; + element->location = g_object_ref (location); + return element; +} + +static void location_cache_element_free (gpointer data) +{ + LocationCacheElement *element = data; + + if (element->signals) + g_array_free (element->signals, TRUE); + g_clear_object (&element->location); + g_slice_free (LocationCacheElement, element); +} + +typedef struct { + GList *elements; +} LocationCacheValue; + +static LocationCacheValue * +location_cache_value_new (void) +{ + LocationCacheValue *value; + + value = g_slice_new (LocationCacheValue); + value->elements = NULL; + return value; +} + +static void location_cache_value_free (gpointer data) +{ + LocationCacheValue *value = data; + + g_list_free_full (value->elements, location_cache_element_free); + g_slice_free (LocationCacheValue, value); +} + struct _GClueWifiPrivate { + GCancellable *intf_cancellable, *bss_cancellable; + GClueMozilla *mozilla; WPASupplicant *supplicant; WPAInterface *interface; GHashTable *bss_proxies; @@ -92,9 +150,7 @@ struct _GClueWifiPrivate { guint scan_timeout; - GClueAccuracyLevel accuracy_level; - - GHashTable *location_cache; /* (element-type GVariant GClueLocation) (owned) */ + GHashTable *location_cache; /* (element-type GVariant LocationCacheValue) (owned) */ guint cache_prune_timeout_id; guint cache_hits, cache_misses; @@ -104,25 +160,14 @@ struct _GClueWifiPrivate { #endif }; -enum -{ - PROP_0, - PROP_ACCURACY_LEVEL, - LAST_PROP -}; -static GParamSpec *gParamSpecs[LAST_PROP]; - static SoupMessage * gclue_wifi_create_query (GClueWebSource *source, + const char **query_data_description, GError **error); static SoupMessage * gclue_wifi_create_submit_query (GClueWebSource *source, GClueLocation *location, GError **error); -static GClueLocation * -gclue_wifi_parse_response (GClueWebSource *source, - const char *json, - GError **error); static GClueAccuracyLevel gclue_wifi_get_available_accuracy_level (GClueWebSource *source, gboolean net_available); @@ -150,6 +195,8 @@ gclue_wifi_finalize (GObject *gwifi) G_OBJECT_CLASS (gclue_wifi_parent_class)->finalize (gwifi); + g_cancellable_cancel (wifi->priv->intf_cancellable); + disconnect_bss_signals (wifi); disconnect_cache_prune_timeout (wifi); @@ -158,47 +205,13 @@ gclue_wifi_finalize (GObject *gwifi) 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); + g_clear_object (&wifi->priv->mozilla); + g_clear_object (&wifi->priv->intf_cancellable); } static void gclue_wifi_constructed (GObject *object); -static void -gclue_wifi_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - GClueWifi *wifi = GCLUE_WIFI (object); - - switch (prop_id) { - case PROP_ACCURACY_LEVEL: - g_value_set_enum (value, wifi->priv->accuracy_level); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -gclue_wifi_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - GClueWifi *wifi = GCLUE_WIFI (object); - - switch (prop_id) { - case PROP_ACCURACY_LEVEL: - wifi->priv->accuracy_level = g_value_get_enum (value); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - static void gclue_wifi_class_init (GClueWifiClass *klass) { @@ -212,24 +225,10 @@ gclue_wifi_class_init (GClueWifiClass *klass) 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; web_class->get_available_accuracy_level = gclue_wifi_get_available_accuracy_level; - gwifi_class->get_property = gclue_wifi_get_property; - gwifi_class->set_property = gclue_wifi_set_property; gwifi_class->finalize = gclue_wifi_finalize; gwifi_class->constructed = gclue_wifi_constructed; - - gParamSpecs[PROP_ACCURACY_LEVEL] = g_param_spec_enum ("accuracy-level", - "AccuracyLevel", - "Max accuracy level", - GCLUE_TYPE_ACCURACY_LEVEL, - GCLUE_ACCURACY_LEVEL_CITY, - G_PARAM_READWRITE | - G_PARAM_CONSTRUCT_ONLY); - g_object_class_install_property (gwifi_class, - PROP_ACCURACY_LEVEL, - gParamSpecs[PROP_ACCURACY_LEVEL]); } static void @@ -345,19 +344,24 @@ on_bss_proxy_ready (GObject *source_object, GAsyncResult *res, gpointer user_data) { - GClueWifi *wifi = GCLUE_WIFI (user_data); + GClueWifi *wifi; WPABSS *bss; - GError *error = NULL; + g_autoptr(GError) error = NULL; char ssid[MAX_SSID_LEN + 1] = { 0 }; bss = wpa_bss_proxy_new_for_bus_finish (res, &error); if (bss == NULL) { - g_debug ("%s", error->message); - g_error_free (error); + if (error && !g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { + g_warning ("BSS proxy setup failed: %s", + error->message); + } return; } + wifi = GCLUE_WIFI (user_data); + if (gclue_mozilla_should_ignore_bss (bss)) { g_object_unref (bss); @@ -365,7 +369,7 @@ on_bss_proxy_ready (GObject *source_object, } get_ssid_from_bss (bss, ssid); - g_debug ("WiFi AP '%s' added.", ssid); + g_debug ("Got WiFi AP '%s'", ssid); if (wpa_bss_get_signal (bss) <= WIFI_SCAN_BSS_NOISE_LEVEL) { const char *path; @@ -396,13 +400,15 @@ on_bss_added (WPAInterface *object, GVariant *properties, gpointer user_data) { + GClueWifi *wifi = GCLUE_WIFI (user_data); + wpa_bss_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, "fi.w1.wpa_supplicant1", path, - NULL, + wifi->priv->bss_cancellable, on_bss_proxy_ready, - user_data); + wifi); } static gboolean @@ -459,7 +465,7 @@ start_wifi_scan (GClueWifi *wifi) wpa_interface_call_scan (WPA_INTERFACE (priv->interface), args, - NULL, + priv->bss_cancellable, on_scan_call_done, wifi); } @@ -492,7 +498,6 @@ on_scan_timeout (gpointer user_data) GClueWifi *wifi = GCLUE_WIFI (user_data); GClueWifiPrivate *priv = wifi->priv; - g_debug ("WiFi scan timeout."); priv->scan_timeout = 0; if (priv->interface == NULL) @@ -503,6 +508,11 @@ on_scan_timeout (gpointer user_data) return G_SOURCE_REMOVE; } +gboolean gclue_wifi_should_skip_bsss (GClueAccuracyLevel level) +{ + return level < GCLUE_ACCURACY_LEVEL_STREET; +} + static gboolean on_scan_wait_done (gpointer wifi) { @@ -511,9 +521,13 @@ on_scan_wait_done (gpointer wifi) g_return_val_if_fail (GCLUE_IS_WIFI (wifi), G_SOURCE_REMOVE); priv = GCLUE_WIFI(wifi)->priv; + /* We have the latest scan result */ + gclue_mozilla_set_wifi (priv->mozilla, wifi); + if (priv->bss_list_changed) { priv->bss_list_changed = FALSE; - g_debug ("Refreshing location…"); + g_debug ("WiFi BSS list changed, refreshing location…"); + gclue_mozilla_set_bss_dirty (priv->mozilla); gclue_web_source_refresh (GCLUE_WEB_SOURCE (wifi)); } priv->scan_wait_id = 0; @@ -521,6 +535,15 @@ on_scan_wait_done (gpointer wifi) return G_SOURCE_REMOVE; } +static GClueAccuracyLevel +get_accuracy_level (GClueWifi *wifi) +{ + GClueAccuracyLevel level; + + g_object_get (G_OBJECT (wifi), "accuracy-level", &level, NULL); + return level; +} + static void on_scan_done (WPAInterface *object, gboolean success, @@ -535,7 +558,6 @@ on_scan_done (WPAInterface *object, return; } - g_debug ("WiFi scan completed"); if (priv->interface == NULL) return; @@ -558,14 +580,14 @@ on_scan_done (WPAInterface *object, * user's location can change quickly. With low accuracy, we don't since * we wouldn't want to drain power unnecessarily. */ - if (priv->accuracy_level >= GCLUE_ACCURACY_LEVEL_STREET) + if (get_accuracy_level (wifi) >= GCLUE_ACCURACY_LEVEL_STREET) timeout = WIFI_SCAN_TIMEOUT_HIGH_ACCURACY; else timeout = WIFI_SCAN_TIMEOUT_LOW_ACCURACY; priv->scan_timeout = g_timeout_add_seconds (timeout, on_scan_timeout, wifi); - g_debug ("Next scan scheduled in %u seconds", timeout); + g_debug ("WiFi scan done, next scheduled in %u seconds", timeout); } static void @@ -573,20 +595,24 @@ on_scan_call_done (GObject *source_object, GAsyncResult *res, gpointer user_data) { - GClueWifi *wifi = GCLUE_WIFI (user_data); - GError *error = NULL; + g_autoptr(GError) error = NULL; - if (!wpa_interface_call_scan_finish - (WPA_INTERFACE (source_object), - res, - &error)) { - g_warning ("Scanning of WiFi networks failed: %s", - error->message); - g_error_free (error); + if (!wpa_interface_call_scan_finish (WPA_INTERFACE (source_object), + res, &error)) { + GClueWifi *wifi; - cancel_wifi_scan (wifi); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + return; + } - return; + wifi = GCLUE_WIFI (user_data); + + if (error) { + g_warning ("Scanning of WiFi networks failed: %s", + error->message); + } + + cancel_wifi_scan (wifi); } } @@ -605,6 +631,9 @@ connect_bss_signals (GClueWifi *wifi) return; } + g_assert (!priv->bss_cancellable); + priv->bss_cancellable = g_cancellable_new (); + start_wifi_scan (wifi); priv->bss_list_changed = TRUE; @@ -633,6 +662,12 @@ disconnect_bss_signals (GClueWifi *wifi) { GClueWifiPrivate *priv = wifi->priv; + if (priv->bss_cancellable) { + g_debug ("Cancelling WiFi requests"); + g_cancellable_cancel (priv->bss_cancellable); + g_clear_object (&priv->bss_cancellable); + } + cancel_wifi_scan (wifi); if (priv->bss_added_id != 0) { @@ -657,22 +692,46 @@ cache_prune (GClueWifi *wifi) GHashTableIter iter; gpointer value; guint64 cutoff_seconds; - guint old_cache_size; + guint old_cache_size, removed_elements = 0; 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); + LocationCacheValue *lcvalue = (LocationCacheValue *)value; + GList *l = lcvalue->elements; + + g_assert (l); + while (l) { + LocationCacheElement *element = (LocationCacheElement *)l->data; + GList *lnext = l->next; + + /* Keep this location? */ + if (gclue_location_get_timestamp (element->location) > + cutoff_seconds) + goto next_el; + + location_cache_element_free (element); + lcvalue->elements = g_list_delete_link (lcvalue->elements, l); + removed_elements++; + + /* Deleted the last entry (element) in this hash bucket? + * Remove this hash table entry then. + */ + if (!lcvalue->elements) { + g_assert (!lnext); + g_hash_table_iter_remove (&iter); + } + + next_el: + l = lnext; + } } - g_debug ("Pruned cache (old size: %u, new size: %u)", - old_cache_size, g_hash_table_size (priv->location_cache)); + g_debug ("Pruned cache (old size: %u, new size: %u, removed elements: %u)", + old_cache_size, g_hash_table_size (priv->location_cache), + removed_elements); } #if GLIB_CHECK_VERSION(2, 64, 0) @@ -782,6 +841,8 @@ gclue_wifi_start (GClueLocationSource *source) static GClueLocationSourceStopResult gclue_wifi_stop (GClueLocationSource *source) { + GClueWifi *wifi = GCLUE_WIFI (source); + GClueWifiPrivate *priv = wifi->priv; GClueLocationSourceClass *base_class; GClueLocationSourceStopResult base_result; @@ -795,6 +856,10 @@ gclue_wifi_stop (GClueLocationSource *source) disconnect_bss_signals (GCLUE_WIFI (source)); disconnect_cache_prune_timeout (GCLUE_WIFI (source)); + if (gclue_mozilla_test_set_wifi (priv->mozilla, wifi, NULL)) { + g_debug ("Removed us as the WiFi source on stop"); + } + return base_result; } @@ -802,15 +867,15 @@ static GClueAccuracyLevel gclue_wifi_get_available_accuracy_level (GClueWebSource *source, gboolean net_available) { - GClueWifiPrivate *priv = GCLUE_WIFI (source)->priv; + GClueWifi *wifi = GCLUE_WIFI (source); + GClueWifiPrivate *priv = wifi->priv; if (!net_available) return GCLUE_ACCURACY_LEVEL_NONE; - else if (priv->interface != NULL && - priv->accuracy_level != GCLUE_ACCURACY_LEVEL_CITY) - return GCLUE_ACCURACY_LEVEL_STREET; - else + else if (!priv->interface) return GCLUE_ACCURACY_LEVEL_CITY; + else + return MIN (get_accuracy_level (wifi), GCLUE_ACCURACY_LEVEL_STREET); } static void @@ -818,18 +883,25 @@ on_interface_proxy_ready (GObject *source_object, GAsyncResult *res, gpointer user_data) { - GClueWifi *wifi = GCLUE_WIFI (user_data); + GClueWifi *wifi; WPAInterface *interface; - GError *error = NULL; + g_autoptr(GError) error = NULL; interface = wpa_interface_proxy_new_for_bus_finish (res, &error); if (interface == NULL) { - g_debug ("%s", error->message); - g_error_free (error); + if (error) { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + return; + } + + g_warning ("Interface proxy add failed: %s", + error->message); + } return; } + wifi = GCLUE_WIFI (user_data); if (wifi->priv->interface != NULL) { g_object_unref (interface); return; @@ -860,7 +932,7 @@ on_interface_added (WPASupplicant *supplicant, G_DBUS_PROXY_FLAGS_NONE, "fi.w1.wpa_supplicant1", path, - NULL, + wifi->priv->intf_cancellable, on_interface_proxy_ready, wifi); } @@ -888,14 +960,27 @@ on_interface_removed (WPASupplicant *supplicant, disconnect_bss_signals (wifi); g_clear_object (&wifi->priv->interface); + if (gclue_mozilla_test_set_wifi (priv->mozilla, wifi, NULL)) { + g_debug ("Removed interface was the WiFi source"); + } + gclue_web_source_refresh (GCLUE_WEB_SOURCE (wifi)); } static void gclue_wifi_init (GClueWifi *wifi) { + GClueWebSource *web_source = GCLUE_WEB_SOURCE (wifi); + wifi->priv = gclue_wifi_get_instance_private (wifi); + wifi->priv->intf_cancellable = g_cancellable_new (); + wifi->priv->mozilla = gclue_mozilla_get_singleton (); + gclue_web_source_set_locate_url (web_source, + gclue_mozilla_get_locate_url (wifi->priv->mozilla)); + gclue_web_source_set_submit_url (web_source, + gclue_mozilla_get_submit_url (wifi->priv->mozilla)); + wifi->priv->bss_proxies = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, @@ -907,7 +992,7 @@ gclue_wifi_init (GClueWifi *wifi) wifi->priv->location_cache = g_hash_table_new_full (variant_hash, g_variant_equal, (GDestroyNotify) g_variant_unref, - g_object_unref); + location_cache_value_free); } static void @@ -916,11 +1001,11 @@ gclue_wifi_constructed (GObject *object) GClueWifi *wifi = GCLUE_WIFI (object); GClueWifiPrivate *priv = wifi->priv; const gchar *const *interfaces; - GError *error = NULL; + g_autoptr(GError) error = NULL; G_OBJECT_CLASS (gclue_wifi_parent_class)->constructed (object); - if (wifi->priv->accuracy_level == GCLUE_ACCURACY_LEVEL_CITY) { + if (get_accuracy_level (wifi) == GCLUE_ACCURACY_LEVEL_CITY) { GClueConfig *config = gclue_config_get_singleton (); if (!gclue_config_get_enable_wifi_source (config)) @@ -936,20 +1021,20 @@ gclue_wifi_constructed (GObject *object) NULL, &error); if (priv->supplicant == NULL) { - g_warning ("Failed to connect to wpa_supplicant service: %s", - error->message); - g_error_free (error); + if (error) + g_warning ("Failed to connect to wpa_supplicant service: %s", + error->message); goto refresh_n_exit; } - g_signal_connect (priv->supplicant, - "interface-added", - G_CALLBACK (on_interface_added), - wifi); - g_signal_connect (priv->supplicant, - "interface-removed", - G_CALLBACK (on_interface_removed), - wifi); + g_signal_connect_object (priv->supplicant, + "interface-added", + G_CALLBACK (on_interface_added), + wifi, 0); + g_signal_connect_object (priv->supplicant, + "interface-removed", + G_CALLBACK (on_interface_removed), + wifi, 0); interfaces = wpa_supplicant_get_interfaces (priv->supplicant); if (interfaces != NULL && interfaces[0] != NULL) @@ -982,23 +1067,29 @@ on_wifi_destroyed (gpointer data, GClueWifi * gclue_wifi_get_singleton (GClueAccuracyLevel level) { - static GClueWifi *wifi[] = { NULL, NULL }; + static GClueWifi *wifi[] = { NULL, NULL, NULL }; guint i; + GClueConfig *config = gclue_config_get_singleton (); + gboolean wifi_enabled; 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) - level = GCLUE_ACCURACY_LEVEL_CITY; + wifi_enabled = gclue_config_get_enable_wifi_source (config); if (level == GCLUE_ACCURACY_LEVEL_CITY) { - GClueConfig *config = gclue_config_get_singleton (); - i = 0; - if (gclue_config_get_enable_wifi_source (config)) + if (wifi_enabled) scramble_location = TRUE; - } else { + } else if (level == GCLUE_ACCURACY_LEVEL_NEIGHBORHOOD) { + g_return_val_if_fail (wifi_enabled, NULL); + i = 1; + scramble_location = TRUE; + } else { + g_return_val_if_fail (wifi_enabled, NULL); + + i = 2; compute_movement = TRUE; } @@ -1017,38 +1108,33 @@ gclue_wifi_get_singleton (GClueAccuracyLevel level) return wifi[i]; } -GClueAccuracyLevel -gclue_wifi_get_accuracy_level (GClueWifi *wifi) +static gboolean +wifi_should_skip_tower (GClueWifi *wifi) { - g_return_val_if_fail (GCLUE_IS_WIFI (wifi), - GCLUE_ACCURACY_LEVEL_NONE); - - return wifi->priv->accuracy_level; + return gclue_3g_should_skip_tower (get_accuracy_level (wifi)); } /* Can return NULL, signifying an empty BSS list. */ -static GList * -get_bss_list (GClueWifi *wifi) +GList * +gclue_wifi_get_bss_list (GClueWifi *wifi) { return g_hash_table_get_values (wifi->priv->bss_proxies); } static SoupMessage * gclue_wifi_create_query (GClueWebSource *source, + const char **query_data_description, GError **error) { GClueWifi *wifi = GCLUE_WIFI (source); - GList *bss_list = NULL; /* As in Access Points */ - SoupMessage *msg; + gboolean skip_tower; if (wifi->priv->interface == NULL) { goto create_query; } - bss_list = get_bss_list (wifi); - /* Empty list? */ - if (bss_list == NULL) { + if (!g_hash_table_size (wifi->priv->bss_proxies)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, @@ -1057,17 +1143,13 @@ gclue_wifi_create_query (GClueWebSource *source, } create_query: - msg = gclue_mozilla_create_query (bss_list, NULL, error); - g_list_free (bss_list); - return msg; -} + skip_tower = wifi_should_skip_tower (wifi); + if (skip_tower) { + g_debug ("Will skip 3GPP tower in query due to our accuracy level"); + } -static GClueLocation * -gclue_wifi_parse_response (GClueWebSource *source, - const char *json, - GError **error) -{ - return gclue_mozilla_parse_response (json, error); + return gclue_mozilla_create_query (wifi->priv->mozilla, skip_tower, FALSE, + query_data_description, error); } static SoupMessage * @@ -1076,7 +1158,6 @@ gclue_wifi_create_submit_query (GClueWebSource *source, GError **error) { GClueWifi *wifi = GCLUE_WIFI (source); - GList *bss_list; /* As in Access Points */ SoupMessage * msg; if (wifi->priv->interface == NULL) { @@ -1087,10 +1168,8 @@ gclue_wifi_create_submit_query (GClueWebSource *source, return NULL; } - bss_list = get_bss_list (wifi); - /* Empty list? */ - if (bss_list == NULL) { + if (!g_hash_table_size (wifi->priv->bss_proxies)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, @@ -1098,11 +1177,9 @@ gclue_wifi_create_submit_query (GClueWebSource *source, return NULL; } - msg = gclue_mozilla_create_submit_query (location, - bss_list, - NULL, + msg = gclue_mozilla_create_submit_query (wifi->priv->mozilla, + location, error); - g_list_free (bss_list); return msg; } @@ -1145,20 +1222,46 @@ variant_hash (gconstpointer key) return g_bytes_hash (bytes); } -static GVariant * -get_location_cache_key (GClueWifi *wifi) +static void location_cache_key_fill_tower (GClueWifi *wifi, GClue3GTower *tower) +{ + GClueWifiPrivate *priv = wifi->priv; + GClue3GTower *moztower; + + memset (tower, 0, sizeof (*tower)); + tower->tec = GCLUE_TOWER_TEC_NO_FIX; + + moztower = gclue_mozilla_get_tower (priv->mozilla); + if (!moztower || wifi_should_skip_tower (wifi)) { + return; + } + + g_assert (moztower->tec != GCLUE_TOWER_TEC_NO_FIX); + *tower = *moztower; +} + +static void location_cache_key_add_tower (GClueWifi *wifi, GVariantBuilder *builder) +{ + GClue3GTower tower; + + location_cache_key_fill_tower (wifi, &tower); + g_variant_builder_add (builder, "u", (guint32)tower.tec); + g_variant_builder_add (builder, "s", tower.opc); + g_variant_builder_add (builder, "t", (guint64)tower.lac); + g_variant_builder_add (builder, "t", (guint64)tower.cell_id); +} + +static GPtrArray * +get_location_cache_bss_array (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. */ + * its query. 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)) { @@ -1169,39 +1272,142 @@ get_location_cache_key (GClueWifi *wifi) g_ptr_array_sort (bss_array, bss_compare); + return g_steal_pointer (&bss_array); +} + +static GVariant * +get_location_cache_hashtable_key (GClueWifi *wifi, GPtrArray *bss_array) +{ + guint i; + GVariantBuilder builder; + /* Serialise to a variant. */ - g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayn)")); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("(usttaay)")); + location_cache_key_add_tower (wifi, &builder); + + g_variant_builder_open (&builder, G_VARIANT_TYPE ("aay")); 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); } + g_variant_builder_close (&builder); return g_variant_builder_end (&builder); } +static GArray * +get_location_cache_signal_array (GClueWifi *wifi, GPtrArray *bss_array) +{ + g_autoptr(GArray) signal_array = NULL; + guint i; + + signal_array = g_array_sized_new (FALSE, FALSE, sizeof (gint16), bss_array->len); + for (i = 0; i < bss_array->len; i++) { + WPABSS *bss = WPA_BSS (bss_array->pdata[i]); + gint16 signal = wpa_bss_get_signal (bss); + + g_array_append_val (signal_array, signal); + } + + return g_steal_pointer (&signal_array); +} + +static gboolean cached_signals_match (GArray *signals1, GArray *signals2) +{ + guint i; + + if (signals1->len != signals2->len) { + g_warning ("Different signal count in one hash table entry: %u vs %u", + signals1->len, signals2->len); + return FALSE; + } + + for (i = 0; i < signals1->len; i++) { + gint s1 = g_array_index (signals1, gint16, i); + gint s2 = g_array_index (signals2, gint16, i); + + if (ABS (s1 - s2) > CACHE_ENTRY_MATCH_SIGNAL_WINDOW / 2) + return FALSE; + } + + return TRUE; +} + static GClueLocation * -duplicate_location_new_timestamp (GClueLocation *location) +find_cached_location (GHashTable *cache, GVariant *key, GArray *signals) +{ + g_autofree gchar *key_str = g_variant_print (key, FALSE); + GClueLocation *location = NULL; + LocationCacheValue *value; + GList *l; + + value = g_hash_table_lookup (cache, key); + if (!value) { + g_debug ("Cache miss for key %s", key_str); + return NULL; + } + + g_assert (value->elements); + for (l = value->elements; l; l = l->next) { + LocationCacheElement *element = l->data; + + if (location && + gclue_location_get_accuracy (element->location) >= + gclue_location_get_accuracy (location)) { + /* Have at least as accurate location already, + * don't bother with comparing signals. + */ + continue; + } + + if (!cached_signals_match (element->signals, signals)) + continue; + + location = element->location; + } + + if (location) { + g_debug ("Cache hit for key %s: got location %p (%s)", + key_str, location, + gclue_location_get_description (location)); + } else { + g_debug ("Cache had key %s, but with different signals", key_str); + } + + return location; +} + +typedef struct { + GVariant *cache_key; + GArray *signals; +} RefreshTaskData; + +static RefreshTaskData * +refresh_task_data_new (GVariant *cache_key, + GArray *signals) { - 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); + RefreshTaskData *tdata; + + tdata = g_slice_new (RefreshTaskData); + tdata->cache_key = g_variant_ref (cache_key); + tdata->signals = signals; + return tdata; +} + +static void refresh_task_data_free (gpointer data) +{ + RefreshTaskData *rdata = data; + + g_clear_pointer (&rdata->cache_key, g_variant_unref); + if (rdata->signals) + g_array_free (rdata->signals, TRUE); + g_slice_free (RefreshTaskData, rdata); } static void @@ -1212,39 +1418,58 @@ gclue_wifi_refresh_async (GClueWebSource *source, { 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_autoptr(GPtrArray) bss_array = get_location_cache_bss_array (wifi); + g_autoptr(GVariant) cache_key = get_location_cache_hashtable_key (wifi, bss_array); + g_autoptr(GArray) signal_array = get_location_cache_signal_array (wifi, bss_array); + GClueLocation *cached_location = find_cached_location (wifi->priv->location_cache, + cache_key, signal_array); + RefreshTaskData *tdata; 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); + new_location = gclue_location_duplicate_fresh (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++; } + tdata = refresh_task_data_new (cache_key, g_steal_pointer (&signal_array)); + g_task_set_task_data (task, tdata, refresh_task_data_free); + /* 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 +add_cached_location (GHashTable *cache, + GVariant *key, GArray **signals, + GClueLocation *location) +{ + LocationCacheValue *value; + LocationCacheElement *element; + + value = g_hash_table_lookup (cache, key); + if (!value) { + value = location_cache_value_new (); + g_hash_table_insert (cache, g_variant_ref (key), value); + } + + element = location_cache_element_new (g_steal_pointer (signals), location); + value->elements = g_list_prepend (value->elements, element); +} + static void refresh_cb (GObject *source_object, GAsyncResult *result, @@ -1255,7 +1480,7 @@ refresh_cb (GObject *source_object, g_autoptr(GTask) task = g_steal_pointer (&user_data); g_autoptr(GClueLocation) location = NULL; g_autoptr(GError) local_error = NULL; - GVariant *cache_key; + RefreshTaskData *tdata; g_autofree gchar *cache_key_str = NULL; double cache_hit_ratio; @@ -1268,9 +1493,11 @@ refresh_cb (GObject *source_object, } /* 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)); + tdata = g_task_get_task_data (task); + cache_key_str = g_variant_print (tdata->cache_key, FALSE); + add_cached_location (wifi->priv->location_cache, + tdata->cache_key, &tdata->signals, + location); if (wifi->priv->cache_hits || wifi->priv->cache_misses) { double cache_attempts; diff --git a/src/gclue-wifi.h b/src/gclue-wifi.h index abb4912d68a9daef0d7ca5c2409679e64e444427..29ee9ce21a9cbf0e632aaf0b239de8fad51fa262 100644 --- a/src/gclue-wifi.h +++ b/src/gclue-wifi.h @@ -63,7 +63,8 @@ struct _GClueWifiClass { }; GClueWifi * gclue_wifi_get_singleton (GClueAccuracyLevel level); -GClueAccuracyLevel gclue_wifi_get_accuracy_level (GClueWifi *wifi); +gboolean gclue_wifi_should_skip_bsss (GClueAccuracyLevel level); +GList *gclue_wifi_get_bss_list (GClueWifi *wifi); G_END_DECLS diff --git a/src/meson.build b/src/meson.build index 13eb1baef240240bca625c70eba78534dd6e945c..b5175f8452dde4867d88dbb1bbb90aeae73253fc 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, @@ -24,14 +24,16 @@ sources += [ 'gclue-main.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', + 'gclue-static-source.c', 'gclue-static-source.h', 'gclue-web-source.c', 'gclue-web-source.h', 'gclue-wifi.h', 'gclue-wifi.c', 'gclue-mozilla.h', 'gclue-mozilla.c', 'gclue-min-uint.h', 'gclue-min-uint.c', - 'gclue-location.h', 'gclue-location.c' ] + 'gclue-location.h', 'gclue-location.c', + 'gclue-utils.h' ] if get_option('3g-source') or get_option('cdma-source') or get_option('modem-gps-source') - geoclue_deps += [ dependency('mm-glib', version: '>= 1.10') ] + geoclue_deps += [ dependency('mm-glib', version: '>= 1.12') ] sources += [ 'gclue-modem.c', 'gclue-modem.h', 'gclue-modem-manager.c',