2017-02-23 21:55:33 +00:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
import Geoclue from 'gi://Geoclue';
|
|
|
|
import Gio from 'gi://Gio';
|
|
|
|
import GLib from 'gi://GLib';
|
|
|
|
import GWeather from 'gi://GWeather';
|
|
|
|
import Shell from 'gi://Shell';
|
|
|
|
import * as Signals from './signals.js';
|
2017-02-23 21:55:33 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
import * as PermissionStore from './permissionStore.js';
|
2019-07-23 10:49:40 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
import {loadInterfaceXML} from './fileUtils.js';
|
2019-07-23 10:49:40 +00:00
|
|
|
|
2022-02-10 23:09:54 +00:00
|
|
|
Gio._promisify(Geoclue.Simple, 'new');
|
2019-12-19 19:50:37 +00:00
|
|
|
|
2019-07-23 10:49:40 +00:00
|
|
|
const WeatherIntegrationIface = loadInterfaceXML('org.gnome.Shell.WeatherIntegration');
|
|
|
|
|
|
|
|
const WEATHER_BUS_NAME = 'org.gnome.Weather';
|
|
|
|
const WEATHER_OBJECT_PATH = '/org/gnome/Weather';
|
|
|
|
const WEATHER_INTEGRATION_IFACE = 'org.gnome.Shell.WeatherIntegration';
|
|
|
|
|
|
|
|
const WEATHER_APP_ID = 'org.gnome.Weather.desktop';
|
2017-02-23 21:55:33 +00:00
|
|
|
|
2017-02-25 00:36:47 +00:00
|
|
|
// Minimum time between updates to show loading indication
|
2023-07-10 09:53:00 +00:00
|
|
|
const UPDATE_THRESHOLD = 10 * GLib.TIME_SPAN_MINUTE;
|
2017-02-25 00:36:47 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
export class WeatherClient extends Signals.EventEmitter {
|
2017-10-31 01:19:44 +00:00
|
|
|
constructor() {
|
2022-07-04 22:30:44 +00:00
|
|
|
super();
|
|
|
|
|
2017-02-23 21:55:33 +00:00
|
|
|
this._loading = false;
|
2017-03-19 13:42:35 +00:00
|
|
|
this._locationValid = false;
|
2017-02-25 00:36:47 +00:00
|
|
|
this._lastUpdate = GLib.DateTime.new_from_unix_local(0);
|
2017-02-23 21:55:33 +00:00
|
|
|
|
2017-03-19 15:34:53 +00:00
|
|
|
this._autoLocationRequested = false;
|
2017-02-23 21:55:33 +00:00
|
|
|
this._mostRecentLocation = null;
|
|
|
|
|
|
|
|
this._gclueService = null;
|
|
|
|
this._gclueStarted = false;
|
2017-03-11 12:46:10 +00:00
|
|
|
this._gclueStarting = false;
|
2017-02-23 21:55:33 +00:00
|
|
|
this._gclueLocationChangedId = 0;
|
|
|
|
|
2019-11-27 22:01:15 +00:00
|
|
|
this._needsAuth = true;
|
2017-03-19 15:29:35 +00:00
|
|
|
this._weatherAuthorized = false;
|
2022-06-23 12:53:29 +00:00
|
|
|
this._permStore = new PermissionStore.PermissionStore(async (proxy, error) => {
|
2017-03-19 15:29:35 +00:00
|
|
|
if (error) {
|
2019-01-30 00:18:24 +00:00
|
|
|
log(`Failed to connect to permissionStore: ${error.message}`);
|
2017-03-19 15:29:35 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-04-03 10:35:03 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-06-23 12:53:29 +00:00
|
|
|
let [perms, data] = [{}, null];
|
|
|
|
try {
|
|
|
|
[perms, data] = await this._permStore.LookupAsync('gnome', 'geolocation');
|
|
|
|
} catch (err) {
|
|
|
|
log(`Error looking up permission: ${err.message}`);
|
|
|
|
}
|
2017-03-19 15:29:35 +00:00
|
|
|
|
2022-06-23 12:53:29 +00:00
|
|
|
const params = ['gnome', 'geolocation', false, data, perms];
|
|
|
|
this._onPermStoreChanged(this._permStore, '', params);
|
2017-03-19 15:29:35 +00:00
|
|
|
});
|
|
|
|
this._permStore.connectSignal('Changed',
|
2023-08-06 23:45:22 +00:00
|
|
|
this._onPermStoreChanged.bind(this));
|
2017-03-19 15:29:35 +00:00
|
|
|
|
2023-08-06 22:40:20 +00:00
|
|
|
this._locationSettings = new Gio.Settings({schema_id: 'org.gnome.system.location'});
|
2017-03-19 13:31:19 +00:00
|
|
|
this._locationSettings.connect('changed::enabled',
|
2023-08-06 23:45:22 +00:00
|
|
|
this._updateAutoLocation.bind(this));
|
2017-03-19 13:31:19 +00:00
|
|
|
|
2017-02-23 21:55:33 +00:00
|
|
|
this._world = GWeather.Location.get_world();
|
|
|
|
|
2021-01-12 16:30:16 +00:00
|
|
|
const providers =
|
|
|
|
GWeather.Provider.METAR |
|
|
|
|
GWeather.Provider.MET_NO |
|
|
|
|
GWeather.Provider.OWM;
|
2021-01-12 16:31:15 +00:00
|
|
|
this._weatherInfo = new GWeather.Info({
|
|
|
|
application_id: 'org.gnome.Shell',
|
2021-07-14 18:07:22 +00:00
|
|
|
contact_info: 'https://gitlab.gnome.org/GNOME/gnome-shell/-/raw/HEAD/gnome-shell.doap',
|
2021-01-14 19:47:52 +00:00
|
|
|
enabled_providers: providers,
|
2021-01-12 16:31:15 +00:00
|
|
|
});
|
2017-02-23 21:55:33 +00:00
|
|
|
this._weatherInfo.connect_after('updated', () => {
|
2017-02-25 00:36:47 +00:00
|
|
|
this._lastUpdate = GLib.DateTime.new_now_local();
|
2017-02-23 21:55:33 +00:00
|
|
|
this.emit('changed');
|
|
|
|
});
|
|
|
|
|
2019-07-23 10:49:40 +00:00
|
|
|
this._weatherApp = null;
|
|
|
|
this._weatherProxy = null;
|
|
|
|
|
2019-12-19 19:50:37 +00:00
|
|
|
this._createWeatherProxy();
|
2019-07-23 10:49:40 +00:00
|
|
|
|
|
|
|
this._settings = new Gio.Settings({
|
2019-08-20 21:43:54 +00:00
|
|
|
schema_id: 'org.gnome.shell.weather',
|
2019-07-23 10:49:40 +00:00
|
|
|
});
|
|
|
|
this._settings.connect('changed::automatic-location',
|
|
|
|
this._onAutomaticLocationChanged.bind(this));
|
2019-08-01 13:18:43 +00:00
|
|
|
this._onAutomaticLocationChanged();
|
2019-07-23 10:49:40 +00:00
|
|
|
this._settings.connect('changed::locations',
|
|
|
|
this._onLocationsChanged.bind(this));
|
2019-08-01 13:18:43 +00:00
|
|
|
this._onLocationsChanged();
|
2019-07-23 10:49:40 +00:00
|
|
|
|
|
|
|
this._appSystem = Shell.AppSystem.get_default();
|
|
|
|
this._appSystem.connect('installed-changed',
|
|
|
|
this._onInstalledChanged.bind(this));
|
|
|
|
this._onInstalledChanged();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-02-23 21:55:33 +00:00
|
|
|
|
|
|
|
get available() {
|
2019-07-23 10:49:40 +00:00
|
|
|
return this._weatherApp != null;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-02-23 21:55:33 +00:00
|
|
|
|
|
|
|
get loading() {
|
|
|
|
return this._loading;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-02-23 21:55:33 +00:00
|
|
|
|
2017-03-19 13:42:35 +00:00
|
|
|
get hasLocation() {
|
|
|
|
return this._locationValid;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-03-19 13:42:35 +00:00
|
|
|
|
2017-02-23 21:55:33 +00:00
|
|
|
get info() {
|
|
|
|
return this._weatherInfo;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-02-23 21:55:33 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
activateApp() {
|
2019-07-23 10:49:40 +00:00
|
|
|
if (this._weatherApp)
|
|
|
|
this._weatherApp.activate();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-02-23 21:55:33 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
update() {
|
2017-03-19 13:42:35 +00:00
|
|
|
if (!this._locationValid)
|
|
|
|
return;
|
|
|
|
|
2017-02-25 00:36:47 +00:00
|
|
|
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();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-02-23 21:55:33 +00:00
|
|
|
|
2017-03-19 15:34:53 +00:00
|
|
|
get _useAutoLocation() {
|
2017-03-19 13:31:19 +00:00
|
|
|
return this._autoLocationRequested &&
|
2017-03-19 15:29:35 +00:00
|
|
|
this._locationSettings.get_boolean('enabled') &&
|
2019-11-27 22:01:15 +00:00
|
|
|
(!this._needsAuth || this._weatherAuthorized);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-03-19 15:34:53 +00:00
|
|
|
|
2019-12-19 19:50:37 +00:00
|
|
|
async _createWeatherProxy() {
|
|
|
|
const nodeInfo = Gio.DBusNodeInfo.new_for_xml(WeatherIntegrationIface);
|
2019-07-23 10:49:40 +00:00
|
|
|
try {
|
2019-12-19 19:50:37 +00:00
|
|
|
this._weatherProxy = await Gio.DBusProxy.new(
|
|
|
|
Gio.DBus.session,
|
|
|
|
Gio.DBusProxyFlags.DO_NOT_AUTO_START | Gio.DBusProxyFlags.GET_INVALIDATED_PROPERTIES,
|
|
|
|
nodeInfo.lookup_interface(WEATHER_INTEGRATION_IFACE),
|
|
|
|
WEATHER_BUS_NAME,
|
|
|
|
WEATHER_OBJECT_PATH,
|
|
|
|
WEATHER_INTEGRATION_IFACE,
|
|
|
|
null);
|
2019-07-23 10:49:40 +00:00
|
|
|
} catch (e) {
|
|
|
|
log(`Failed to create GNOME Weather proxy: ${e}`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._weatherProxy.connect('g-properties-changed',
|
|
|
|
this._onWeatherPropertiesChanged.bind(this));
|
2019-07-31 23:24:13 +00:00
|
|
|
this._onWeatherPropertiesChanged();
|
2019-07-23 10:49:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_onWeatherPropertiesChanged() {
|
2019-07-31 23:24:13 +00:00
|
|
|
if (this._weatherProxy.g_name_owner == null)
|
|
|
|
return;
|
|
|
|
|
2019-07-23 10:49:40 +00:00
|
|
|
this._settings.set_boolean('automatic-location',
|
|
|
|
this._weatherProxy.AutomaticLocation);
|
|
|
|
this._settings.set_value('locations',
|
|
|
|
new GLib.Variant('av', this._weatherProxy.Locations));
|
|
|
|
}
|
|
|
|
|
|
|
|
_onInstalledChanged() {
|
2019-08-19 19:38:51 +00:00
|
|
|
let hadApp = this._weatherApp != null;
|
2019-07-23 10:49:40 +00:00
|
|
|
this._weatherApp = this._appSystem.lookup_app(WEATHER_APP_ID);
|
2019-08-19 19:38:51 +00:00
|
|
|
let haveApp = this._weatherApp != null;
|
2019-07-23 10:49:40 +00:00
|
|
|
|
|
|
|
if (hadApp !== haveApp)
|
|
|
|
this.emit('changed');
|
2019-11-27 22:01:15 +00:00
|
|
|
|
|
|
|
let neededAuth = this._needsAuth;
|
|
|
|
this._needsAuth = this._weatherApp === null ||
|
|
|
|
this._weatherApp.app_info.has_key('X-Flatpak');
|
|
|
|
|
|
|
|
if (neededAuth !== this._needsAuth)
|
|
|
|
this._updateAutoLocation();
|
2019-07-23 10:49:40 +00:00
|
|
|
}
|
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_loadInfo() {
|
2017-02-23 21:55:33 +00:00
|
|
|
let id = this._weatherInfo.connect('updated', () => {
|
|
|
|
this._weatherInfo.disconnect(id);
|
|
|
|
this._loading = false;
|
|
|
|
});
|
|
|
|
|
|
|
|
this._loading = true;
|
|
|
|
this.emit('changed');
|
|
|
|
|
|
|
|
this._weatherInfo.update();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-02-23 21:55:33 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_locationsEqual(loc1, loc2) {
|
2023-08-07 00:51:19 +00:00
|
|
|
if (loc1 === loc2)
|
2017-02-23 21:55:33 +00:00
|
|
|
return true;
|
|
|
|
|
|
|
|
if (loc1 == null || loc2 == null)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return loc1.equal(loc2);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-02-23 21:55:33 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_setLocation(location) {
|
2017-02-23 21:55:33 +00:00
|
|
|
if (this._locationsEqual(this._weatherInfo.location, location))
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._weatherInfo.abort();
|
|
|
|
this._weatherInfo.set_location(location);
|
2019-08-19 19:38:51 +00:00
|
|
|
this._locationValid = location != null;
|
2017-02-23 21:55:33 +00:00
|
|
|
|
|
|
|
if (location)
|
|
|
|
this._loadInfo();
|
|
|
|
else
|
|
|
|
this.emit('changed');
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-02-23 21:55:33 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_updateLocationMonitoring() {
|
2017-02-23 21:55:33 +00:00
|
|
|
if (this._useAutoLocation) {
|
2023-08-07 00:51:19 +00:00
|
|
|
if (this._gclueLocationChangedId !== 0 || this._gclueService == null)
|
2017-02-23 21:55:33 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
this._gclueLocationChangedId =
|
|
|
|
this._gclueService.connect('notify::location',
|
2023-08-06 23:45:22 +00:00
|
|
|
this._onGClueLocationChanged.bind(this));
|
2017-02-23 21:55:33 +00:00
|
|
|
this._onGClueLocationChanged();
|
|
|
|
} else {
|
|
|
|
if (this._gclueLocationChangedId)
|
|
|
|
this._gclueService.disconnect(this._gclueLocationChangedId);
|
|
|
|
this._gclueLocationChangedId = 0;
|
|
|
|
}
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-02-23 21:55:33 +00:00
|
|
|
|
2019-12-19 19:50:37 +00:00
|
|
|
async _startGClueService() {
|
2017-03-11 12:46:10 +00:00
|
|
|
if (this._gclueStarting)
|
2017-02-23 21:55:33 +00:00
|
|
|
return;
|
|
|
|
|
2017-03-11 12:46:10 +00:00
|
|
|
this._gclueStarting = true;
|
|
|
|
|
2019-12-19 19:50:37 +00:00
|
|
|
try {
|
|
|
|
this._gclueService = await Geoclue.Simple.new(
|
|
|
|
'org.gnome.Shell', Geoclue.AccuracyLevel.CITY, null);
|
|
|
|
} 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();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-02-23 21:55:33 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_onGClueLocationChanged() {
|
2017-02-23 21:55:33 +00:00
|
|
|
let geoLocation = this._gclueService.location;
|
2023-08-09 17:40:35 +00:00
|
|
|
// Provide empty name so GWeather sets location name
|
|
|
|
const location = GWeather.Location.new_detached('',
|
2023-08-06 23:45:22 +00:00
|
|
|
null,
|
|
|
|
geoLocation.latitude,
|
|
|
|
geoLocation.longitude);
|
2017-02-23 21:55:33 +00:00
|
|
|
this._setLocation(location);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-02-23 21:55:33 +00:00
|
|
|
|
2019-08-01 13:18:43 +00:00
|
|
|
_onAutomaticLocationChanged() {
|
|
|
|
let useAutoLocation = this._settings.get_boolean('automatic-location');
|
2023-08-07 00:51:19 +00:00
|
|
|
if (this._autoLocationRequested === useAutoLocation)
|
2017-02-23 21:55:33 +00:00
|
|
|
return;
|
|
|
|
|
2017-03-19 15:34:53 +00:00
|
|
|
this._autoLocationRequested = useAutoLocation;
|
2017-02-23 21:55:33 +00:00
|
|
|
|
2017-03-19 13:31:19 +00:00
|
|
|
this._updateAutoLocation();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-03-19 13:31:19 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_updateAutoLocation() {
|
2017-02-23 21:55:33 +00:00
|
|
|
this._updateLocationMonitoring();
|
|
|
|
|
2017-03-11 12:46:10 +00:00
|
|
|
if (this._useAutoLocation)
|
|
|
|
this._startGClueService();
|
|
|
|
else
|
2017-02-23 21:55:33 +00:00
|
|
|
this._setLocation(this._mostRecentLocation);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-02-23 21:55:33 +00:00
|
|
|
|
2019-08-01 13:18:43 +00:00
|
|
|
_onLocationsChanged() {
|
2022-08-10 09:56:14 +00:00
|
|
|
let locations = this._settings.get_value('locations').deepUnpack();
|
2019-08-01 13:18:43 +00:00
|
|
|
let serialized = locations.shift();
|
2017-02-23 21:55:33 +00:00
|
|
|
let mostRecentLocation = null;
|
|
|
|
|
|
|
|
if (serialized)
|
|
|
|
mostRecentLocation = this._world.deserialize(serialized);
|
|
|
|
|
|
|
|
if (this._locationsEqual(this._mostRecentLocation, mostRecentLocation))
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._mostRecentLocation = mostRecentLocation;
|
|
|
|
|
2017-03-11 12:46:10 +00:00
|
|
|
if (!this._useAutoLocation || !this._gclueStarted)
|
2017-02-23 21:55:33 +00:00
|
|
|
this._setLocation(this._mostRecentLocation);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-03-19 15:29:35 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_onPermStoreChanged(proxy, sender, params) {
|
2019-01-31 14:08:00 +00:00
|
|
|
let [table, id, deleted_, data_, perms] = params;
|
2017-03-19 15:29:35 +00:00
|
|
|
|
2023-08-07 00:51:19 +00:00
|
|
|
if (table !== 'gnome' || id !== 'geolocation')
|
2017-03-19 15:29:35 +00:00
|
|
|
return;
|
|
|
|
|
2019-01-31 15:39:50 +00:00
|
|
|
let permission = perms['org.gnome.Weather'] || ['NONE'];
|
2017-03-19 15:29:35 +00:00
|
|
|
let [accuracy] = permission;
|
2023-08-07 00:51:19 +00:00
|
|
|
this._weatherAuthorized = accuracy !== 'NONE';
|
2017-03-19 15:29:35 +00:00
|
|
|
|
|
|
|
this._updateAutoLocation();
|
2017-02-23 21:55:33 +00:00
|
|
|
}
|
2023-07-10 09:53:00 +00:00
|
|
|
}
|