Add Location Services and manual schedule support

This commit is contained in:
Romain
2020-05-06 20:25:37 +00:00
parent cd423c2379
commit c62cf30c5a
14 changed files with 1071 additions and 239 deletions
+1 -1
View File
@@ -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
+3 -1
View File
@@ -2,7 +2,9 @@
![](./screenshot.png)
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/)!_
+3 -1
View File
@@ -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
+58 -3
View File
@@ -16,13 +16,68 @@ You should have received a copy of the GNU General Public License along with
this program. If not, see <http s ://www.gnu.org/licenses/>.
*/
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 = `
<node>
<interface name="org.freedesktop.GeoClue2.Manager">
<property name="InUse" type="b" access="read"/>
<property name="AvailableAccuracyLevel" type="u" access="read"/>
<method name="GetClient">
<arg name="client" type="o" direction="out"/>
</method>
<method name="CreateClient">
<arg name="client" type="o" direction="out"/>
</method>
<method name="DeleteClient">
<arg name="client" type="o" direction="in"/>
</method>
<method name="AddAgent">
<arg name="id" type="s" direction="in"/>
</method>
</interface>
</node>`;
var GEOCLUE_CLIENT_INTERFACE = `
<node>
<interface name="org.freedesktop.GeoClue2.Client">
<property name="Location" type="o" access="read"/>
<property name="DistanceThreshold" type="u" access="readwrite">
<annotation name="org.freedesktop.Accounts.DefaultValue" value="0"/>
</property>
<property name="TimeThreshold" type="u" access="readwrite">
<annotation name="org.freedesktop.Accounts.DefaultValue" value="0"/>
</property>
<property name="DesktopId" type="s" access="readwrite"/>
<property name="RequestedAccuracyLevel" type="u" access="readwrite"/>
<property name="Active" type="b" access="read"/>
<method name="Start"/>
<method name="Stop"/>
<signal name="LocationUpdated">
<arg name="old" type="o"/>
<arg name="new" type="o"/>
</signal>
</interface>
</node>`;
var GEOCLUE_LOCATION_INTERFACE = `
<node>
<interface name="org.freedesktop.GeoClue2.Location">
<property name="Latitude" type="d" access="read"/>
<property name="Longitude" type="d" access="read"/>
<property name="Accuracy" type="d" access="read"/>
<property name="Altitude" type="d" access="read"/>
<property name="Speed" type="d" access="read"/>
<property name="Heading" type="d" access="read"/>
<property name="Description" type="s" access="read"/>
<property name="Timestamp" type="(tt)" access="read"/>
</interface>
</node>`;
var debug = false;
+1 -1
View File
@@ -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",
-186
View File
@@ -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 <http s ://www.gnu.org/licenses/>.
*/
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;
}
}
}
+19 -21
View File
@@ -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);
}
}
+2 -2
View File
@@ -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();
}
+545
View File
@@ -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 <http s ://www.gnu.org/licenses/>.
*/
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;
}
}
}
+101 -15
View File
@@ -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 <romain@romainvigier.fr>, 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 <none>\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 <romain@romainvigier.fr>\n"
"Language-Team: French - France <romain@romainvigier.fr>\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 lextension. Veuillez lactiver "
"dans les réglages du système."
"Sils sont activés, lextension essayera dutiliser 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 dobtenir 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 dobtenir lemplacement 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 lextension. Veuillez "
#~ "lactiver 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."
+73 -7
View File
@@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""
+239
View File
@@ -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 <http s ://www.gnu.org/licenses/>.
*/
'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;
}
@@ -16,5 +16,30 @@
<summary>Night theme</summary>
<description>The theme to use during nighttime</description>
</key>
<key name="time-force-manual" type="b">
<default>false</default>
<summary>Use manual schedule</summary>
<description>Disable automatic times detection</description>
</key>
<key name="time-source" type="s">
<choices>
<choice value="nightlight"/>
<choice value="location"/>
<choice value="manual"/>
</choices>
<default>"manual"</default>
<summary>Time source</summary>
<description>The source used to check current time</description>
</key>
<key name="time-sunrise" type="d">
<default>6</default>
<summary>Sunrise time</summary>
<description>When the day starts</description>
</key>
<key name="time-sunset" type="d">
<default>20</default>
<summary>Sunset time</summary>
<description>When the day ends</description>
</key>
</schema>
</schemalist>
+1 -1
View File
@@ -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}`);
}
}