gnome-shell/js/misc/weather.js
Brendan William ae86b58d59 weather: Prevent setting Geoclue source as location name
Prior to Geoclue v2.7, the location description did not seem to ever be
set. Now, the source used to determine the location is set as the
description. This can result in the location name in dateMenu to be set
as "WiFi", "GeoIP", etc., since we relay the description as the location
name to GWeather. Instead, pass an empty string so GWeather continues
to set the location name itself.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2729>
2023-08-16 19:17:21 +00:00

330 lines
10 KiB
JavaScript

// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
import Geoclue from 'gi://Geoclue';
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import GWeather from 'gi://GWeather';
import Shell from 'gi://Shell';
import * as Signals from './signals.js';
import * as PermissionStore from './permissionStore.js';
import {loadInterfaceXML} from './fileUtils.js';
Gio._promisify(Geoclue.Simple, 'new');
const WeatherIntegrationIface = loadInterfaceXML('org.gnome.Shell.WeatherIntegration');
const WEATHER_BUS_NAME = 'org.gnome.Weather';
const WEATHER_OBJECT_PATH = '/org/gnome/Weather';
const WEATHER_INTEGRATION_IFACE = 'org.gnome.Shell.WeatherIntegration';
const WEATHER_APP_ID = 'org.gnome.Weather.desktop';
// Minimum time between updates to show loading indication
const UPDATE_THRESHOLD = 10 * GLib.TIME_SPAN_MINUTE;
export class WeatherClient extends Signals.EventEmitter {
constructor() {
super();
this._loading = false;
this._locationValid = false;
this._lastUpdate = GLib.DateTime.new_from_unix_local(0);
this._autoLocationRequested = false;
this._mostRecentLocation = null;
this._gclueService = null;
this._gclueStarted = false;
this._gclueStarting = false;
this._gclueLocationChangedId = 0;
this._needsAuth = true;
this._weatherAuthorized = false;
this._permStore = new PermissionStore.PermissionStore(async (proxy, error) => {
if (error) {
log(`Failed to connect to permissionStore: ${error.message}`);
return;
}
if (this._permStore.g_name_owner == null) {
// Failed to auto-start, likely because xdg-desktop-portal
// isn't installed; don't restrict access to location service
this._weatherAuthorized = true;
this._updateAutoLocation();
return;
}
let [perms, data] = [{}, null];
try {
[perms, data] = await this._permStore.LookupAsync('gnome', 'geolocation');
} catch (err) {
log(`Error looking up permission: ${err.message}`);
}
const params = ['gnome', 'geolocation', false, data, perms];
this._onPermStoreChanged(this._permStore, '', params);
});
this._permStore.connectSignal('Changed',
this._onPermStoreChanged.bind(this));
this._locationSettings = new Gio.Settings({schema_id: 'org.gnome.system.location'});
this._locationSettings.connect('changed::enabled',
this._updateAutoLocation.bind(this));
this._world = GWeather.Location.get_world();
const providers =
GWeather.Provider.METAR |
GWeather.Provider.MET_NO |
GWeather.Provider.OWM;
this._weatherInfo = new GWeather.Info({
application_id: 'org.gnome.Shell',
contact_info: 'https://gitlab.gnome.org/GNOME/gnome-shell/-/raw/HEAD/gnome-shell.doap',
enabled_providers: providers,
});
this._weatherInfo.connect_after('updated', () => {
this._lastUpdate = GLib.DateTime.new_now_local();
this.emit('changed');
});
this._weatherApp = null;
this._weatherProxy = null;
this._createWeatherProxy();
this._settings = new Gio.Settings({
schema_id: 'org.gnome.shell.weather',
});
this._settings.connect('changed::automatic-location',
this._onAutomaticLocationChanged.bind(this));
this._onAutomaticLocationChanged();
this._settings.connect('changed::locations',
this._onLocationsChanged.bind(this));
this._onLocationsChanged();
this._appSystem = Shell.AppSystem.get_default();
this._appSystem.connect('installed-changed',
this._onInstalledChanged.bind(this));
this._onInstalledChanged();
}
get available() {
return this._weatherApp != null;
}
get loading() {
return this._loading;
}
get hasLocation() {
return this._locationValid;
}
get info() {
return this._weatherInfo;
}
activateApp() {
if (this._weatherApp)
this._weatherApp.activate();
}
update() {
if (!this._locationValid)
return;
let now = GLib.DateTime.new_now_local();
// Update without loading indication if the current info is recent enough
if (this._weatherInfo.is_valid() &&
now.difference(this._lastUpdate) < UPDATE_THRESHOLD)
this._weatherInfo.update();
else
this._loadInfo();
}
get _useAutoLocation() {
return this._autoLocationRequested &&
this._locationSettings.get_boolean('enabled') &&
(!this._needsAuth || this._weatherAuthorized);
}
async _createWeatherProxy() {
const nodeInfo = Gio.DBusNodeInfo.new_for_xml(WeatherIntegrationIface);
try {
this._weatherProxy = await Gio.DBusProxy.new(
Gio.DBus.session,
Gio.DBusProxyFlags.DO_NOT_AUTO_START | Gio.DBusProxyFlags.GET_INVALIDATED_PROPERTIES,
nodeInfo.lookup_interface(WEATHER_INTEGRATION_IFACE),
WEATHER_BUS_NAME,
WEATHER_OBJECT_PATH,
WEATHER_INTEGRATION_IFACE,
null);
} catch (e) {
log(`Failed to create GNOME Weather proxy: ${e}`);
return;
}
this._weatherProxy.connect('g-properties-changed',
this._onWeatherPropertiesChanged.bind(this));
this._onWeatherPropertiesChanged();
}
_onWeatherPropertiesChanged() {
if (this._weatherProxy.g_name_owner == null)
return;
this._settings.set_boolean('automatic-location',
this._weatherProxy.AutomaticLocation);
this._settings.set_value('locations',
new GLib.Variant('av', this._weatherProxy.Locations));
}
_onInstalledChanged() {
let hadApp = this._weatherApp != null;
this._weatherApp = this._appSystem.lookup_app(WEATHER_APP_ID);
let haveApp = this._weatherApp != null;
if (hadApp !== haveApp)
this.emit('changed');
let neededAuth = this._needsAuth;
this._needsAuth = this._weatherApp === null ||
this._weatherApp.app_info.has_key('X-Flatpak');
if (neededAuth !== this._needsAuth)
this._updateAutoLocation();
}
_loadInfo() {
let id = this._weatherInfo.connect('updated', () => {
this._weatherInfo.disconnect(id);
this._loading = false;
});
this._loading = true;
this.emit('changed');
this._weatherInfo.update();
}
_locationsEqual(loc1, loc2) {
if (loc1 === loc2)
return true;
if (loc1 == null || loc2 == null)
return false;
return loc1.equal(loc2);
}
_setLocation(location) {
if (this._locationsEqual(this._weatherInfo.location, location))
return;
this._weatherInfo.abort();
this._weatherInfo.set_location(location);
this._locationValid = location != null;
if (location)
this._loadInfo();
else
this.emit('changed');
}
_updateLocationMonitoring() {
if (this._useAutoLocation) {
if (this._gclueLocationChangedId !== 0 || this._gclueService == null)
return;
this._gclueLocationChangedId =
this._gclueService.connect('notify::location',
this._onGClueLocationChanged.bind(this));
this._onGClueLocationChanged();
} else {
if (this._gclueLocationChangedId)
this._gclueService.disconnect(this._gclueLocationChangedId);
this._gclueLocationChangedId = 0;
}
}
async _startGClueService() {
if (this._gclueStarting)
return;
this._gclueStarting = true;
try {
this._gclueService = await Geoclue.Simple.new(
'org.gnome.Shell', Geoclue.AccuracyLevel.CITY, null);
} catch (e) {
log(`Failed to connect to Geoclue2 service: ${e.message}`);
this._setLocation(this._mostRecentLocation);
return;
}
this._gclueStarted = true;
this._gclueService.get_client().distance_threshold = 100;
this._updateLocationMonitoring();
}
_onGClueLocationChanged() {
let geoLocation = this._gclueService.location;
// Provide empty name so GWeather sets location name
const location = GWeather.Location.new_detached('',
null,
geoLocation.latitude,
geoLocation.longitude);
this._setLocation(location);
}
_onAutomaticLocationChanged() {
let useAutoLocation = this._settings.get_boolean('automatic-location');
if (this._autoLocationRequested === useAutoLocation)
return;
this._autoLocationRequested = useAutoLocation;
this._updateAutoLocation();
}
_updateAutoLocation() {
this._updateLocationMonitoring();
if (this._useAutoLocation)
this._startGClueService();
else
this._setLocation(this._mostRecentLocation);
}
_onLocationsChanged() {
let locations = this._settings.get_value('locations').deepUnpack();
let serialized = locations.shift();
let mostRecentLocation = null;
if (serialized)
mostRecentLocation = this._world.deserialize(serialized);
if (this._locationsEqual(this._mostRecentLocation, mostRecentLocation))
return;
this._mostRecentLocation = mostRecentLocation;
if (!this._useAutoLocation || !this._gclueStarted)
this._setLocation(this._mostRecentLocation);
}
_onPermStoreChanged(proxy, sender, params) {
let [table, id, deleted_, data_, perms] = params;
if (table !== 'gnome' || id !== 'geolocation')
return;
let permission = perms['org.gnome.Weather'] || ['NONE'];
let [accuracy] = permission;
this._weatherAuthorized = accuracy !== 'NONE';
this._updateAutoLocation();
}
}