From d9965df197f5df8b7e69694e8057d74c270139c9 Mon Sep 17 00:00:00 2001 From: Romain Vigier Date: Mon, 24 Oct 2022 19:45:49 +0200 Subject: [PATCH] Timer: Manage time itself and only use one schedule --- README.md | 8 + ....extensions.nightthemeswitcher.gschema.xml | 11 +- src/enums/SourceType.js | 13 - src/meson.build | 4 - src/modules/Source.js | 31 -- src/modules/SourceLocation.js | 229 -------------- src/modules/SourceSchedule.js | 79 ----- src/modules/Switcher.js | 2 +- src/modules/Timer.js | 285 +++++++++++++----- src/po/POTFILES | 5 - src/preferences/SchedulePage.js | 4 +- 11 files changed, 223 insertions(+), 448 deletions(-) delete mode 100644 src/enums/SourceType.js delete mode 100644 src/modules/Source.js delete mode 100644 src/modules/SourceLocation.js delete mode 100644 src/modules/SourceSchedule.js diff --git a/README.md b/README.md index 1ea2448..cfcd31d 100644 --- a/README.md +++ b/README.md @@ -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)' +``` diff --git a/src/data/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml b/src/data/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml index f43d8f6..9797c21 100644 --- a/src/data/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml +++ b/src/data/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml @@ -71,17 +71,14 @@ SPDX-License-Identifier: GPL-3.0-or-later false - + 6 - + 20 - - 6 - - - 20 + + (91,181) 0.4 diff --git a/src/enums/SourceType.js b/src/enums/SourceType.js deleted file mode 100644 index f711727..0000000 --- a/src/enums/SourceType.js +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Romain Vigier -// SPDX-License-Identifier: GPL-3.0-or-later - -/** - * Source types. - * - * @readonly - * @enum {string} - */ -var SourceType = { - LOCATION: 'location', - SCHEDULE: 'schedule', -}; diff --git a/src/meson.build b/src/meson.build index 22a60f5..b954ad7 100644 --- a/src/meson.build +++ b/src/meson.build @@ -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', diff --git a/src/modules/Source.js b/src/modules/Source.js deleted file mode 100644 index e097946..0000000 --- a/src/modules/Source.js +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Romain Vigier -// 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; - } -}; diff --git a/src/modules/SourceLocation.js b/src/modules/SourceLocation.js deleted file mode 100644 index b0f1444..0000000 --- a/src/modules/SourceLocation.js +++ /dev/null @@ -1,229 +0,0 @@ -// SPDX-FileCopyrightText: 2020-2022 Romain Vigier -// 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); diff --git a/src/modules/SourceSchedule.js b/src/modules/SourceSchedule.js deleted file mode 100644 index 88be7e8..0000000 --- a/src/modules/SourceSchedule.js +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-FileCopyrightText: 2020-2022 Romain Vigier -// 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); diff --git a/src/modules/Switcher.js b/src/modules/Switcher.js index 67c3483..c406dad 100644 --- a/src/modules/Switcher.js +++ b/src/modules/Switcher.js @@ -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() { diff --git a/src/modules/Timer.js b/src/modules/Timer.js index 6de11d1..7b69a67 100644 --- a/src/modules/Timer.js +++ b/src/modules/Timer.js @@ -1,9 +1,8 @@ // SPDX-FileCopyrightText: 2020-2022 Romain Vigier // 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); diff --git a/src/po/POTFILES b/src/po/POTFILES index 7005a6d..224ee9e 100644 --- a/src/po/POTFILES +++ b/src/po/POTFILES @@ -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 diff --git a/src/preferences/SchedulePage.js b/src/preferences/SchedulePage.js index 80551cb..c234b19 100644 --- a/src/preferences/SchedulePage.js +++ b/src/preferences/SchedulePage.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];