gnome-shell/js/misc/weather.js
Florian Müllner fea0192772 weather: Handle missing permission store
Our weather integration is supposed to follow GNOME Weather's settings,
including its permission to use location services. However there's a
discrepancy in case xdg-desktop-portal is unavailable:

While our geoclue agent grants all applications access to location
services in that case, the weather integration treats it as if
access was denied.

Fix this by handling this case explicitly.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1130
2019-04-03 13:27:42 +00:00

250 lines
8.0 KiB
JavaScript

// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const { Geoclue, Gio, GLib, GWeather } = imports.gi;
const Signals = imports.signals;
const PermissionStore = imports.misc.permissionStore;
const Util = imports.misc.util;
// Minimum time between updates to show loading indication
var UPDATE_THRESHOLD = 10 * GLib.TIME_SPAN_MINUTE;
var WeatherClient = class {
constructor() {
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._weatherAuthorized = false;
this._permStore = new PermissionStore.PermissionStore((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;
}
this._permStore.LookupRemote('gnome', 'geolocation', (res, error) => {
if (error)
log('Error looking up permission: ' + error.message);
let [perms, data] = error ? [{}, null] : res;
let 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();
this._providers = GWeather.Provider.METAR |
GWeather.Provider.YR_NO |
GWeather.Provider.OWM;
this._weatherInfo = new GWeather.Info({ enabled_providers: 0 });
this._weatherInfo.connect_after('updated', () => {
this._lastUpdate = GLib.DateTime.new_now_local();
this.emit('changed');
});
this._weatherAppMon = new Util.AppSettingsMonitor('org.gnome.Weather.desktop',
'org.gnome.Weather');
this._weatherAppMon.connect('available-changed', () => { this.emit('changed'); });
this._weatherAppMon.watchSetting('automatic-location',
this._onAutomaticLocationChanged.bind(this));
this._weatherAppMon.watchSetting('locations',
this._onLocationsChanged.bind(this));
}
get available() {
return this._weatherAppMon.available;
}
get loading() {
return this._loading;
}
get hasLocation() {
return this._locationValid;
}
get info() {
return this._weatherInfo;
}
activateApp() {
this._weatherAppMon.activateApp();
}
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._weatherAuthorized;
}
_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);
this._weatherInfo.set_enabled_providers(location ? this._providers : 0);
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;
}
}
_startGClueService() {
if (this._gclueStarting)
return;
this._gclueStarting = true;
Geoclue.Simple.new('org.gnome.Shell', Geoclue.AccuracyLevel.CITY, null,
(o, res) => {
try {
this._gclueService = Geoclue.Simple.new_finish(res);
} 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;
let location = GWeather.Location.new_detached(geoLocation.description,
null,
geoLocation.latitude,
geoLocation.longitude);
this._setLocation(location);
}
_onAutomaticLocationChanged(settings, key) {
let useAutoLocation = settings.get_boolean(key);
if (this._autoLocationRequested == useAutoLocation)
return;
this._autoLocationRequested = useAutoLocation;
this._updateAutoLocation();
}
_updateAutoLocation() {
this._updateLocationMonitoring();
if (this._useAutoLocation)
this._startGClueService();
else
this._setLocation(this._mostRecentLocation);
}
_onLocationsChanged(settings, key) {
let serialized = settings.get_value(key).deep_unpack().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();
}
};
Signals.addSignalMethods(WeatherClient.prototype);