fea0192772
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
250 lines
8.0 KiB
JavaScript
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);
|