diff --git a/Makefile b/Makefile
index 7d1ae3d..794cd9a 100644
--- a/Makefile
+++ b/Makefile
@@ -74,7 +74,7 @@ pot:
--package-name="$(NAME)" \
--package-version="$(VERSION)" \
--output=./src/po/$(UUID).pot \
- ./src/**/*.js ./src/**/*.xml
+ ./src/*.js ./src/**/*.js ./src/**/*.xml
sed -i '1,4s/SOME DESCRIPTIVE TITLE./$(NAME)/g' ./src/po/$(UUID).pot
sed -i '1,4s/YEAR/$(COPYRIGHT_YEAR)/' ./src/po/$(UUID).pot
sed -i "1,4s/THE PACKAGE'S COPYRIGHT HOLDER/$(AUTHOR_NAME)/" ./src/po/$(UUID).pot
diff --git a/README.md b/README.md
index 82ea905..b89b7d7 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,9 @@

-Automatically toggle between your light and dark GTK theme variants when Night Light activates.
+Automatically toggle between your light and dark GTK theme variants.
+
+Supports Night Light, Location Services and manual schedule.
_Do you need to change your GNOME shell theme as well? Try [Night Shell Switcher](https://gitlab.com/rmnvgr/nightshellswitcher-gnome-shell-extension/)!_
diff --git a/ego_description.txt b/ego_description.txt
index 587a7ee..d864e35 100644
--- a/ego_description.txt
+++ b/ego_description.txt
@@ -1,4 +1,6 @@
-Night mode for GNOME apps! Automatically toggle between your light and dark GTK theme variants when Night Light activates.
+Night mode for GNOME apps! Automatically toggle between your light and dark GTK theme variants.
+
+Supports Night Light, Location Services and manual schedule.
These themes have been tested and work:
- Adapta
diff --git a/src/config.js b/src/config.js
index fb47850..ccf6a17 100644
--- a/src/config.js
+++ b/src/config.js
@@ -16,13 +16,68 @@ You should have received a copy of the GNU General Public License along with
this program. If not, see .
*/
-var EXT_NAME = 'Night Theme Switcher';
-var EXT_UUID = 'nightthemeswitcher@romainvigier.fr';
-
var THEME_GSETTINGS_SCHEMA = 'org.gnome.desktop.interface';
var THEME_GSETTINGS_PROPERTY = 'gtk-theme';
var NIGHTLIGHT_GSETTINGS_SCHEMA = 'org.gnome.settings-daemon.plugins.color';
var NIGHTLIGHT_GSETTINGS_PROPERTY = 'night-light-enabled';
+var LOCATION_GSETTINGS_SCHEMA = 'org.gnome.system.location';
+var LOCATION_GSETTINGS_PROPERTY = 'enabled';
+
+// GeoClue2 interfaces from https://gitlab.freedesktop.org/geoclue/geoclue/
+var GEOCLUE_MANAGER_INTERFACE = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+var GEOCLUE_CLIENT_INTERFACE = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+var GEOCLUE_LOCATION_INTERFACE = `
+
+
+
+
+
+
+
+
+
+
+
+`;
+
var debug = false;
diff --git a/src/metadata.json b/src/metadata.json
index 39d9335..e725f42 100644
--- a/src/metadata.json
+++ b/src/metadata.json
@@ -1,6 +1,6 @@
{
"name": "Night Theme Switcher",
- "description": "Automatically toggle between your light and dark GTK theme variants when Night Light activates.",
+ "description": "Automatically toggle between your light and dark GTK theme variants.",
"uuid": "nightthemeswitcher@romainvigier.fr",
"gettext-domain": "nightthemeswitcher@romainvigier.fr",
"settings-schema": "org.gnome.shell.extensions.nightthemeswitcher",
diff --git a/src/modules/Nightlighter.js b/src/modules/Nightlighter.js
deleted file mode 100644
index dce3cb8..0000000
--- a/src/modules/Nightlighter.js
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
-Night Theme Switcher Gnome Shell extension
-
-Copyright (C) 2020 Romain Vigier
-
-This program is free software: you can redistribute it and/or modify it under
-the terms of the GNU General Public License as published by the Free Software
-Foundation, either version 3 of the License, or (at your option) any later
-version.
-
-This program is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License along with
-this program. If not, see .
-*/
-
-const { extensionUtils, fileUtils } = imports.misc;
-const { Gio } = imports.gi;
-const { main } = imports.ui;
-
-const Me = extensionUtils.getCurrentExtension();
-const config = Me.imports.config;
-
-const { log_debug } = Me.imports.utils;
-
-const Gettext = imports.gettext.domain(config.EXT_UUID);
-const _ = Gettext.gettext;
-
-
-/*
-The Nightlighter establishes a connection with the session bus to get the
-current Night Light status. It listens to changes and reports them.
-
-As Night Light is essential for the extension to work, it continuously checks
-if it is enabled and warns the user if that's not the case. To not overwhelm
-the user with notifications, it only warns once and then only listens to Night
-Light changes to reactivate itself automatically.
-*/
-
-var Nightlighter = class {
-
- constructor() {
- log_debug('Initializing Nightlighter...');
- this.first_nightlight_not_enabled_warning = true;
- this.nightlight_gsettings = new Gio.Settings({ schema: config.NIGHTLIGHT_GSETTINGS_SCHEMA });
- log_debug('Nightlighter initialized.');
- }
-
- enable() {
- log_debug('Enabling NightLighter...');
- try {
- this._listen_to_nightlight_status();
- this._check_nightlight_status();
- this._connect_to_dbus();
- this._listen_to_nightlight_changes();
- this.emit();
- log_debug('Nightlighter enabled.');
- }
- catch(e) {
- if ( e.message ) {
- main.notifyError(config.EXT_NAME, e.message);
- }
- }
- }
-
- disable() {
- log_debug('Disabling Nightlighter...');
- this._stop_listening_to_nightlight_status();
- this._stop_listening_to_nightlight_changes();
- this._disconnect_from_dbus();
- log_debug('Nightlighter disabled.');
- }
-
- get time() {
- return this._get_nightlight_active() ? 'night' : 'day';
- }
-
- subscribe(callback) {
- this.nightlight_change_callback = callback;
- }
-
- emit() {
- if ( this.nightlight_change_callback ) {
- this.nightlight_change_callback();
- }
- }
-
- _is_nightlight_enabled() {
- return this.nightlight_gsettings.get_boolean(config.NIGHTLIGHT_GSETTINGS_PROPERTY);
- }
-
- _check_nightlight_status() {
- if ( !this._is_nightlight_enabled() ) {
- if ( this.first_nightlight_not_enabled_warning ) {
- this.first_nightlight_not_enabled_warning = false;
- const message = _('Night Light must be enabled to use this extension. Please enable it in your system settings.');
- throw new Error(message);
- }
- else {
- throw new Error();
- }
- }
- }
-
- _listen_to_nightlight_status() {
- if ( !this.nightlight_status_connect ) {
- this.nightlight_status_connect = this.nightlight_gsettings.connect(
- 'changed::' + config.NIGHTLIGHT_GSETTINGS_PROPERTY,
- this._on_nightlight_status_change.bind(this)
- );
- log_debug('Listening to Night Light status changes...');
- }
- }
-
- _stop_listening_to_nightlight_status() {
- if ( this.nightlight_gsettings && this.nightlight_status_connect ) {
- this.nightlight_gsettings.disconnect(this.nightlight_status_connect);
- this.nightlight_status_connect = null;
- log_debug('Stopped listening to Night Light status changes.');
- }
- }
-
- _on_nightlight_status_change() {
- log_debug('Night Light status has changed.');
- this.enable();
- }
-
- _connect_to_dbus() {
- if ( !this.dbus_proxy ) {
- log_debug('Connecting to DBus...');
- const color_interface = fileUtils.loadInterfaceXML('org.gnome.SettingsDaemon.Color');
- const ColorProxy = Gio.DBusProxy.makeProxyWrapper(color_interface);
- this.dbus_proxy = new ColorProxy(
- Gio.DBus.session,
- 'org.gnome.SettingsDaemon.Color',
- '/org/gnome/SettingsDaemon/Color'
- );
- if ( !this.dbus_proxy ) {
- const message = _('Unable to create proxy to the session bus.');
- throw new Error(message);
- }
- }
- }
-
- _disconnect_from_dbus() {
- if ( this.dbus_proxy ) {
- this.dbus_proxy = null;
- log_debug('Disconnected from DBus.');
- }
- }
-
- _listen_to_nightlight_changes() {
- if ( !this.nightlight_changes_connect ) {
- this.nightlight_changes_connect = this.dbus_proxy.connect(
- 'g-properties-changed',
- this._on_nightlight_change.bind(this)
- );
- log_debug('Listening to Night Light changes...');
- }
- }
-
- _stop_listening_to_nightlight_changes() {
- if ( this.dbus_proxy && this.nightlight_changes_connect ) {
- this.dbus_proxy.disconnect(this.nightlight_changes_connect);
- this.nightlight_changes_connect = null;
- log_debug('Stopped listening to Night Light changes.');
- }
- }
-
- _on_nightlight_change(dbus_properties, dbus_signals) {
- const signals = dbus_signals.deep_unpack();
- if ( signals.NightLightActive ) {
- log_debug('Night Light has become ' + (signals.NightLightActive.unpack() ? '' : 'in') + 'active.');
- this.emit();
- }
- }
-
- _get_nightlight_active() {
- if ( this.dbus_proxy ) {
- return this.dbus_proxy.NightLightActive;
- }
- }
-
-}
diff --git a/src/modules/Switcher.js b/src/modules/Switcher.js
index b442830..6659854 100644
--- a/src/modules/Switcher.js
+++ b/src/modules/Switcher.js
@@ -24,31 +24,30 @@ const config = Me.imports.config;
const { log_debug } = Me.imports.utils;
-const { Nightlighter } = Me.imports.modules.Nightlighter;
const { Themer } = Me.imports.modules.Themer;
+const { Timer } = Me.imports.modules.Timer;
/*
The Switcher is the orchestrator of the extension.
When the extension is enabled, it asks the Themer to listen to theme changes
-from the user and the Nightlighter to listen to changes in the Night Light
-status:
+from the user and the Timer to listen to changes in the current time:
- On theme change, it asks the Themer to guess the new day and night
variants for that theme, and to apply the relevant variant depending on the
- Night Light status.
- - On Night Light activation or deactivation, it asks the Themer to apply
- the relevant variant.
+ time of the day.
+ - On time of the day change, it asks the Themer to apply the relevant
+ variant.
-When the extension is disabled, it asks the Themer and the Nightlighter to
-disable themselves.
+When the extension is disabled, it asks the Themer and the Timer to disable
+themselves.
*/
var Switcher = class {
constructor() {
log_debug('Initializing extension...');
- extensionUtils.initTranslations(config.EXT_UUID);
+ extensionUtils.initTranslations(Me.metadata.uuid);
log_debug('Extension initialized.');
}
@@ -57,17 +56,16 @@ var Switcher = class {
try {
this.theme = new Themer();
this.theme.enable();
- this.theme.subscribe(this._on_theme_change.bind(this));
+ this.theme.subscribe(this._on_theme_changed.bind(this));
- this.nightlight = new Nightlighter();
- this.nightlight.enable();
- this.nightlight.subscribe(this._on_nightlight_change.bind(this));
+ this.time = new Timer();
+ this.time.enable();
+ this.time.subscribe(this._on_time_changed.bind(this));
- this.theme.set_variant(this.nightlight.time);
log_debug('Extension enabled.');
}
catch(e) {
- main.notifyError(config.EXT_NAME, e.message);
+ main.notifyError(Me.metadata.name, e.message);
}
}
@@ -75,23 +73,23 @@ var Switcher = class {
log_debug('Disabling extension...');
try {
this.theme.disable();
- this.nightlight.disable();
+ this.time.disable();
}
catch(e) {} // Since we're disabling, we'll just ignore errors.
finally {
this.theme = null;
- this.nightlight = null;
+ this.time = null;
}
log_debug('Extension disabled.');
}
- _on_theme_change() {
+ _on_theme_changed() {
this.theme.update_variants();
- this.theme.set_variant(this.nightlight.time);
+ this.theme.set_variant(this.time.current);
}
- _on_nightlight_change() {
- this.theme.set_variant(this.nightlight.time);
+ _on_time_changed() {
+ this.theme.set_variant(this.time.current);
}
}
diff --git a/src/modules/Themer.js b/src/modules/Themer.js
index ab1621a..0d3d093 100644
--- a/src/modules/Themer.js
+++ b/src/modules/Themer.js
@@ -120,7 +120,7 @@ var Themer = class {
if ( !this.theme_change_connect ) {
this.theme_change_connect = this.theme_gsettings.connect(
'changed::' + config.THEME_GSETTINGS_PROPERTY,
- this._on_theme_change.bind(this)
+ this._on_theme_changed.bind(this)
);
log_debug('Listening for theme changes...');
}
@@ -134,7 +134,7 @@ var Themer = class {
}
}
- _on_theme_change() {
+ _on_theme_changed() {
log_debug(`Theme has changed to "${this.current_theme}".`);
this.emit();
}
diff --git a/src/modules/Timer.js b/src/modules/Timer.js
new file mode 100644
index 0000000..b5ffcd4
--- /dev/null
+++ b/src/modules/Timer.js
@@ -0,0 +1,545 @@
+/*
+Night Theme Switcher Gnome Shell extension
+
+Copyright (C) 2020 Romain Vigier
+
+This program is free software: you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see .
+*/
+
+const MainLoop = imports.mainloop;
+const { Gio, GLib } = imports.gi;
+const { extensionUtils, fileUtils } = imports.misc;
+const { main } = imports.ui;
+
+const Me = extensionUtils.getCurrentExtension();
+const config = Me.imports.config;
+
+const { log_debug } = Me.imports.utils;
+
+const Gettext = imports.gettext.domain(Me.metadata.uuid);
+const _ = Gettext.gettext;
+
+
+/*
+The Timer checks for changes in the time of day and reports them.
+
+I can either use Night Light or Location Services as a source.
+
+As Night Light or Location Services are essential for the extension to work,
+it continuously checks if one of them is enabled and warns the user if that's
+not the case. To not overwhelm the user with notifications, it only warns once
+and then only listens to Night Light or Location Services changes to reactivate
+itself automatically.
+*/
+
+var Timer = class {
+
+ constructor() {
+ log_debug('Initializing Timer...');
+ this.settings = extensionUtils.getSettings();
+ this.nightlight_gsettings = new Gio.Settings({ schema: config.NIGHTLIGHT_GSETTINGS_SCHEMA });
+ this.location_gsettings = new Gio.Settings({ schema: config.LOCATION_GSETTINGS_SCHEMA });
+ log_debug('Nightlighter initialized.');
+ }
+
+ enable() {
+ log_debug('Enabling Timer...');
+ try {
+ this.ready = false;
+ this.settings.set_string('time-source', this._get_source());
+ this._listen_to_nightlight_status();
+ this._listen_to_location_status();
+ this._listen_to_force_manual_status();
+ switch (this.settings.get_string('time-source')) {
+ case 'nightlight':
+ log_debug('Using Night Light as a source.');
+ this._connect_to_color_dbus_proxy();
+ this._listen_to_nightlight_changes();
+ break;
+ case 'location':
+ log_debug('Using Location Services as a source.');
+ this._connect_to_geoclue_dbus_proxy();
+ this._listen_to_location_changes();
+ this._connect_to_geoclue_location_dbus_proxy();
+ this._update_location();
+ this._update_location_suntimes();
+ this._watch_for_location_time_change();
+ this._regularly_update_location_suntimes();
+ break;
+ case 'manual':
+ log_debug('Using manual schedule as a source.');
+ this._watch_for_location_time_change();
+ }
+ this.ready = true;
+ this.emit();
+ log_debug('Timer enabled.');
+ }
+ catch(e) {
+ if ( e.message ) {
+ main.notifyError(Me.metadata.name, e.message);
+ }
+ }
+ }
+
+ disable() {
+ log_debug('Disabling Timer...');
+ this._stop_listening_to_nightlight_status();
+ this._stop_listening_to_location_status();
+ this._stop_listening_to_force_manual_status();
+ switch (this.settings.get_string('time-source')) {
+ case 'nightlight':
+ this._stop_listening_to_nightlight_changes();
+ this._disconnect_from_color_dbus_proxy();
+ break;
+ case 'location':
+ this._stop_regularly_updating_location_suntimes();
+ this._stop_watching_for_location_time_change();
+ this._stop_listening_to_location_changes();
+ this._disconnect_from_geoclue_location_dbus_proxy();
+ this._disconnect_from_geoclue_dbus_proxy();
+ break;
+ }
+ log_debug('Timer disabled.');
+ }
+
+ get current() {
+ if ( this.ready ) {
+ switch (this.settings.get_string('time-source')) {
+ case 'nightlight':
+ return this._is_nightlight_active() ? 'night' : 'day';
+ case 'location':
+ case 'manual':
+ return this._is_location_daytime() ? 'day' : 'night';
+ }
+ }
+ else {
+ return 'day';
+ }
+ }
+
+ subscribe(callback) {
+ this.time_change_callback = callback;
+ }
+
+ emit() {
+ if ( this.time_change_callback ) {
+ this.time_change_callback();
+ }
+ }
+
+ _get_source() {
+ if ( this._is_force_manual_enabled() ) {
+ return 'manual';
+ }
+ else if ( this._is_nightlight_enabled() ) {
+ return 'nightlight';
+ }
+ else if ( this._is_location_enabled() ) {
+ return 'location';
+ }
+ else {
+ return 'manual';
+ }
+ }
+
+
+ /*
+ Check if Night Light is enabled and restart the Timer on changes.
+ */
+
+ _is_nightlight_enabled() {
+ return this.nightlight_gsettings.get_boolean(config.NIGHTLIGHT_GSETTINGS_PROPERTY);
+ }
+
+ _listen_to_nightlight_status() {
+ if ( !this.nightlight_status_connect ) {
+ this.nightlight_status_connect = this.nightlight_gsettings.connect(
+ 'changed::' + config.NIGHTLIGHT_GSETTINGS_PROPERTY,
+ this._on_nightlight_status_changed.bind(this)
+ );
+ log_debug('Listening to Night Light status changes...');
+ }
+ }
+
+ _stop_listening_to_nightlight_status() {
+ if ( this.nightlight_gsettings && this.nightlight_status_connect ) {
+ this.nightlight_gsettings.disconnect(this.nightlight_status_connect);
+ this.nightlight_status_connect = null;
+ log_debug('Stopped listening to Night Light status changes.');
+ }
+ }
+
+ _on_nightlight_status_changed() {
+ log_debug('Night Light status has changed.');
+ this.enable();
+ }
+
+
+ /*
+ Check if Location Services are enabled and restart the Timer on changes.
+ */
+
+ _is_location_enabled() {
+ return this.location_gsettings.get_boolean(config.LOCATION_GSETTINGS_PROPERTY);
+ }
+
+ _listen_to_location_status() {
+ if ( !this.location_status_connect ) {
+ this.location_status_connect = this.location_gsettings.connect(
+ 'changed::' + config.LOCATION_GSETTINGS_PROPERTY,
+ this._on_location_status_changed.bind(this)
+ );
+ log_debug('Listening to Location Services status changes...');
+ }
+ }
+
+ _stop_listening_to_location_status() {
+ if ( this.location_gsettings && this.location_status_connect ) {
+ this.location_gsettings.disconnect(this.location_status_connect);
+ this.location_status_connect = null;
+ log_debug('Stopped listening to Location Services status changes.');
+ }
+ }
+
+ _on_location_status_changed() {
+ log_debug('Location Services status has changed.');
+ this.enable();
+ }
+
+ /*
+ Check if the user wants to force a manual schedule and restart the Timer on
+ changes.
+ */
+
+ _is_force_manual_enabled() {
+ return this.settings.get_boolean('time-force-manual');
+ }
+
+ _listen_to_force_manual_status() {
+ if ( !this.force_manual_status_connect ) {
+ this.force_manual_status_connect = this.settings.connect(
+ 'changed::time-force-manual',
+ this._on_force_manual_status_changed.bind(this)
+ );
+ log_debug('Listening to manual schedule status changes...');
+ }
+ }
+
+ _stop_listening_to_force_manual_status() {
+ if ( this.settings && this.force_manual_status_connect ) {
+ this.settings.disconnect(this.force_manual_status_connect);
+ this.force_manual_status_connect = null;
+ log_debug('Stopped listening to manual schedule status changes.');
+ }
+ }
+
+ _on_force_manual_status_changed() {
+ log_debug('Manual schedule status has changed.');
+ this.enable();
+ }
+
+
+ /*
+ Use Night Light as a time source.
+ */
+
+ _connect_to_color_dbus_proxy() {
+ try {
+ log_debug('Connecting to Color DBus proxy...');
+ const color_interface = fileUtils.loadInterfaceXML('org.gnome.SettingsDaemon.Color');
+ const ColorProxy = Gio.DBusProxy.makeProxyWrapper(color_interface);
+ this.color_dbus_proxy = new ColorProxy(
+ Gio.DBus.session,
+ 'org.gnome.SettingsDaemon.Color',
+ '/org/gnome/SettingsDaemon/Color'
+ );
+ log_debug('Connected to Color DBus proxy.');
+ }
+ catch(e) {
+ const message = _('Unable to connect to Color DBus proxy.');
+ throw new Error(message);
+ }
+ }
+
+ _disconnect_from_color_dbus_proxy() {
+ if ( this.color_dbus_proxy ) {
+ log_debug('Disconnecting from Color DBus Proxy...');
+ this.color_dbus_proxy = null;
+ log_debug('Disconnected from Color DBus Proxy.');
+ }
+ }
+
+ _listen_to_nightlight_changes() {
+ if ( !this.nightlight_changes_connect ) {
+ this.nightlight_changes_connect = this.color_dbus_proxy.connect(
+ 'g-properties-changed',
+ this._on_nightlight_changed.bind(this)
+ );
+ log_debug('Listening to Night Light changes...');
+ }
+ }
+
+ _stop_listening_to_nightlight_changes() {
+ if ( this.color_dbus_proxy && this.nightlight_changes_connect ) {
+ this.color_dbus_proxy.disconnect(this.nightlight_changes_connect);
+ this.nightlight_changes_connect = null;
+ log_debug('Stopped listening to Night Light changes.');
+ }
+ }
+
+ _on_nightlight_changed(sender, dbus_properties) {
+ const properties = dbus_properties.deep_unpack();
+ if ( properties.NightLightActive ) {
+ log_debug('Night Light has become ' + (properties.NightLightActive.unpack() ? '' : 'in') + 'active.');
+ this.emit();
+ }
+ }
+
+ _is_nightlight_active() {
+ if ( this.color_dbus_proxy ) {
+ return this.color_dbus_proxy.NightLightActive;
+ }
+ }
+
+
+ /*
+ Use location as a time source.
+ */
+
+ _connect_to_geoclue_dbus_proxy() {
+ try {
+ log_debug('Connecting to GeoClue Manager DBus proxy...');
+ const GeoClueManagerProxy = Gio.DBusProxy.makeProxyWrapper(config.GEOCLUE_MANAGER_INTERFACE);
+ this.geoclue_manager_dbus_proxy = new GeoClueManagerProxy(
+ Gio.DBus.system,
+ 'org.freedesktop.GeoClue2',
+ '/org/freedesktop/GeoClue2/Manager'
+ );
+ log_debug('Connected to GeoClue Manager DBus proxy.');
+ }
+ catch(e) {
+ const message = _('Unable to connect to GeoClue Manager DBus proxy.');
+ throw new Error(message);
+ }
+
+ try {
+ log_debug('Requesting new GeoClue Client...');
+ this.geoclue_client = this.geoclue_manager_dbus_proxy.GetClientSync()[0];
+ log_debug(`Got a GeoClue Client at ${this.geoclue_client}`);
+ }
+ catch(e) {
+ const message = _('Unable to get a GeoClue Client.');
+ throw new Error(message);
+ }
+
+ try {
+ log_debug('Connecting to GeoClue Client DBus proxy...');
+ const GeoClueClientProxy = Gio.DBusProxy.makeProxyWrapper(config.GEOCLUE_CLIENT_INTERFACE);
+ this.geoclue_client_dbus_proxy = new GeoClueClientProxy(
+ Gio.DBus.system,
+ 'org.freedesktop.GeoClue2',
+ this.geoclue_client
+ );
+ this.geoclue_client_dbus_proxy.DesktopId = Me.metadata.uuid;
+ this.geoclue_client_dbus_proxy.DistanceThreshold = 10000;
+ this.geoclue_client_dbus_proxy.RequestedAccuracyLevel = 4;
+ log_debug('Connected to GeoClue Client DBus proxy.');
+ }
+ catch(e) {
+ const message = _('Unable to connect to GeoClue Client DBus proxy.');
+ throw new Error(message);
+ }
+ }
+
+ _disconnect_from_geoclue_dbus_proxy() {
+ if ( this.geoclue_client_dbus_proxy ) {
+ log_debug('Disconnecting from GeoClue Client DBus proxy...');
+ if ( this.geoclue_manager_dbus_proxy && this.geoclue_client ) {
+ this.geoclue_manager_dbus_proxy.DeleteClientSync(this.geoclue_client);
+ this.geoclue_client = null;
+ }
+ this.geoclue_client_dbus_proxy = null;
+ log_debug('Disconnected from GeoClue Client DBus Proxy.');
+ }
+
+ if ( this.geoclue_manager_dbus_proxy ) {
+ log_debug('Disconnecting from GeoClue Manager DBus proxy...');
+ this.geoclue_manager_dbus_proxy = null;
+ log_debug('Disconnected from GeoClue Manager DBus proxy.');
+ }
+ }
+
+ _listen_to_location_changes() {
+ if ( !this.location_changes_connect ) {
+ this.location_changes_connect = this.geoclue_client_dbus_proxy.connectSignal('LocationUpdated', this._on_location_changed.bind(this));
+ this.geoclue_client_dbus_proxy.StartSync();
+ log_debug('Listening to location changes...');
+ }
+ }
+
+ _stop_listening_to_location_changes() {
+ if ( this.geoclue_client_dbus_proxy && this.location_changes_connect ) {
+ this.geoclue_client_dbus_proxy.disconnectSignal(this.location_changes_connect);
+ this.location_changes_connect = null;
+ this.geoclue_client_dbus_proxy.StopSync();
+ log_debug('Stopped listening to location changes.');
+ }
+ }
+
+ _on_location_changed(proxy, sender, [old_location_path, new_location_path]) {
+ log_debug('Location has changed.');
+ this._connect_to_geoclue_location_dbus_proxy(new_location_path);
+ this._update_location();
+ this._update_location_suntimes();
+ }
+
+ _connect_to_geoclue_location_dbus_proxy(path) {
+ if ( !path && this.geoclue_client_dbus_proxy ) {
+ path = this.geoclue_client_dbus_proxy.Location;
+ }
+ if ( path !== '/' ) {
+ try {
+ log_debug('Connecting to GeoClue Location DBus proxy...');
+ const GeoClueLocationProxy = Gio.DBusProxy.makeProxyWrapper(config.GEOCLUE_LOCATION_INTERFACE);
+ this.geoclue_location_dbus_proxy = new GeoClueLocationProxy(
+ Gio.DBus.system,
+ 'org.freedesktop.GeoClue2',
+ path
+ );
+ log_debug('Connected to GeoClue Location DBus proxy.');
+ }
+ catch(e) {
+ const message = _('Unable to connect to GeoClue Location DBus proxy.');
+ throw new Error(message);
+ }
+ }
+ }
+
+ _disconnect_from_geoclue_location_dbus_proxy() {
+ if ( this.geoclue_location_dbus_proxy ) {
+ log_debug('Disconnecting from GeoClue Location DBus proxy...');
+ this.geoclue_location_dbus_proxy = null;
+ log_debug('Disconnected from GeoClue Location DBus proxy.')
+ }
+ }
+
+ _update_location() {
+ if ( this.geoclue_location_dbus_proxy ) {
+ try {
+ log_debug('Updating location...');
+ const latitude = this.geoclue_location_dbus_proxy.Latitude;
+ const longitude = this.geoclue_location_dbus_proxy.Longitude;
+
+ this.location = new Map([
+ ['latitude', latitude],
+ ['longitude', longitude]
+ ]);
+ log_debug(`Current location: (${latitude};${longitude})`);
+ }
+ catch(e) {
+ const message = _('Unable to get current location.');
+ throw new Error(message);
+ }
+ }
+ }
+
+ _update_location_suntimes() {
+ if ( !this.location ) {
+ return;
+ }
+
+ log_debug('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 dt_now = GLib.DateTime.new_now_local();
+ const dt_zero = GLib.DateTime.new_utc(1900, 1, 1, 0, 0, 0);
+
+ const time_span = dt_now.difference(dt_zero);
+
+ const date = time_span / 1000 / 1000 / 60 / 60 / 24 + 2;
+ const tz_offset = dt_now.get_utc_offset() / 1000 / 1000 / 60 / 60;
+ const time_past_local_midnight = 0;
+
+ const julian_day = date + 2415018.5 + time_past_local_midnight - tz_offset / 24;
+ const julian_century = (julian_day - 2451545) / 36525;
+ const geom_mean_long_sun = (280.46646 + julian_century * (36000.76983 + julian_century * 0.0003032)) % 360;
+ const geom_mean_anom_sun = 357.52911 + julian_century * (35999.05029 - 0.0001537 * julian_century);
+ const eccent_earth_orbit = 0.016708634 - julian_century * (0.000042037 + 0.0000001267 * julian_century);
+ const sun_eq_of_ctr = Math.sin(Math.rad(geom_mean_anom_sun)) * (1.914602 - julian_century * (0.004817 + 0.000014 * julian_century)) + Math.sin(Math.rad(2 * geom_mean_anom_sun)) * (0.019993 - 0.000101 * julian_century) + Math.sin(Math.rad(3 * geom_mean_anom_sun)) * 0.000289;
+ const sun_true_long = geom_mean_long_sun + sun_eq_of_ctr;
+ const sun_app_long = sun_true_long - 0.00569 - 0.00478 * Math.sin(Math.rad(125.04 - 1934.136 * julian_century));
+ const mean_obliq_ecliptic = 23 + (26 + ((21.448 - julian_century * (46.815 + julian_century * (0.00059 - julian_century * 0.001813)))) / 60) / 60;
+ const obliq_corr = mean_obliq_ecliptic + 0.00256 * Math.cos(Math.rad(125.04 - 1934.136 * julian_century));
+ const sun_declin = Math.deg(Math.asin(Math.sin(Math.rad(obliq_corr)) * Math.sin(Math.rad(sun_app_long))));
+ const var_y = Math.tan(Math.rad(obliq_corr / 2)) * Math.tan(Math.rad(obliq_corr / 2));
+ const eq_of_time = 4 * Math.deg(var_y * Math.sin(2 * Math.rad(geom_mean_long_sun)) - 2 * eccent_earth_orbit * Math.sin(Math.rad(geom_mean_anom_sun)) + 4 * eccent_earth_orbit * var_y * Math.sin(Math.rad(geom_mean_anom_sun)) * Math.cos(2 * Math.rad(geom_mean_long_sun)) - 0.5 * var_y * var_y * Math.sin(4 * Math.rad(geom_mean_long_sun)) - 1.25 * eccent_earth_orbit * eccent_earth_orbit * Math.sin(2 * Math.rad(geom_mean_anom_sun)));
+ const ha_sunrise = Math.deg(Math.acos(Math.cos(Math.rad(90.833)) / (Math.cos(Math.rad(latitude)) * Math.cos(Math.rad(sun_declin))) - Math.tan(Math.rad(latitude)) * Math.tan(Math.rad(sun_declin))));
+ const solar_noon = (720 - 4 * longitude - eq_of_time + tz_offset * 60) / 1440;
+
+ const sunrise_time = solar_noon - ha_sunrise * 4 / 1440;
+ const sunset_time = solar_noon + ha_sunrise * 4 / 1440;
+
+ const sunrise = sunrise_time * 24;
+ const sunset = sunset_time * 24;
+
+ this.settings.set_double('time-sunrise', sunrise);
+ this.settings.set_double('time-sunset', sunset);
+ log_debug(`New sun times: (sunrise: ${sunrise}; sunset: ${sunset})`);
+ }
+
+ _regularly_update_location_suntimes() {
+ this.regularly_update_suntimes_timer = MainLoop.timeout_add_seconds(3600, () => {
+ this._update_location_suntimes();
+ return true; // Repeat the loop
+ });
+ }
+
+ _stop_regularly_updating_location_suntimes() {
+ if ( this.regularly_update_suntimes_timer ) {
+ MainLoop.source_remove(this.regularly_update_suntimes_timer);
+ this.regularly_update_suntimes_timer = null;
+ }
+ }
+
+ _is_location_daytime() {
+ 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('time-sunrise') && hour <= this.settings.get_double('time-sunset') );
+ }
+
+ _watch_for_location_time_change() {
+ this.location_time_change_timer = MainLoop.timeout_add_seconds(1, () => {
+ const previous_time = this.location_daytime;
+ if ( previous_time !== this._is_location_daytime() ) {
+ log_debug('Time of the day has changed.');
+ this.location_daytime = this._is_location_daytime();
+ this.emit();
+ }
+ return true; // Repeat the loop
+ }, null);
+ }
+
+ _stop_watching_for_location_time_change() {
+ if ( this.location_time_change_timer ) {
+ MainLoop.source_remove(this.location_time_change_timer);
+ this.location_time_change_timer = null;
+ }
+ }
+
+}
diff --git a/src/po/fr.po b/src/po/fr.po
index 27c2353..23d66da 100644
--- a/src/po/fr.po
+++ b/src/po/fr.po
@@ -1,34 +1,70 @@
# Night Theme Switcher
# Copyright (C) 2019 Romain Vigier
# This file is distributed under the same license as the Night Theme Switcher package.
-# Romain Vigier <>, 2019-2020.
+# Romain Vigier , 2019-2020.
#
msgid ""
msgstr ""
"Project-Id-Version: Night Theme Switcher 7\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-04-12 14:18+0200\n"
-"PO-Revision-Date: 2020-04-12 14:21+0200\n"
-"Last-Translator: Romain Vigier <>\n"
-"Language-Team: French \n"
-"Language: fr\n"
+"POT-Creation-Date: 2020-05-06 16:40+0200\n"
+"PO-Revision-Date: 2020-05-06 16:40+0200\n"
+"Last-Translator: Romain Vigier \n"
+"Language-Team: French - France \n"
+"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
-"X-Generator: Gtranslator 3.34.0\n"
+"X-Generator: Gtranslator 3.36.0\n"
-#: src/modules/Nightlighter.js:101
+#: src/prefs.js:58
msgid ""
-"Night Light must be enabled to use this extension. Please enable it in your "
-"system settings."
+"The extension will try to use Night Light or Location Services to "
+"automatically set your current sunrise and sunset times if they are "
+"enabled.\n"
+"If you prefer, you can set a manual schedule."
msgstr ""
-"Le mode nuit doit être activé pour utiliser l’extension. Veuillez l’activer "
-"dans les réglages du système."
+"S’ils sont activés, l’extension essayera d’utiliser le mode nuit ou les "
+"services de localisation pour définir automatiquement les heures de lever et "
+"de coucher du soleil.\n"
+"Si vous préférez, vous pouvez définir manuellement un horaire."
-#: src/modules/Nightlighter.js:144
-msgid "Unable to create proxy to the session bus."
-msgstr "Impossible de créer une passerelle vers le bus de la session."
+#: src/prefs.js:93
+msgid "Manual schedule:"
+msgstr "Horaire manuel :"
+
+#: src/prefs.js:138
+msgid "Sunrise: "
+msgstr "Lever du soleil :"
+
+#: src/prefs.js:139
+msgid "Sunset: "
+msgstr "Coucher du soleil :"
+
+#: src/modules/Timer.js:268
+msgid "Unable to connect to Color DBus proxy."
+msgstr "Impossible de se connecter à la passerelle DBus Color."
+
+#: src/modules/Timer.js:330
+msgid "Unable to connect to GeoClue Manager DBus proxy."
+msgstr "Impossible de se connecter à la passerelle DBus GeoClue Manager."
+
+#: src/modules/Timer.js:340
+msgid "Unable to get a GeoClue Client."
+msgstr "Impossible d’obtenir un client GeoClue."
+
+#: src/modules/Timer.js:358
+msgid "Unable to connect to GeoClue Client DBus proxy."
+msgstr "Impossible de se connecter à la passerelle DBus GeoClue Client."
+
+#: src/modules/Timer.js:417
+msgid "Unable to connect to GeoClue Location DBus proxy."
+msgstr "Impossible de se connecter à la passerelle DBus GeoClue Location."
+
+#: src/modules/Timer.js:447
+msgid "Unable to get current location."
+msgstr "Impossible d’obtenir l’emplacement actuel."
#: src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml:6
msgid "Original theme"
@@ -54,6 +90,56 @@ msgstr "Thème nocturne"
msgid "The theme to use during nighttime"
msgstr "Thème à utiliser pendant la nuit"
+#: src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml:21
+msgid "Use manual schedule"
+msgstr "Utiliser un horaire manuel"
+
+#: src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml:22
+msgid "Disable automatic times detection"
+msgstr "Désactive la détection automatique des horaires"
+
+#: src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml:31
+msgid "Time source"
+msgstr "Source des horaires"
+
+#: src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml:32
+msgid "The source used to check current time"
+msgstr "Source utilisée pour vérifier les horaires"
+
+#: src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml:36
+msgid "Sunrise time"
+msgstr "Heure du lever"
+
+#: src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml:37
+msgid "When the day starts"
+msgstr "Quand le jour se lève"
+
+#: src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml:41
+msgid "Sunset time"
+msgstr "Heure du coucher"
+
+#: src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml:42
+msgid "When the day ends"
+msgstr "Quand le jour se couche"
+
+#~ msgid ""
+#~ "Night Light or Location Services must be enabled to use this extension. "
+#~ "Please enable it in your system settings."
+#~ msgstr ""
+#~ "Le mode nuit ou les services de localisation doivent être activés pour "
+#~ "utiliser cette extension. Veuillez les activer dans vos paramètres du "
+#~ "système."
+
+#~ msgid ""
+#~ "Night Light must be enabled to use this extension. Please enable it in "
+#~ "your system settings."
+#~ msgstr ""
+#~ "Le mode nuit doit être activé pour utiliser l’extension. Veuillez "
+#~ "l’activer dans les réglages du système."
+
+#~ msgid "Unable to create proxy to the session bus."
+#~ msgstr "Impossible de créer une passerelle vers le bus de la session."
+
#~ msgid "Unable to connect to the session bus."
#~ msgstr "Impossible de se connecter au bus de la session."
diff --git a/src/po/nightthemeswitcher@romainvigier.fr.pot b/src/po/nightthemeswitcher@romainvigier.fr.pot
index b857027..073ddb5 100644
--- a/src/po/nightthemeswitcher@romainvigier.fr.pot
+++ b/src/po/nightthemeswitcher@romainvigier.fr.pot
@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: Night Theme Switcher 22\n"
+"Project-Id-Version: Night Theme Switcher 25\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-04-12 14:18+0200\n"
+"POT-Creation-Date: 2020-05-06 16:40+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -17,14 +17,48 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
-#: src/modules/Nightlighter.js:101
+#: src/prefs.js:58
msgid ""
-"Night Light must be enabled to use this extension. Please enable it in your "
-"system settings."
+"The extension will try to use Night Light or Location Services to "
+"automatically set your current sunrise and sunset times if they are "
+"enabled.\n"
+"If you prefer, you can set a manual schedule."
msgstr ""
-#: src/modules/Nightlighter.js:144
-msgid "Unable to create proxy to the session bus."
+#: src/prefs.js:93
+msgid "Manual schedule:"
+msgstr ""
+
+#: src/prefs.js:138
+msgid "Sunrise: "
+msgstr ""
+
+#: src/prefs.js:139
+msgid "Sunset: "
+msgstr ""
+
+#: src/modules/Timer.js:268
+msgid "Unable to connect to Color DBus proxy."
+msgstr ""
+
+#: src/modules/Timer.js:330
+msgid "Unable to connect to GeoClue Manager DBus proxy."
+msgstr ""
+
+#: src/modules/Timer.js:340
+msgid "Unable to get a GeoClue Client."
+msgstr ""
+
+#: src/modules/Timer.js:358
+msgid "Unable to connect to GeoClue Client DBus proxy."
+msgstr ""
+
+#: src/modules/Timer.js:417
+msgid "Unable to connect to GeoClue Location DBus proxy."
+msgstr ""
+
+#: src/modules/Timer.js:447
+msgid "Unable to get current location."
msgstr ""
#: src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml:6
@@ -50,3 +84,35 @@ msgstr ""
#: src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml:17
msgid "The theme to use during nighttime"
msgstr ""
+
+#: src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml:21
+msgid "Use manual schedule"
+msgstr ""
+
+#: src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml:22
+msgid "Disable automatic times detection"
+msgstr ""
+
+#: src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml:31
+msgid "Time source"
+msgstr ""
+
+#: src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml:32
+msgid "The source used to check current time"
+msgstr ""
+
+#: src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml:36
+msgid "Sunrise time"
+msgstr ""
+
+#: src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml:37
+msgid "When the day starts"
+msgstr ""
+
+#: src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml:41
+msgid "Sunset time"
+msgstr ""
+
+#: src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml:42
+msgid "When the day ends"
+msgstr ""
diff --git a/src/prefs.js b/src/prefs.js
new file mode 100644
index 0000000..3018c64
--- /dev/null
+++ b/src/prefs.js
@@ -0,0 +1,239 @@
+/*
+Night Theme Switcher Gnome Shell extension
+
+Copyright (C) 2020 Romain Vigier
+
+This program is free software: you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see .
+*/
+
+'use strict';
+
+const { Gio, GLib, Gtk } = imports.gi;
+const { extensionUtils } = imports.misc;
+
+const Me = extensionUtils.getCurrentExtension();
+const config = Me.imports.config;
+
+const { log_debug } = Me.imports.utils;
+
+const Gettext = imports.gettext.domain(Me.metadata.uuid);
+const _ = Gettext.gettext;
+
+
+let settings;
+
+
+function init() {
+ log_debug('Initializing preferences...');
+ settings = extensionUtils.getSettings();
+ extensionUtils.initTranslations(Me.metadata.uuid);
+ log_debug('Preferences initialized.');
+}
+
+function buildPrefsWidget() {
+ const prefs_widget = new Gtk.Box({
+ orientation: Gtk.Orientation.VERTICAL,
+ spacing: 30,
+ margin_top: 30,
+ margin_end: 36,
+ margin_start: 36,
+ valign: Gtk.Align.START,
+ halign: Gtk.Align.CENTER,
+ width_request: 450,
+ margin_bottom: 36,
+ visible: true
+ })
+
+ const prefs_info = new Gtk.Label({
+ label: _('The extension will try to use Night Light or Location Services to automatically set your current sunrise and sunset times if they are enabled.\nIf you prefer, you can set a manual schedule.'),
+ wrap: true,
+ max_width_chars: 60,
+ halign: Gtk.Align.FILL,
+ opacity: 0.7,
+ visible: true
+ });
+ prefs_widget.pack_start(prefs_info, true, true, 0);
+
+ const prefs_frame = new Gtk.Frame({
+ can_focus: false,
+ visible: true
+ });
+ prefs_widget.pack_start(prefs_frame, true, true, 0);
+
+ const prefs_list = new Gtk.ListBox({
+ border_width: 0,
+ margin: 0,
+ can_focus: false,
+ selection_mode: Gtk.SelectionMode.NONE,
+ visible: true
+ });
+ prefs_frame.add(prefs_list);
+
+ // Force manual toggle
+ const force_manual_box = new Gtk.Box({
+ margin: 16,
+ spacing: 12,
+ orientation: Gtk.Orientation.HORIZONTAL,
+ can_focus: false,
+ visible: true
+ });
+ prefs_list.add(force_manual_box);
+
+ const force_manual_label = new Gtk.Label({
+ label: _('Manual schedule:'),
+ halign: Gtk.Align.START,
+ visible: true
+ });
+ force_manual_box.pack_start(force_manual_label, true, true, 0);
+
+ const force_manual_toggle = new Gtk.Switch({
+ active: this.settings.get_boolean('time-force-manual'),
+ halign: Gtk.Align.END,
+ visible: true
+ });
+ settings.bind(
+ 'time-force-manual',
+ force_manual_toggle,
+ 'active',
+ Gio.SettingsBindFlags.DEFAULT
+ );
+ force_manual_box.pack_start(force_manual_toggle, false, false, 0);
+
+ prefs_list.add(new Gtk.Separator({
+ orientation: Gtk.Orientation.VERTICAL,
+ can_focus: false,
+ visible: true
+ }));
+
+ // Times schedule
+ ['sunrise', 'sunset'].forEach((suntime, i, suntimes) => {
+ const time_box = new Gtk.Box({
+ margin: 16,
+ spacing: 12,
+ orientation: Gtk.Orientation.HORIZONTAL,
+ can_focus: false,
+ visible: true
+ });
+ prefs_list.add(time_box);
+
+ if ( i < suntimes.length - 1 ) {
+ prefs_list.add(new Gtk.Separator({
+ orientation: Gtk.Orientation.VERTICAL,
+ can_focus: false,
+ visible: true
+ }));
+ }
+
+ const time_labels = new Map([
+ ['sunrise', _('Sunrise: ')],
+ ['sunset', _('Sunset: ')]
+ ]);
+ const time_label = new Gtk.Label({
+ label: time_labels.get(suntime),
+ halign: Gtk.Align.START,
+ visible: true
+ })
+ time_box.pack_start(time_label, true, true, 0);
+
+ const time = settings.get_double(`time-${suntime}`);
+ const hour = Math.trunc(time);
+ const minutes = Math.round((time - hour) * 60);
+
+ const time_hour_spin = new Gtk.SpinButton({
+ adjustment: new Gtk.Adjustment({
+ value: hour,
+ lower: 0,
+ upper: 23,
+ step_increment: 1,
+ page_increment: 0,
+ page_size: 0
+ }),
+ value: hour,
+ numeric: true,
+ wrap: true,
+ orientation: Gtk.Orientation.VERTICAL,
+ visible: true
+ });
+ settings.bind(
+ 'time-force-manual',
+ time_hour_spin,
+ 'sensitive',
+ Gio.SettingsBindFlags.DEFAULT
+ );
+ settings.connect(`changed::time-${suntime}`, () => {
+ const hour = Math.trunc(settings.get_double(`time-${suntime}`));
+ time_hour_spin.value = hour;
+ });
+ time_hour_spin.connect('output', () => {
+ const text = time_hour_spin.adjustment.value.toString().padStart(2, '0');
+ time_hour_spin.set_text(text);
+ return true;
+ });
+ time_hour_spin.connect('value-changed', () => {
+ const old_time = settings.get_double(`time-${suntime}`);
+ const old_hour = Math.trunc(old_time);
+ const minutes = old_time - old_hour;
+ const new_time = time_hour_spin.value + minutes;
+ settings.set_double(`time-${suntime}`, new_time);
+ });
+ time_box.pack_start(time_hour_spin, false, false, 0);
+
+ const time_separator = new Gtk.Label({
+ label: ':',
+ visible: true
+ })
+ time_box.pack_start(time_separator, false, false, 0);
+
+ const time_minutes_spin = new Gtk.SpinButton({
+ adjustment: new Gtk.Adjustment({
+ value: minutes,
+ lower: 0,
+ upper: 59,
+ step_increment: 1,
+ page_increment: 0,
+ page_size: 0
+ }),
+ value: minutes,
+ numeric: true,
+ wrap: true,
+ orientation: Gtk.Orientation.VERTICAL,
+ visible: true
+ });
+ settings.bind(
+ 'time-force-manual',
+ time_minutes_spin,
+ 'sensitive',
+ Gio.SettingsBindFlags.DEFAULT
+ );
+ settings.connect(`changed::time-${suntime}`, () => {
+ const time = settings.get_double(`time-${suntime}`);
+ const hour = Math.trunc(time);
+ const minutes = Math.round((time - hour) * 60);
+ time_minutes_spin.value = minutes;
+ });
+ time_minutes_spin.connect('output', () => {
+ const text = time_minutes_spin.adjustment.value.toString().padStart(2, '0');
+ time_minutes_spin.set_text(text);
+ return true;
+ });
+ time_minutes_spin.connect('value-changed', () => {
+ const hour = Math.trunc(settings.get_double(`time-${suntime}`));
+ const minutes = time_minutes_spin.value / 60;
+ const new_time = hour + minutes;
+ settings.set_double(`time-${suntime}`, new_time);
+ });
+ time_box.pack_start(time_minutes_spin, false, false, 0);
+ });
+
+ return prefs_widget;
+}
diff --git a/src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml b/src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml
index dbf3fab..7597ae4 100644
--- a/src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml
+++ b/src/schemas/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml
@@ -16,5 +16,30 @@
Night theme
The theme to use during nighttime
+
+ false
+ Use manual schedule
+ Disable automatic times detection
+
+
+
+
+
+
+
+ "manual"
+ Time source
+ The source used to check current time
+
+
+ 6
+ Sunrise time
+ When the day starts
+
+
+ 20
+ Sunset time
+ When the day ends
+
diff --git a/src/utils.js b/src/utils.js
index 61559c3..eb66e5d 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -23,6 +23,6 @@ const config = Me.imports.config;
var log_debug = function(message) {
if ( config.debug ) {
- log(`[DEBUG] ${config.EXT_NAME}: ${message}`);
+ log(`[DEBUG] ${Me.metadata.name}: ${message}`);
}
}