diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml
index 72cc4f875..b774299c8 100644
--- a/js/js-resources.gresource.xml
+++ b/js/js-resources.gresource.xml
@@ -25,6 +25,7 @@
misc/params.js
misc/smartcardManager.js
misc/util.js
+ misc/weather.js
perf/core.js
perf/hwtest.js
diff --git a/js/misc/weather.js b/js/misc/weather.js
new file mode 100644
index 000000000..f645222e4
--- /dev/null
+++ b/js/misc/weather.js
@@ -0,0 +1,178 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+
+const Geoclue = imports.gi.Geoclue;
+const Gio = imports.gi.Gio;
+const GWeather = imports.gi.GWeather;
+const Lang = imports.lang;
+const Signals = imports.signals;
+
+const Util = imports.misc.util;
+
+const WeatherClient = new Lang.Class({
+ Name: 'WeatherClient',
+
+ _init: function() {
+ this._loading = false;
+
+ this._useAutoLocation = false;
+ this._mostRecentLocation = null;
+
+ this._gclueService = null;
+ this._gclueStarted = false;
+ this._gclueFailed = false;
+ this._gclueLocationChangedId = 0;
+
+ this._world = GWeather.Location.get_world();
+
+ let providers = GWeather.Provider.METAR |
+ GWeather.Provider.YR_NO |
+ GWeather.Provider.OWM;
+ this._weatherInfo = new GWeather.Info({ enabled_providers: providers });
+ this._weatherInfo.connect_after('updated', () => {
+ this.emit('changed');
+ });
+
+ this._weatherAppMon = new Util.AppSettingsMonitor('org.gnome.Weather.Application.desktop',
+ 'org.gnome.Weather.Application');
+ this._weatherAppMon.connect('available-changed', () => { this.emit('changed'); });
+ this._weatherAppMon.watchSetting('automatic-location',
+ Lang.bind(this, this._onAutomaticLocationChanged));
+ this._weatherAppMon.watchSetting('locations',
+ Lang.bind(this, this._onLocationsChanged));
+ },
+
+ get available() {
+ return this._weatherAppMon.available;
+ },
+
+ get loading() {
+ return this._loading;
+ },
+
+ get info() {
+ return this._weatherInfo;
+ },
+
+ activateApp: function() {
+ this._weatherAppMon.activateApp();
+ },
+
+ update: function() {
+ this._loadInfo();
+ },
+
+ _loadInfo: function() {
+ let id = this._weatherInfo.connect('updated', () => {
+ this._weatherInfo.disconnect(id);
+ this._loading = false;
+ });
+
+ this._loading = true;
+ this.emit('changed');
+
+ this._weatherInfo.update();
+ },
+
+ _locationsEqual: function(loc1, loc2) {
+ if (loc1 == loc2)
+ return true;
+
+ if (loc1 == null || loc2 == null)
+ return false;
+
+ return loc1.equal(loc2);
+ },
+
+ _setLocation: function(location) {
+ if (this._locationsEqual(this._weatherInfo.location, location))
+ return;
+
+ this._weatherInfo.abort();
+ this._weatherInfo.set_location(location);
+
+ if (location)
+ this._loadInfo();
+ else
+ this.emit('changed');
+ },
+
+ _updateLocationMonitoring: function() {
+ if (this._useAutoLocation) {
+ if (this._gclueLocationChangedId != 0 || this._gclueService == null)
+ return;
+
+ this._gclueLocationChangedId =
+ this._gclueService.connect('notify::location',
+ Lang.bind(this, this._onGClueLocationChanged));
+ this._onGClueLocationChanged();
+ } else {
+ if (this._gclueLocationChangedId)
+ this._gclueService.disconnect(this._gclueLocationChangedId);
+ this._gclueLocationChangedId = 0;
+ }
+ },
+
+ _startGClueService: function() {
+ if (this._gclueStarted)
+ return;
+
+ this._gclueStarted = 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._gclueFailed = true;
+ this._setLocation(this._mostRecentLocation);
+ return;
+ }
+
+ this._gclueService.get_client().distance_threshold = 100;
+ this._updateLocationMonitoring();
+ });
+ },
+
+ _onGClueLocationChanged: function() {
+ let geoLocation = this._gclueService.location;
+ let location = GWeather.Location.new_detached(geoLocation.description,
+ null,
+ geoLocation.latitude,
+ geoLocation.longitude);
+ this._setLocation(location);
+ },
+
+ _onAutomaticLocationChanged: function(settings, key) {
+ let useAutoLocation = settings.get_boolean(key);
+ if (this._useAutoLocation == useAutoLocation)
+ return;
+
+ this._useAutoLocation = useAutoLocation;
+
+ this._updateLocationMonitoring();
+
+ if (this._useAutoLocation) {
+ if (!this._gclueStarted)
+ this._startGClueService();
+ } else {
+ this._setLocation(this._mostRecentLocation);
+ }
+ },
+
+ _onLocationsChanged: function(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._gclueFailed)
+ this._setLocation(this._mostRecentLocation);
+ }
+});
+Signals.addSignalMethods(WeatherClient.prototype);