Timer: Manage time itself and only use one schedule
This commit is contained in:
@@ -81,3 +81,11 @@ There's a hidden setting to configure the offset (in hours) applied to the calcu
|
||||
```
|
||||
gsettings --schemadir ~/.local/share/gnome-shell/extensions/nightthemeswitcher@romainvigier.fr/schemas/ set org.gnome.shell.extensions.nightthemeswitcher.time offset $DESIRED_OFFSET
|
||||
```
|
||||
|
||||
### I have disabled Location services but want to use sunrise and sunset times from my location
|
||||
|
||||
If you know your coordinates, you can enter them in a hidden setting, and the extension will use them to calculate the sunrise and sunset times. You can set it with the `gsettings` command:
|
||||
|
||||
```
|
||||
gsettings --schemadir ~/.local/share/gnome-shell/extensions/nightthemeswitcher@romainvigier.fr/schemas/ set org.gnome.shell.extensions.nightthemeswitcher.time location '($LATITUDE,$LONGITUDE)'
|
||||
```
|
||||
|
||||
@@ -71,17 +71,14 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<key name="manual-schedule" type="b">
|
||||
<default>false</default>
|
||||
</key>
|
||||
<key name="schedule-sunrise" type="d">
|
||||
<key name="sunrise" type="d">
|
||||
<default>6</default>
|
||||
</key>
|
||||
<key name="schedule-sunset" type="d">
|
||||
<key name="sunset" type="d">
|
||||
<default>20</default>
|
||||
</key>
|
||||
<key name="location-sunrise" type="d">
|
||||
<default>6</default>
|
||||
</key>
|
||||
<key name="location-sunset" type="d">
|
||||
<default>20</default>
|
||||
<key name="location" type="(dd)">
|
||||
<default>(91,181)</default>
|
||||
</key>
|
||||
<key name="offset" type="d">
|
||||
<default>0.4</default>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 Romain Vigier <contact AT romainvigier.fr>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/**
|
||||
* Source types.
|
||||
*
|
||||
* @readonly
|
||||
* @enum {string}
|
||||
*/
|
||||
var SourceType = {
|
||||
LOCATION: 'location',
|
||||
SCHEDULE: 'schedule',
|
||||
};
|
||||
@@ -20,13 +20,9 @@ main = [
|
||||
'utils.js',
|
||||
]
|
||||
enums = [
|
||||
'enums/SourceType.js',
|
||||
'enums/Time.js',
|
||||
]
|
||||
modules = [
|
||||
'modules/Source.js',
|
||||
'modules/SourceLocation.js',
|
||||
'modules/SourceSchedule.js',
|
||||
'modules/Switcher.js',
|
||||
'modules/SwitcherCommands.js',
|
||||
'modules/SwitcherTheme.js',
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 Romain Vigier <contact AT romainvigier.fr>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
const { extensionUtils } = imports.misc;
|
||||
|
||||
const Me = extensionUtils.getCurrentExtension();
|
||||
|
||||
const { Time } = Me.imports.enums.Time;
|
||||
|
||||
|
||||
/**
|
||||
* Time source base class.
|
||||
*
|
||||
* It needs to be enabled before being able to retrieve the time and disabled
|
||||
* before being disposed.
|
||||
*
|
||||
* It emits the `time-changed` signal containing the new time when the time
|
||||
* changes.
|
||||
*/
|
||||
var Source = class {
|
||||
enable() {}
|
||||
|
||||
disable() {}
|
||||
|
||||
/**
|
||||
* @type {Time}
|
||||
*/
|
||||
get time() {
|
||||
return Time.UNKNOWN;
|
||||
}
|
||||
};
|
||||
@@ -1,229 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020-2022 Romain Vigier <contact AT romainvigier.fr>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
const { Geoclue, Gio, GLib } = imports.gi;
|
||||
const { extensionUtils } = imports.misc;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const Me = extensionUtils.getCurrentExtension();
|
||||
|
||||
const debug = Me.imports.debug;
|
||||
|
||||
const { Source } = Me.imports.modules.Source;
|
||||
|
||||
const { Time } = Me.imports.enums.Time;
|
||||
|
||||
|
||||
/**
|
||||
* The Location source uses Location Services to get the current sunrise and
|
||||
* sunset times.
|
||||
*
|
||||
* It gets the current user's location with the GeoClue2 DBus proxy and
|
||||
* calculates the times.
|
||||
*
|
||||
* It will recalculate every hour and when the user's location changes to stay
|
||||
* up to date.
|
||||
*
|
||||
* Every second, it will check if the time has changed and signal if that's the
|
||||
* case.
|
||||
*/
|
||||
var SourceLocation = class extends Source {
|
||||
#settings;
|
||||
|
||||
#cancellable = null;
|
||||
#previouslyDaytime = null;
|
||||
#geoclue = null;
|
||||
#geoclueConnection = null;
|
||||
#timeChangeTimer = null;
|
||||
#regularlyUpdateSuntimesTimer = null;
|
||||
#settingsConnections = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.#settings = extensionUtils.getSettings(`${Me.metadata['settings-schema']}.time`);
|
||||
}
|
||||
|
||||
enable() {
|
||||
super.enable();
|
||||
debug.message('Enabling Location source...');
|
||||
this.#cancellable = new Gio.Cancellable();
|
||||
this.#connectSettings();
|
||||
this.#connectToGeoclue();
|
||||
this.#watchForTimeChange();
|
||||
this.#regularlyUpdateSuntimes();
|
||||
this.emit('time-changed', this.time);
|
||||
debug.message('Location source enabled.');
|
||||
}
|
||||
|
||||
disable() {
|
||||
debug.message('Disabling Location source...');
|
||||
this.#stopRegularlyUpdatingSuntimes();
|
||||
this.#stopWatchingForTimeChange();
|
||||
this.#disconnectFromGeoclue();
|
||||
this.#disconnectSettings();
|
||||
this.#cancellable.cancel();
|
||||
this.#cancellable = null;
|
||||
debug.message('Location source disabled.');
|
||||
super.disable();
|
||||
}
|
||||
|
||||
|
||||
get time() {
|
||||
return this.#isDaytime() ? Time.DAY : Time.NIGHT;
|
||||
}
|
||||
|
||||
|
||||
#connectSettings() {
|
||||
debug.message('Connecting Location source to settings...');
|
||||
this.#settingsConnections.push({
|
||||
settings: this.#settings,
|
||||
id: this.#settings.connect('changed::offset', this.#updateSuntimes.bind(this)),
|
||||
});
|
||||
}
|
||||
|
||||
#disconnectSettings() {
|
||||
this.#settingsConnections.forEach(({ settings, id }) => settings.disconnect(id));
|
||||
this.#settingsConnections = [];
|
||||
debug.message('Disconnected Location source from settings.');
|
||||
}
|
||||
|
||||
|
||||
#connectToGeoclue() {
|
||||
debug.message('Connecting to GeoClue...');
|
||||
Geoclue.Simple.new(
|
||||
'org.gnome.Shell',
|
||||
Geoclue.AccuracyLevel.CITY,
|
||||
this.#cancellable,
|
||||
this.#onGeoclueReady.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
#disconnectFromGeoclue() {
|
||||
debug.message('Disconnecting from GeoClue...');
|
||||
if (this.#geoclueConnection) {
|
||||
this.#geoclue.disconnect(this.#geoclueConnection);
|
||||
this.#geoclueConnection = null;
|
||||
}
|
||||
debug.message('Disconnected from GeoClue.');
|
||||
}
|
||||
|
||||
|
||||
#onGeoclueReady(_, result) {
|
||||
this.#geoclue = Geoclue.Simple.new_finish(result);
|
||||
if (!this.#geoclue) {
|
||||
console.error(`[${Me.metadata.name}] Unable to retrieve the location, using the manual schedule times instead.`);
|
||||
this.#settings.set_double('location-sunrise', this.#settings.get_double('schedule-sunrise'));
|
||||
this.#settings.set_double('location-sunset', this.#settings.get_double('schedule-sunset'));
|
||||
}
|
||||
this.#geoclueConnection = this.#geoclue.connect('notify::location', this.#onLocationUpdated.bind(this));
|
||||
debug.message('Connected to GeoClue.');
|
||||
this.#onLocationUpdated();
|
||||
this.emit('time-changed', this.time);
|
||||
}
|
||||
|
||||
#onLocationUpdated(_geoclue, _location) {
|
||||
debug.message('Location has changed.');
|
||||
this.#updateLocation();
|
||||
this.#updateSuntimes();
|
||||
}
|
||||
|
||||
|
||||
#updateLocation() {
|
||||
if (this.#geoclue) {
|
||||
debug.message('Updating location...');
|
||||
const { latitude, longitude } = this.#geoclue.get_location();
|
||||
this.location = new Map([
|
||||
['latitude', latitude],
|
||||
['longitude', longitude],
|
||||
]);
|
||||
debug.message(`Current location: (${latitude};${longitude})`);
|
||||
}
|
||||
}
|
||||
|
||||
#updateSuntimes() {
|
||||
if (!this.location)
|
||||
return;
|
||||
|
||||
debug.message('Updating sun times...');
|
||||
|
||||
Math.rad = degrees => degrees * Math.PI / 180;
|
||||
Math.deg = radians => radians * 180 / Math.PI;
|
||||
|
||||
// Calculations from https://www.esrl.noaa.gov/gmd/grad/solcalc/calcdetails.html
|
||||
const latitude = this.location.get('latitude');
|
||||
const longitude = this.location.get('longitude');
|
||||
|
||||
const dtNow = GLib.DateTime.new_now_local();
|
||||
const dtZero = GLib.DateTime.new_utc(1900, 1, 1, 0, 0, 0);
|
||||
|
||||
const timeSpan = dtNow.difference(dtZero);
|
||||
|
||||
const date = timeSpan / 1000 / 1000 / 60 / 60 / 24 + 2;
|
||||
const tzOffset = dtNow.get_utc_offset() / 1000 / 1000 / 60 / 60;
|
||||
|
||||
const julianDay = date + 2415018.5 - tzOffset / 24;
|
||||
const julianCentury = (julianDay - 2451545) / 36525;
|
||||
const geomMeanLongSun = (280.46646 + julianCentury * (36000.76983 + julianCentury * 0.0003032)) % 360;
|
||||
const geomMeanAnomSun = 357.52911 + julianCentury * (35999.05029 - 0.0001537 * julianCentury);
|
||||
const eccentEarthOrbit = 0.016708634 - julianCentury * (0.000042037 + 0.0000001267 * julianCentury);
|
||||
const sunEqOfCtr = Math.sin(Math.rad(geomMeanAnomSun)) * (1.914602 - julianCentury * (0.004817 + 0.000014 * julianCentury)) + Math.sin(Math.rad(2 * geomMeanAnomSun)) * (0.019993 - 0.000101 * julianCentury) + Math.sin(Math.rad(3 * geomMeanAnomSun)) * 0.000289;
|
||||
const sunTrueLong = geomMeanLongSun + sunEqOfCtr;
|
||||
const sunAppLong = sunTrueLong - 0.00569 - 0.00478 * Math.sin(Math.rad(125.04 - 1934.136 * julianCentury));
|
||||
const meanObliqEcliptic = 23 + (26 + ((21.448 - julianCentury * (46.815 + julianCentury * (0.00059 - julianCentury * 0.001813)))) / 60) / 60;
|
||||
const obliqCorr = meanObliqEcliptic + 0.00256 * Math.cos(Math.rad(125.04 - 1934.136 * julianCentury));
|
||||
const sunDeclin = Math.deg(Math.asin(Math.sin(Math.rad(obliqCorr)) * Math.sin(Math.rad(sunAppLong))));
|
||||
const varY = Math.tan(Math.rad(obliqCorr / 2)) * Math.tan(Math.rad(obliqCorr / 2));
|
||||
const eqOfTime = 4 * Math.deg(varY * Math.sin(2 * Math.rad(geomMeanLongSun)) - 2 * eccentEarthOrbit * Math.sin(Math.rad(geomMeanAnomSun)) + 4 * eccentEarthOrbit * varY * Math.sin(Math.rad(geomMeanAnomSun)) * Math.cos(2 * Math.rad(geomMeanLongSun)) - 0.5 * varY * varY * Math.sin(4 * Math.rad(geomMeanLongSun)) - 1.25 * eccentEarthOrbit * eccentEarthOrbit * Math.sin(2 * Math.rad(geomMeanAnomSun)));
|
||||
const haSunrise = Math.deg(Math.acos(Math.cos(Math.rad(90.833)) / (Math.cos(Math.rad(latitude)) * Math.cos(Math.rad(sunDeclin))) - Math.tan(Math.rad(latitude)) * Math.tan(Math.rad(sunDeclin))));
|
||||
const solarNoon = (720 - 4 * longitude - eqOfTime + tzOffset * 60) / 1440;
|
||||
|
||||
const timeSunrise = solarNoon - haSunrise * 4 / 1440;
|
||||
const timeSunset = solarNoon + haSunrise * 4 / 1440;
|
||||
|
||||
const offset = this.#settings.get_double('offset');
|
||||
const sunrise = timeSunrise * 24 + offset;
|
||||
const sunset = timeSunset * 24 - offset;
|
||||
|
||||
this.#settings.set_double('location-sunrise', sunrise);
|
||||
this.#settings.set_double('location-sunset', sunset);
|
||||
|
||||
debug.message(`New sun times: (sunrise: ${sunrise}; sunset: ${sunset})`);
|
||||
}
|
||||
|
||||
#regularlyUpdateSuntimes() {
|
||||
debug.message('Regularly updating sun times...');
|
||||
this.#regularlyUpdateSuntimesTimer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 3600, () => {
|
||||
this.#updateSuntimes();
|
||||
return GLib.SOURCE_CONTINUE;
|
||||
});
|
||||
}
|
||||
|
||||
#stopRegularlyUpdatingSuntimes() {
|
||||
GLib.Source.remove(this.#regularlyUpdateSuntimesTimer);
|
||||
this.#regularlyUpdateSuntimesTimer = null;
|
||||
debug.message('Stopped regularly updating sun times.');
|
||||
}
|
||||
|
||||
#isDaytime() {
|
||||
const time = GLib.DateTime.new_now_local();
|
||||
const hour = time.get_hour() + time.get_minute() / 60 + time.get_second() / 3600;
|
||||
return hour >= this.#settings.get_double('location-sunrise') && hour <= this.#settings.get_double('location-sunset');
|
||||
}
|
||||
|
||||
#watchForTimeChange() {
|
||||
debug.message('Watching for time change...');
|
||||
this.#timeChangeTimer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => {
|
||||
if (this.#previouslyDaytime !== this.#isDaytime()) {
|
||||
this.#previouslyDaytime = this.#isDaytime();
|
||||
this.emit('time-changed', this.time);
|
||||
}
|
||||
return GLib.SOURCE_CONTINUE;
|
||||
});
|
||||
}
|
||||
|
||||
#stopWatchingForTimeChange() {
|
||||
GLib.Source.remove(this.#timeChangeTimer);
|
||||
debug.message('Stopped watching for time change.');
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(SourceLocation.prototype);
|
||||
@@ -1,79 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020-2022 Romain Vigier <contact AT romainvigier.fr>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
const { GLib } = imports.gi;
|
||||
const { extensionUtils } = imports.misc;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const Me = extensionUtils.getCurrentExtension();
|
||||
|
||||
const debug = Me.imports.debug;
|
||||
|
||||
const { Source } = Me.imports.modules.Source;
|
||||
|
||||
const { Time } = Me.imports.enums.Time;
|
||||
|
||||
|
||||
/**
|
||||
* The Schedule source uses a manual schedule to get the current time.
|
||||
*
|
||||
* Every second, it will check if the time has changed and signal if that's the
|
||||
* case.
|
||||
*
|
||||
* The user can change the schedule in the extension's preferences.
|
||||
*/
|
||||
var SourceSchedule = class extends Source {
|
||||
#settings;
|
||||
|
||||
#previouslyDaytime = null;
|
||||
#timeChangeTimer = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.#settings = extensionUtils.getSettings(`${Me.metadata['settings-schema']}.time`);
|
||||
}
|
||||
|
||||
enable() {
|
||||
super.enable();
|
||||
debug.message('Enabling Schedule source...');
|
||||
this.#watchForTimeChange();
|
||||
this.emit('time-changed', this.time);
|
||||
debug.message('Schedule source enabled.');
|
||||
}
|
||||
|
||||
disable() {
|
||||
debug.message('Disabling Schedule source...');
|
||||
this.#stopWatchingForTimeChange();
|
||||
debug.message('Schedule source disabled.');
|
||||
super.disable();
|
||||
}
|
||||
|
||||
|
||||
get time() {
|
||||
return this.#isDaytime() ? Time.DAY : Time.NIGHT;
|
||||
}
|
||||
|
||||
|
||||
#isDaytime() {
|
||||
const time = GLib.DateTime.new_now_local();
|
||||
const hour = time.get_hour() + time.get_minute() / 60 + time.get_second() / 3600;
|
||||
return hour >= this.#settings.get_double('schedule-sunrise') && hour <= this.#settings.get_double('schedule-sunset');
|
||||
}
|
||||
|
||||
#watchForTimeChange() {
|
||||
debug.message('Watching for time change...');
|
||||
this.#timeChangeTimer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => {
|
||||
if (this.#previouslyDaytime !== this.#isDaytime()) {
|
||||
this.#previouslyDaytime = this.#isDaytime();
|
||||
this.emit('time-changed', this.time);
|
||||
}
|
||||
return GLib.SOURCE_CONTINUE;
|
||||
});
|
||||
}
|
||||
|
||||
#stopWatchingForTimeChange() {
|
||||
GLib.Source.remove(this.#timeChangeTimer);
|
||||
debug.message('Stopped watching for time change.');
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(SourceSchedule.prototype);
|
||||
@@ -77,7 +77,7 @@ var Switcher = class {
|
||||
|
||||
#connectTimer() {
|
||||
debug.message(`Connecting ${this.#name} switcher to Timer...`);
|
||||
this.#timerConnection = this.#timer.connect('time-changed', this.#onTimeChanged.bind(this));
|
||||
this.#timerConnection = this.#timer.connect('notify::time', this.#onTimeChanged.bind(this));
|
||||
}
|
||||
|
||||
#disconnectTimer() {
|
||||
|
||||
+208
-77
@@ -1,9 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2020-2022 Romain Vigier <contact AT romainvigier.fr>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
const { Gio, Meta, Shell } = imports.gi;
|
||||
const { Geoclue, Gio, GLib, GObject, Meta, Shell } = imports.gi;
|
||||
const { extensionUtils } = imports.misc;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const { main } = imports.ui;
|
||||
|
||||
@@ -11,72 +10,100 @@ const Me = extensionUtils.getCurrentExtension();
|
||||
|
||||
const debug = Me.imports.debug;
|
||||
|
||||
const { SourceType } = Me.imports.enums.SourceType;
|
||||
const { Time } = Me.imports.enums.Time;
|
||||
const { SourceLocation } = Me.imports.modules.SourceLocation;
|
||||
const { SourceSchedule } = Me.imports.modules.SourceSchedule;
|
||||
|
||||
|
||||
/**
|
||||
* The Timer is responsible for signaling any time change to the other modules.
|
||||
*
|
||||
* They can connect to its 'time-changed' signal and ask its 'time' property
|
||||
* for the current time.
|
||||
* They can connect to its 'time' property and query it for the current time.
|
||||
*
|
||||
* It will try to use the current location as a time source but will fall back
|
||||
* to a manual schedule if the location services are disabled or if the user
|
||||
* forced the manual schedule in the preferences.
|
||||
*/
|
||||
var Timer = class {
|
||||
var Timer = class extends GObject.Object {
|
||||
#settings;
|
||||
#interfaceSettings;
|
||||
#locationSettings;
|
||||
#time;
|
||||
|
||||
#source = null;
|
||||
#sourceConnectionId = null;
|
||||
#cancellable = null;
|
||||
#previousKeybinding = null;
|
||||
#timeTimeoutId = null;
|
||||
#geoclue = null;
|
||||
#geoclueLocationConnectionId = null;
|
||||
#suntimesTimeoutId = null;
|
||||
#manuallySetTime = false;
|
||||
|
||||
#settingsConnections = [];
|
||||
|
||||
static {
|
||||
GObject.registerClass({
|
||||
Properties: {
|
||||
time: GObject.ParamSpec.string('time', 'Time', 'Time', GObject.ParamFlags.READWRITE, Time.UNKNOWN),
|
||||
},
|
||||
}, this);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.#settings = extensionUtils.getSettings(`${Me.metadata['settings-schema']}.time`);
|
||||
this.#interfaceSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' });
|
||||
this.#locationSettings = new Gio.Settings({ schema: 'org.gnome.system.location' });
|
||||
this.#time = this.#interfaceSettings.get_string('color-scheme') === 'prefer-dark' ? Time.NIGHT : Time.DAY;
|
||||
}
|
||||
|
||||
enable() {
|
||||
debug.message('Enabling Timer...');
|
||||
this.#cancellable = new Gio.Cancellable();
|
||||
this.#connectSettings();
|
||||
this.#createSource();
|
||||
this.#connectSource();
|
||||
this.#enableSource();
|
||||
this.#trackTime();
|
||||
if (this.#settings.get_boolean('manual-schedule')) {
|
||||
debug.message('Using the manual schedule.');
|
||||
} else {
|
||||
debug.message('Using location.');
|
||||
this.#trackLocation();
|
||||
this.#trackSuntimes();
|
||||
}
|
||||
this.#addKeybinding();
|
||||
this.#changeTime(this.#computeTime());
|
||||
debug.message('Timer enabled.');
|
||||
}
|
||||
|
||||
disable() {
|
||||
debug.message('Disabling Timer...');
|
||||
this.#removeKeybinding();
|
||||
this.#disconnectSource();
|
||||
this.#disableSource();
|
||||
this.#untrackSuntimes();
|
||||
this.#untrackLocation();
|
||||
this.#untrackTime();
|
||||
this.#disconnectSettings();
|
||||
this.#cancellable.cancel();
|
||||
debug.message('Timer disabled.');
|
||||
}
|
||||
|
||||
|
||||
get time() {
|
||||
return this.#time;
|
||||
return this.#time || Time.UNKNOWN;
|
||||
}
|
||||
|
||||
set time(time) {
|
||||
if (time === this.#time)
|
||||
#changeTime(time, manual = false) {
|
||||
if (time === this.#time) {
|
||||
if (!manual && this.#manuallySetTime)
|
||||
this.#manuallySetTime = false;
|
||||
return;
|
||||
debug.message(`Time has changed to ${time}.`);
|
||||
}
|
||||
|
||||
if (!manual && time !== this.#time && this.#manuallySetTime)
|
||||
return;
|
||||
|
||||
this.#time = time;
|
||||
this.#manuallySetTime = manual;
|
||||
|
||||
debug.message(manual ? `Time manually set to ${time}.` : `Time changed to ${time}.`);
|
||||
|
||||
main.layoutManager.screenTransition.run();
|
||||
this.#interfaceSettings.set_string('color-scheme', time === Time.NIGHT ? 'prefer-dark' : 'default');
|
||||
this.emit('time-changed', time);
|
||||
this.notify('time');
|
||||
}
|
||||
|
||||
|
||||
@@ -84,11 +111,15 @@ var Timer = class {
|
||||
debug.message('Connecting Timer to settings...');
|
||||
this.#settingsConnections.push({
|
||||
settings: this.#locationSettings,
|
||||
id: this.#locationSettings.connect('changed::enabled', this.#onSourceChanged.bind(this)),
|
||||
id: this.#locationSettings.connect('changed::enabled', this.#onLocationStateChanged.bind(this)),
|
||||
});
|
||||
this.#settingsConnections.push({
|
||||
settings: this.#settings,
|
||||
id: this.#settings.connect('changed::manual-schedule', this.#onSourceChanged.bind(this)),
|
||||
id: this.#settings.connect('changed::manual-schedule', this.#onManualScheduleStateChanged.bind(this)),
|
||||
});
|
||||
this.#settingsConnections.push({
|
||||
settings: this.#settings,
|
||||
id: this.#settings.connect('changed::offset', this.#onOffsetChanged.bind(this)),
|
||||
});
|
||||
this.#settingsConnections.push({
|
||||
settings: this.#settings,
|
||||
@@ -106,67 +137,59 @@ var Timer = class {
|
||||
debug.message('Disconnected Timer from settings.');
|
||||
}
|
||||
|
||||
#createSource() {
|
||||
const source = this.#getSource();
|
||||
switch (source) {
|
||||
case SourceType.LOCATION:
|
||||
this.#source = new SourceLocation();
|
||||
break;
|
||||
case SourceType.SCHEDULE:
|
||||
this.#source = new SourceSchedule();
|
||||
break;
|
||||
|
||||
#trackTime() {
|
||||
debug.message('Watching for time change...');
|
||||
this.#timeTimeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => {
|
||||
this.#changeTime(this.#computeTime());
|
||||
return GLib.SOURCE_CONTINUE;
|
||||
});
|
||||
}
|
||||
|
||||
#untrackTime() {
|
||||
if (this.#timeTimeoutId) {
|
||||
GLib.Source.remove(this.#timeTimeoutId);
|
||||
this.#timeTimeoutId = null;
|
||||
}
|
||||
}
|
||||
|
||||
#enableSource() {
|
||||
this.#source.enable();
|
||||
}
|
||||
|
||||
#disableSource() {
|
||||
if (this.#source)
|
||||
this.#source.disable();
|
||||
this.#source = null;
|
||||
}
|
||||
|
||||
#connectSource() {
|
||||
debug.message('Connecting to time source...');
|
||||
this.#sourceConnectionId = this.#source.connect('time-changed', this.#onTimeChanged.bind(this));
|
||||
}
|
||||
|
||||
#disconnectSource() {
|
||||
if (this.#sourceConnectionId && this.#source)
|
||||
this.#source.disconnect(this.#sourceConnectionId);
|
||||
this.#sourceConnectionId = null;
|
||||
debug.message('Disconnected from time source.');
|
||||
debug.message('Stopped watching for time change.');
|
||||
}
|
||||
|
||||
|
||||
#onSourceChanged() {
|
||||
this.disable();
|
||||
this.enable();
|
||||
#trackLocation() {
|
||||
debug.message('Connecting to GeoClue...');
|
||||
Geoclue.Simple.new(
|
||||
'org.gnome.Shell',
|
||||
Geoclue.AccuracyLevel.CITY,
|
||||
this.#cancellable,
|
||||
this.#onGeoclueReady.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
#onOndemandKeybindingChanged() {
|
||||
this.#removeKeybinding();
|
||||
this.#addKeybinding();
|
||||
}
|
||||
|
||||
#onTimeChanged(_source, newTime) {
|
||||
this.time = newTime;
|
||||
}
|
||||
|
||||
#onColorSchemeChanged() {
|
||||
this.time = this.#interfaceSettings.get_string('color-scheme') === 'prefer-dark' ? Time.NIGHT : Time.DAY;
|
||||
#untrackLocation() {
|
||||
debug.message('Disconnecting from GeoClue...');
|
||||
if (this.#geoclue && this.#geoclueLocationConnectionId) {
|
||||
this.#geoclue.disconnect(this.#geoclueLocationConnectionId);
|
||||
this.#geoclueLocationConnectionId = null;
|
||||
this.#geoclue = null;
|
||||
}
|
||||
debug.message('Disconnected from GeoClue.');
|
||||
}
|
||||
|
||||
|
||||
#getSource() {
|
||||
debug.message('Getting time source...');
|
||||
let source = SourceType.SCHEDULE;
|
||||
if (this.#locationSettings.get_boolean('enabled') && !this.#settings.get_boolean('manual-schedule'))
|
||||
source = SourceType.LOCATION;
|
||||
debug.message(`Time source is ${source}.`);
|
||||
return source;
|
||||
#trackSuntimes() {
|
||||
debug.message('Regularly updating sun times...');
|
||||
this.#suntimesTimeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 3600, () => {
|
||||
this.#updateSuntimes();
|
||||
return GLib.SOURCE_CONTINUE;
|
||||
});
|
||||
}
|
||||
|
||||
#untrackSuntimes() {
|
||||
if (this.#suntimesTimeoutId) {
|
||||
GLib.Source.remove(this.#suntimesTimeoutId);
|
||||
this.#suntimesTimeoutId = null;
|
||||
}
|
||||
debug.message('Stopped regularly updating sun times.');
|
||||
}
|
||||
|
||||
|
||||
@@ -181,7 +204,8 @@ var Timer = class {
|
||||
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
|
||||
Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW,
|
||||
() => {
|
||||
this.time = this.time === Time.NIGHT ? Time.DAY : Time.NIGHT;
|
||||
const time = this.time === Time.NIGHT ? Time.DAY : Time.NIGHT;
|
||||
this.#changeTime(time, true);
|
||||
}
|
||||
);
|
||||
debug.message('Added keybinding.');
|
||||
@@ -194,5 +218,112 @@ var Timer = class {
|
||||
debug.message('Removed keybinding.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#onLocationStateChanged() {
|
||||
this.disable();
|
||||
this.enable();
|
||||
}
|
||||
|
||||
#onManualScheduleStateChanged() {
|
||||
this.disable();
|
||||
this.enable();
|
||||
}
|
||||
|
||||
#onOffsetChanged() {
|
||||
this.#updateSuntimes();
|
||||
}
|
||||
|
||||
#onOndemandKeybindingChanged() {
|
||||
this.#removeKeybinding();
|
||||
this.#addKeybinding();
|
||||
}
|
||||
|
||||
#onColorSchemeChanged() {
|
||||
const time = this.#interfaceSettings.get_string('color-scheme') === 'prefer-dark' ? Time.NIGHT : Time.DAY;
|
||||
this.#changeTime(time, true);
|
||||
}
|
||||
|
||||
#onGeoclueReady(_, result) {
|
||||
try {
|
||||
this.#geoclue = Geoclue.Simple.new_finish(result);
|
||||
this.#geoclueLocationConnectionId = this.#geoclue.connect('notify::location', this.#onLocationChanged.bind(this));
|
||||
debug.message('Connected to GeoClue.');
|
||||
this.#onLocationChanged();
|
||||
} catch (e) {
|
||||
const [latitude, longitude] = this.#settings.get_value('location').deepUnpack();
|
||||
if (latitude >= -90 && latitude <= 90 && longitude >= -180 && longitude <= 180) {
|
||||
console.error(`[${Me.metadata.name}] Unable to retrieve the location, using the last known location instead.`);
|
||||
this.#updateSuntimes();
|
||||
} else {
|
||||
console.error(`[${Me.metadata.name}] Unable to retrieve the location, using the manual schedule times instead.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#onLocationChanged(_geoclue, _location) {
|
||||
debug.message('Location has changed.');
|
||||
const { latitude, longitude } = this.#geoclue.get_location();
|
||||
this.#settings.set_value('location', new GLib.Variant('(dd)', [latitude, longitude]));
|
||||
debug.message(`Current location: (${latitude};${longitude})`);
|
||||
this.#updateSuntimes();
|
||||
}
|
||||
|
||||
|
||||
#computeTime() {
|
||||
const sunrise = this.#settings.get_double('sunrise');
|
||||
const sunset = this.#settings.get_double('sunset');
|
||||
const datetime = GLib.DateTime.new_now_local();
|
||||
const hour = datetime.get_hour() + datetime.get_minute() / 60 + datetime.get_second() / 3600;
|
||||
return hour >= sunrise && hour <= sunset ? Time.DAY : Time.NIGHT;
|
||||
}
|
||||
|
||||
#updateSuntimes() {
|
||||
const [latitude, longitude] = this.#settings.get_value('location').deepUnpack();
|
||||
|
||||
if (latitude < -90 && latitude > 90 && longitude < -180 && longitude > 180)
|
||||
return;
|
||||
|
||||
debug.message('Updating sun times...');
|
||||
|
||||
Math.rad = degrees => degrees * Math.PI / 180;
|
||||
Math.deg = radians => radians * 180 / Math.PI;
|
||||
|
||||
// Calculations from https://www.esrl.noaa.gov/gmd/grad/solcalc/calcdetails.html
|
||||
const dtNow = GLib.DateTime.new_now_local();
|
||||
const dtZero = GLib.DateTime.new_utc(1900, 1, 1, 0, 0, 0);
|
||||
|
||||
const timeSpan = dtNow.difference(dtZero);
|
||||
|
||||
const date = timeSpan / 1000 / 1000 / 60 / 60 / 24 + 2;
|
||||
const tzOffset = dtNow.get_utc_offset() / 1000 / 1000 / 60 / 60;
|
||||
|
||||
const julianDay = date + 2415018.5 - tzOffset / 24;
|
||||
const julianCentury = (julianDay - 2451545) / 36525;
|
||||
const geomMeanLongSun = (280.46646 + julianCentury * (36000.76983 + julianCentury * 0.0003032)) % 360;
|
||||
const geomMeanAnomSun = 357.52911 + julianCentury * (35999.05029 - 0.0001537 * julianCentury);
|
||||
const eccentEarthOrbit = 0.016708634 - julianCentury * (0.000042037 + 0.0000001267 * julianCentury);
|
||||
const sunEqOfCtr = Math.sin(Math.rad(geomMeanAnomSun)) * (1.914602 - julianCentury * (0.004817 + 0.000014 * julianCentury)) + Math.sin(Math.rad(2 * geomMeanAnomSun)) * (0.019993 - 0.000101 * julianCentury) + Math.sin(Math.rad(3 * geomMeanAnomSun)) * 0.000289;
|
||||
const sunTrueLong = geomMeanLongSun + sunEqOfCtr;
|
||||
const sunAppLong = sunTrueLong - 0.00569 - 0.00478 * Math.sin(Math.rad(125.04 - 1934.136 * julianCentury));
|
||||
const meanObliqEcliptic = 23 + (26 + ((21.448 - julianCentury * (46.815 + julianCentury * (0.00059 - julianCentury * 0.001813)))) / 60) / 60;
|
||||
const obliqCorr = meanObliqEcliptic + 0.00256 * Math.cos(Math.rad(125.04 - 1934.136 * julianCentury));
|
||||
const sunDeclin = Math.deg(Math.asin(Math.sin(Math.rad(obliqCorr)) * Math.sin(Math.rad(sunAppLong))));
|
||||
const varY = Math.tan(Math.rad(obliqCorr / 2)) * Math.tan(Math.rad(obliqCorr / 2));
|
||||
const eqOfTime = 4 * Math.deg(varY * Math.sin(2 * Math.rad(geomMeanLongSun)) - 2 * eccentEarthOrbit * Math.sin(Math.rad(geomMeanAnomSun)) + 4 * eccentEarthOrbit * varY * Math.sin(Math.rad(geomMeanAnomSun)) * Math.cos(2 * Math.rad(geomMeanLongSun)) - 0.5 * varY * varY * Math.sin(4 * Math.rad(geomMeanLongSun)) - 1.25 * eccentEarthOrbit * eccentEarthOrbit * Math.sin(2 * Math.rad(geomMeanAnomSun)));
|
||||
const haSunrise = Math.deg(Math.acos(Math.cos(Math.rad(90.833)) / (Math.cos(Math.rad(latitude)) * Math.cos(Math.rad(sunDeclin))) - Math.tan(Math.rad(latitude)) * Math.tan(Math.rad(sunDeclin))));
|
||||
const solarNoon = (720 - 4 * longitude - eqOfTime + tzOffset * 60) / 1440;
|
||||
|
||||
const timeSunrise = solarNoon - haSunrise * 4 / 1440;
|
||||
const timeSunset = solarNoon + haSunrise * 4 / 1440;
|
||||
|
||||
const offset = this.#settings.get_double('offset');
|
||||
const sunrise = timeSunrise * 24 + offset;
|
||||
const sunset = timeSunset * 24 - offset;
|
||||
|
||||
this.#settings.set_double('sunrise', sunrise);
|
||||
this.#settings.set_double('sunset', sunset);
|
||||
|
||||
debug.message(`New sun times: (sunrise: ${sunrise}; sunset: ${sunset})`);
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(Timer.prototype);
|
||||
|
||||
@@ -17,13 +17,8 @@ src/data/ui/ShortcutButton.ui
|
||||
src/data/ui/ThemesPage.ui
|
||||
src/data/ui/TimeChooser.ui
|
||||
|
||||
src/enums/SourceType.js
|
||||
src/enums/SunTime.js
|
||||
src/enums/Time.js
|
||||
|
||||
src/modules/Source.js
|
||||
src/modules/SourceLocation.js
|
||||
src/modules/SourceSchedule.js
|
||||
src/modules/Switcher.js
|
||||
src/modules/SwitcherCommands.js
|
||||
src/modules/SwitcherTheme.js
|
||||
|
||||
@@ -23,8 +23,8 @@ var SchedulePage = GObject.registerClass({
|
||||
|
||||
settings.bind('manual-schedule', this._manual_schedule_switch, 'active', Gio.SettingsBindFlags.DEFAULT);
|
||||
|
||||
settings.bind('schedule-sunrise', this._schedule_sunrise_time_chooser, 'time', Gio.SettingsBindFlags.DEFAULT);
|
||||
settings.bind('schedule-sunset', this._schedule_sunset_time_chooser, 'time', Gio.SettingsBindFlags.DEFAULT);
|
||||
settings.bind('sunrise', this._schedule_sunrise_time_chooser, 'time', Gio.SettingsBindFlags.DEFAULT);
|
||||
settings.bind('sunset', this._schedule_sunset_time_chooser, 'time', Gio.SettingsBindFlags.DEFAULT);
|
||||
|
||||
settings.connect('changed::nightthemeswitcher-ondemand-keybinding', () => {
|
||||
this._keyboard_shortcut_button.keybinding = settings.get_strv('nightthemeswitcher-ondemand-keybinding')[0];
|
||||
|
||||
Reference in New Issue
Block a user