// -*- 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);