
Braces are optional for single-line arrow functions, but there's a subtle difference: Without braces, the expression is implicitly used as return value; with braces, the function returns nothing unless there's an explicit return. We currently reflect that in our style by only omitting braces when the function is expected to have a return value, but that's not very obvious, not an important differentiation to make, and not easy to express in an automatic rule. So just omit braces consistently as mandated by gjs' coding style. https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/608
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);
|