Architecture rewrite and new features

This commit is contained in:
Romain
2020-05-25 08:41:35 +00:00
parent c64f0e8993
commit f21bc025b7
83 changed files with 4563 additions and 1686 deletions
+131
View File
@@ -0,0 +1,131 @@
/*
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 { GLib } = imports.gi;
const { extensionUtils } = imports.misc;
const Me = extensionUtils.getCurrentExtension();
const e = Me.imports.extension;
const { log_debug } = Me.imports.utils;
/**
* The Backgrounder is responsible for changing the desktop background
* according to the time.
*
* When the user changes its desktop background (for example via the system
* settings), it will use it as the current time background.
*/
var Backgrounder = class {
enable() {
log_debug('Enabling Backgrounder...');
this._watch_status();
if ( e.settingsManager.backgrounds_enabled ) {
this._change_background(e.timer.time);
this._connect_settings();
this._connect_timer();
}
log_debug('Backgrounder enabled.');
}
disable() {
log_debug('Disabling Backgrounder...');
this._disconnect_timer();
this._disconnect_settings();
this._unwatch_status();
log_debug('Backgrounder disabled.');
}
_watch_status() {
log_debug('Watching backgrounds status...');
this._backgrounds_status_changed_connect = e.settingsManager.connect('backgrounds-status-changed', this._on_backgrounds_status_changed.bind(this));
}
_unwatch_status() {
if ( this._backgrounds_status_changed_connect ) {
e.settingsManager.disconnect(this._backgrounds_status_changed_connect);
this._backgrounds_status_changed_connect = null;
}
log_debug('Stopped watching backgrounds status.');
}
_connect_settings() {
log_debug('Connecting Backgrounder to settings...');
this._background_time_changed_connect = e.settingsManager.connect('background-time-changed', this._on_background_time_changed.bind(this));
this._background_changed_connect = e.settingsManager.connect('background-changed', this._on_background_changed.bind(this));
}
_disconnect_settings() {
if ( this._background_time_changed_connect ) {
e.settingsManager.disconnect(this._background_time_changed_connect);
this._background_time_changed_connect = null;
}
if ( this._background_changed_connect ) {
e.settingsManager.disconnect(this._background_changed_connect);
this._background_changed_connect = null;
}
log_debug('Disconnected Backgrounder from settings.');
}
_connect_timer() {
log_debug('Connecting Backgrounder to Timer...');
this._time_changed_connect = e.timer.connect('time-changed', this._on_time_changed.bind(this));
}
_disconnect_timer() {
if ( this._time_changed_connect ) {
e.timer.disconnect(this._time_changed_connect);
this._time_changed_connect = null;
}
log_debug('Disconnecting Backgrounder from Timer.');
}
_on_backgrounds_status_changed(settings, enabled) {
this.disable();
this.enable();
}
_on_background_time_changed(settings, changed_background_time) {
if ( changed_background_time === e.timer.time ) {
this._change_background(changed_background_time);
}
}
_on_background_changed(settings, new_background) {
switch (e.timer.time) {
case 'day':
e.settingsManager.background_day = new_background;
break;
case 'night':
e.settingsManager.background_night = new_background;
}
}
_on_time_changed(timer, new_time) {
this._change_background(new_time);
}
_change_background(time) {
e.settingsManager.background = time === 'day' ? e.settingsManager.background_day : e.settingsManager.background_night;
}
}
+93
View File
@@ -0,0 +1,93 @@
/*
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 { GLib } = imports.gi;
const { extensionUtils } = imports.misc;
const Me = extensionUtils.getCurrentExtension();
const e = Me.imports.extension;
const { log_debug } = Me.imports.utils;
/**
* The Commander is responsible for spawning commands according to the time.
*/
var Commander = class {
enable() {
log_debug('Enabling Commander...');
this._watch_status();
if ( e.settingsManager.commands_enabled ) {
this._connect_timer();
}
log_debug('Commander enabled.');
}
disable() {
log_debug('Disabling Commander...');
this._disconnect_timer();
this._unwatch_status();
log_debug('Commander disabled.');
}
_watch_status() {
log_debug('Watching commands status...');
this._commands_status_changed_connect = e.settingsManager.connect('commands-status-changed', this._on_commands_status_changed.bind(this));
}
_unwatch_status() {
if ( this._commands_status_changed_connect ) {
e.settingsManager.disconnect(this._commands_status_changed_connect);
this._commands_status_changed_connect = null;
}
log_debug('Stopped watching commands status.');
}
_connect_timer() {
log_debug('Connecting Commander to Timer...');
this._time_changed_connect = e.timer.connect('time-changed', this._on_time_changed.bind(this));
}
_disconnect_timer() {
if ( this._time_changed_connect ) {
e.timer.disconnect(this._time_changed_connect);
this._time_changed_connect = null;
}
log_debug('Disconnecting Commander from Timer.');
}
_on_commands_status_changed(settings, enabled) {
this.disable();
this.enable();
}
_on_time_changed(timer, new_time) {
this._spawn_command(new_time);
}
_spawn_command(time) {
const command = time === 'day' ? e.settingsManager.command_sunrise : e.settingsManager.command_sunset;
GLib.spawn_async(null, ['sh', '-c', command], null, GLib.SpawnFlags.SEARCH_PATH, null);
log_debug(`Spawned ${time} command.`);
}
}
+171
View File
@@ -0,0 +1,171 @@
/*
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 { Gio, GLib, Gtk } = imports.gi;
const { extensionUtils } = imports.misc;
const { main } = imports.ui;
const Me = extensionUtils.getCurrentExtension();
const e = Me.imports.extension;
const { log_debug, log_error, get_installed_gtk_themes } = Me.imports.utils;
const { GtkVariants } = Me.imports.modules.GtkVariants;
const Gettext = imports.gettext.domain(Me.metadata.uuid);
const _ = Gettext.gettext;
/**
* The GTK Themer is responsible for changing the GTK theme according to the
* time.
*
* When the user changes its GTK theme (for example via GNOME Tweaks), it will
* try to automatically guess the day and night variants for this theme. It
* will warn the user if it is unable to guess.
*
* In manual mode, it will not attempt to update the variants. The user can
* change the GTK variants in the extension's preferences.
*/
var GtkThemer = class {
enable() {
log_debug('Enabling GTK Themer...');
try {
this._update_variants();
this._set_variant(e.timer.time);
this._connect_settings();
this._connect_timer();
}
catch(e) {
log_error(e);
}
log_debug('GTK Themer enabled.');
}
disable() {
log_debug('Disabling GTK Themer...');
this._disconnect_timer();
this._disconnect_settings();
this._reset_theme();
log_debug('GTK Themer disabled.');
}
_connect_settings() {
log_debug('Connecting GTK Themer to settings...');
this._gtk_variant_changed_connect = e.settingsManager.connect('gtk-variant-changed', this._on_gtk_variant_changed.bind(this));
this._gtk_theme_changed_connect = e.settingsManager.connect('gtk-theme-changed', this._on_gtk_theme_changed.bind(this));
}
_disconnect_settings() {
if ( this._gtk_variant_changed_connect ) {
e.settingsManager.disconnect(this._gtk_variant_changed_connect);
this._gtk_variant_changed_connect = null;
}
if ( this._gtk_theme_changed_connect ) {
e.settingsManager.disconnect(this._gtk_theme_changed_connect);
this._gtk_theme_changed_connect = null;
}
log_debug('Disconnected GTK Themer from settings.');
}
_connect_timer() {
log_debug('Connecting GTK Themer to Timer...');
this._time_changed_connect = e.timer.connect('time-changed', this._on_time_changed.bind(this));
}
_disconnect_timer() {
if ( this._time_changed_connect ) {
e.timer.disconnect(this._time_changed_connect);
this._time_changed_connect = null;
}
log_debug('Disconnected GTK Themer from Timer.');
}
_on_gtk_variant_changed(settings, changed_variant_time) {
if ( changed_variant_time === e.timer.time ) {
this._set_variant(changed_variant_time);
}
}
_on_gtk_theme_changed(settings, new_theme) {
try {
this._update_variants();
this._set_variant(e.timer.time);
}
catch(e) {
log_error(e);
}
}
_on_time_changed(timer, new_time) {
this._set_variant(new_time);
}
_are_variants_up_to_date() {
return ( e.settingsManager.gtk_theme === e.settingsManager.gtk_variant_day || e.settingsManager.gtk_theme === e.settingsManager.gtk_variant_night );
}
_set_variant(time) {
log_debug(`Setting the GTK ${time} variant...`);
switch (time) {
case 'day':
e.settingsManager.gtk_theme = e.settingsManager.gtk_variant_day;
break;
case 'night':
e.settingsManager.gtk_theme = e.settingsManager.gtk_variant_night;
break;
case 'original':
e.settingsManager.gtk_theme = e.settingsManager.gtk_variant_original;
break;
}
}
_update_variants() {
if ( e.settingsManager.manual_gtk_variants || this._are_variants_up_to_date() ) {
return;
}
log_debug('Updating GTK variants...');
const variants = GtkVariants.guess_from(e.settingsManager.gtk_theme);
const installed_themes = get_installed_gtk_themes();
if ( !installed_themes.has(variants.get('day')) || !installed_themes.has(variants.get('night')) ) {
e.settingsManager.gtk_variant_original = variants.get('original');
const message = _('Unable to automatically detect the day and night variants for the "%s" GTK theme. Please manually choose them in the extension\'s preferences.').format(variants.get('original'));
throw new Error(message);
}
e.settingsManager.gtk_variant_day = variants.get('day');
e.settingsManager.gtk_variant_night = variants.get('night');
e.settingsManager.gtk_variant_original = variants.get('original');
log_debug(`New GTK variants. { day: '${variants.get('day')}'; night: '${variants.get('night')}' }`);
}
_reset_theme() {
// We don't reset the theme when locking the session to prevent
// flicker on unlocking
if ( !main.screenShield.locked ) {
log_debug('Resetting to the user\'s original GTK theme...');
this._set_variant('original');
}
}
}
@@ -17,32 +17,30 @@ 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/>.
*/
/*
The magic of guessing theme variants happens here.
If the theme doesn't fit a particular case, we'll do the following:
- Remove any signs of a dark variant to the theme name to get the day
variant
- Remove any signs of a light variant to the day variant and add '-dark' to
get the night variant
For themes that don't work with the general rule, a particular case must be
written. Day and night variants should be guessed with the most generic light
and dark variants the theme offer, except if the user explicitly chose a
specific variant.
Light variants, from the most to the least generic:
- ''
- '-light'
- '-darker'
Dark variants, from the most the least generic:
- '-dark'
- '-darkest'
*/
var Variants = class {
/**
* The magic of guessing theme variants happens here.
*
* If the theme doesn't fit a particular case, we'll do the following:
* - Remove any signs of a dark variant to the theme name to get the day
* variant
* - Remove any signs of a light variant to the day variant and add '-dark' to
* get the night variant
*
* For themes that don't work with the general rule, a particular case must be
* written. Day and night variants should be guessed with the most generic light
* and dark variants the theme offer, except if the user explicitly chose a
* specific variant.
*
* Light variants, from the most to the least generic:
* - ''
* - '-light'
* - '-darker'
*
* Dark variants, from the most the least generic:
* - '-dark'
* - '-darkest'
*/
var GtkVariants = class {
static guess_from(name) {
const variants = new Map();
+427
View File
@@ -0,0 +1,427 @@
/*
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 { Gio } = imports.gi;
const { extensionUtils } = imports.misc;
const Signals = imports.signals;
const Me = extensionUtils.getCurrentExtension();
const { log_debug, get_userthemes_extension, get_userthemes_settings } = Me.imports.utils;
const shell_minor_version = parseInt(imports.misc.config.PACKAGE_VERSION.split('.')[1]);
if ( shell_minor_version <= 30 ) {
extensionUtils.getSettings = Me.imports.convenience.getSettings;
}
/**
* The Settings Manager centralizes all the different settings the extension
* needs. It handles getting and settings values as well as signaling any
* changes.
*/
var SettingsManager = class {
constructor() {
log_debug('Initializing settings...');
this._extensionsSettings = extensionUtils.getSettings();
this._colorSettings = new Gio.Settings({ schema: 'org.gnome.settings-daemon.plugins.color' });
this._locationSettings = new Gio.Settings({ schema: 'org.gnome.system.location' });
this._interfaceSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' });
this._backgroundSettings = new Gio.Settings({ schema: 'org.gnome.desktop.background' });
this._userthemesSettings = get_userthemes_settings();
log_debug('Settings initialized.');
}
enable() {
log_debug('Connecting settings signals...');
this._gtk_variant_day_changed_connect = this._extensionsSettings.connect('changed::gtk-variant-day', this._on_gtk_variant_day_changed.bind(this));
this._gtk_variant_night_changed_connect = this._extensionsSettings.connect('changed::gtk-variant-night', this._on_gtk_variant_night_changed.bind(this));
this._gtk_variant_original_changed_connect = this._extensionsSettings.connect('changed::gtk-variant-original', this._on_gtk_variant_original_changed.bind(this));
this._shell_variant_day_changed_connect = this._extensionsSettings.connect('changed::shell-variant-day', this._on_shell_variant_day_changed.bind(this));
this._shell_variant_night_changed_connect = this._extensionsSettings.connect('changed::shell-variant-night', this._on_shell_variant_night_changed.bind(this));
this._shell_variant_original_changed_connect = this._extensionsSettings.connect('changed::shell-variant-original', this._on_shell_variant_original_changed.bind(this));
this._time_source_changed_connect = this._extensionsSettings.connect('changed::time-source', this._on_time_source_changed.bind(this));
this._manual_time_source_changed_connect = this._extensionsSettings.connect('changed::manual-time-source', this._on_manual_time_source_changed.bind(this));
this._commands_status_connect = this._extensionsSettings.connect('changed::commands-enabled', this._on_commands_status_changed.bind(this));
this._backgrounds_status_connect = this._extensionsSettings.connect('changed::backgrounds-enabled', this._on_backgrounds_status_changed.bind(this));
this._background_day_changed_connect = this._extensionsSettings.connect('changed::background-day', this._on_background_day_changed.bind(this));
this._background_night_changed_connect = this._extensionsSettings.connect('changed::background-night', this._on_background_night_changed.bind(this));
this._nightlight_status_connect = this._colorSettings.connect('changed::night-light-enabled', this._on_nightlight_status_changed.bind(this));
this._location_status_connect = this._locationSettings.connect('changed::enabled', this._on_location_status_changed.bind(this));
this._gtk_theme_changed_connect = this._interfaceSettings.connect('changed::gtk-theme', this._on_gtk_theme_changed.bind(this));
this._background_changed_connect = this._backgroundSettings.connect('changed::picture-uri', this._on_background_changed.bind(this));
if ( this._userthemesSettings ) {
this._shell_theme_changed_connect = this._userthemesSettings.connect('changed::name', this._on_shell_theme_changed.bind(this));
}
log_debug('Settings signals connected.');
}
disable() {
log_debug('Disconnecting settings signals...');
this._extensionsSettings.disconnect(this._gtk_variant_day_changed_connect);
this._extensionsSettings.disconnect(this._gtk_variant_night_changed_connect);
this._extensionsSettings.disconnect(this._gtk_variant_original_changed_connect);
this._extensionsSettings.disconnect(this._shell_variant_day_changed_connect);
this._extensionsSettings.disconnect(this._shell_variant_night_changed_connect);
this._extensionsSettings.disconnect(this._shell_variant_original_changed_connect);
this._extensionsSettings.disconnect(this._time_source_changed_connect);
this._extensionsSettings.disconnect(this._manual_time_source_changed_connect);
this._extensionsSettings.disconnect(this._commands_status_connect);
this._extensionsSettings.disconnect(this._backgrounds_status_connect);
this._extensionsSettings.disconnect(this._background_day_changed_connect);
this._extensionsSettings.disconnect(this._background_night_changed_connect);
this._colorSettings.disconnect(this._nightlight_status_connect);
this._locationSettings.disconnect(this._location_status_connect);
this._interfaceSettings.disconnect(this._gtk_theme_changed_connect);
this._backgroundSettings.disconnect(this._background_changed_connect);
if ( this._userthemesSettings ) {
this._userthemesSettings.disconnect(this._shell_theme_changed_connect);
}
log_debug('Settings signals disconnected.');
}
/**
* SETTERS AND GETTERS
*/
/* GTK variants settings */
get gtk_variant_day() {
return this._extensionsSettings.get_string('gtk-variant-day');
}
set gtk_variant_day(value) {
if ( value !== this.gtk_variant_day ) {
this._extensionsSettings.set_string('gtk-variant-day', value);
log_debug(`The GTK day variant has been set to '${value}'.`);
}
}
get gtk_variant_night() {
return this._extensionsSettings.get_string('gtk-variant-night');
}
set gtk_variant_night(value) {
if ( value !== this.gtk_variant_night ) {
this._extensionsSettings.set_string('gtk-variant-night', value);
log_debug(`The GTK night variant has been set to '${value}'.`);
}
}
get gtk_variant_original() {
return this._extensionsSettings.get_string('gtk-variant-original');
}
set gtk_variant_original(value) {
if ( value !== this.gtk_variant_original ) {
this._extensionsSettings.set_string('gtk-variant-original', value);
log_debug(`The GTK original variant has been set to '${value}'.`);
}
}
get manual_gtk_variants() {
return this._extensionsSettings.get_boolean('manual-gtk-variants');
}
/* Shell variants settings */
get shell_variant_day() {
return this._extensionsSettings.get_string('shell-variant-day');
}
set shell_variant_day(value) {
if ( value !== this.shell_variant_day ) {
this._extensionsSettings.set_string('shell-variant-day', value);
log_debug(`The shell day variant has been set to '${value}'.`);
}
}
get shell_variant_night() {
return this._extensionsSettings.get_string('shell-variant-night');
}
set shell_variant_night(value) {
if ( value !== this.shell_variant_night ) {
this._extensionsSettings.set_string('shell-variant-night', value);
log_debug(`The shell night variant has been set to '${value}'.`);
}
}
get shell_variant_original() {
return this._extensionsSettings.get_string('shell-variant-original');
}
set shell_variant_original(value) {
if ( value !== this.shell_variant_original ) {
this._extensionsSettings.set_string('shell-variant-original', value);
log_debug(`The shell original variant has been set to '${value}'.`);
}
}
get manual_shell_variants() {
return this._extensionsSettings.get_boolean('manual-shell-variants');
}
/* Time source settings */
get time_source() {
return this._extensionsSettings.get_string('time-source');
}
get manual_time_source() {
return this._extensionsSettings.get_boolean('manual-time-source');
}
set time_source(value) {
if ( value !== this.time_source ) {
this._extensionsSettings.set_string('time-source', value);
log_debug(`The time source has been set to ${value}.`);
}
}
get schedule_sunrise() {
return this._extensionsSettings.get_double('schedule-sunrise');
}
get schedule_sunset() {
return this._extensionsSettings.get_double('schedule-sunset');
}
/* Commands settings */
get commands_enabled() {
return this._extensionsSettings.get_boolean('commands-enabled');
}
get command_sunrise() {
return this._extensionsSettings.get_string('command-sunrise');
}
get command_sunset() {
return this._extensionsSettings.get_string('command-sunset');
}
/* Background settings */
get backgrounds_enabled() {
return this._extensionsSettings.get_boolean('backgrounds-enabled');
}
get background_day() {
return this._extensionsSettings.get_string('background-day') || this.background;
}
set background_day(value) {
this._extensionsSettings.set_string('background-day', value);
}
get background_night() {
return this._extensionsSettings.get_string('background-night') || this.background;
}
set background_night(value) {
this._extensionsSettings.set_string('background-night', value);
}
/* Night Light settings */
get nightlight_enabled() {
return this._colorSettings.get_boolean('night-light-enabled');
}
/* Location settings */
get location_enabled() {
return this._locationSettings.get_boolean('enabled');
}
/* GTK theme settings */
get gtk_theme() {
return this._interfaceSettings.get_string('gtk-theme');
}
set gtk_theme(value) {
if ( value !== this.gtk_theme ) {
this._interfaceSettings.set_string('gtk-theme', value);
log_debug(`GTK theme has been set to '${value}.'`);
}
}
/* Shell theme settings */
get shell_theme() {
if ( this._userthemesSettings ) {
return this._userthemesSettings.get_string('name');
}
}
set shell_theme(value) {
if ( this._userthemesSettings && value !== this.shell_theme ) {
this._userthemesSettings.set_string('name', value);
}
}
get use_userthemes() {
const extension = get_userthemes_extension();
return (extension && extension.state === 1);
}
/* Background settings */
get background() {
return this._backgroundSettings.get_string('picture-uri');
}
set background(value) {
if ( value !== this.background ) {
this._backgroundSettings.set_string('picture-uri', value);
}
}
/**
* SIGNALS
*/
/* GTK variants */
_on_gtk_variant_day_changed(settings, changed_key) {
log_debug(`GTK day variant has changed to '${this.gtk_variant_day}'.`);
this.emit('gtk-variant-changed', 'day');
}
_on_gtk_variant_night_changed(settings, changed_key) {
log_debug(`GTK night variant has changed to '${this.gtk_variant_night}'.`);
this.emit('gtk-variant-changed', 'night');
}
_on_gtk_variant_original_changed(settings, changed_key) {
log_debug(`GTK original variant has changed to '${this.gtk_variant_original}'.`);
this.emit('gtk-variant-changed', 'original');
}
/* Shell variants */
_on_shell_variant_day_changed(settings, changed_key) {
log_debug(`Shell day variant has changed to '${this.shell_variant_day}'.`);
this.emit('shell-variant-changed', 'day');
}
_on_shell_variant_night_changed(settings, changed_key) {
log_debug(`Shell night variant has changed to '${this.shell_variant_night}'.`);
this.emit('shell-variant-changed', 'night');
}
_on_shell_variant_original_changed(settings, changed_key) {
log_debug(`Shell original variant has changed to '${this.shell_variant_original}'.`);
this.emit('shell-variant-changed', 'original');
}
/* Time source */
_on_time_source_changed(settings, changed_key) {
log_debug(`Time source has changed to ${this.time_source}.`);
this.emit('time-source-changed', this.time_source);
}
_on_manual_time_source_changed(settings, changed_key) {
log_debug('Manual time source has been ' + (this.manual_time_source ? 'ena' : 'disa') + 'bled.');
this.emit('manual-time-source-changed', this.manual_time_source);
}
/* Commands */
_on_commands_status_changed(settings, changed_key) {
log_debug('Commands have been ' + (this.commands_enabled ? 'ena' : 'disa') + 'bled.');
this.emit('commands-status-changed', this.commands_enabled);
}
/* Backgrounds */
_on_backgrounds_status_changed(settings, changed_key) {
log_debug('Backgrounds have been ' + (this.backgrounds_enabled ? 'ena' : 'disa') + 'bled.');
this.emit('backgrounds-status-changed', this.backgrounds_enabled);
}
_on_background_day_changed(settings, changed_key) {
log_debug(`Day background has changed to '${this.background_day}'.`);
this.emit('background-time-changed', 'day');
}
_on_background_night_changed(settings, changed_key) {
log_debug(`Night background has changed to '${this.background_night}'.`);
this.emit('background-time-changed', 'night');
}
/* Night Light */
_on_nightlight_status_changed(settings, changed_key) {
log_debug('Night Light has been ' + (this.nightlight_enabled ? 'ena' : 'disa') + 'bled.');
this.emit('nightlight-status-changed', this.nightlight_enabled);
}
/* Location */
_on_location_status_changed(settings, changed_key) {
log_debug('Location has been ' + (this.location_enabled ? 'ena' : 'disa') + 'bled.');
this.emit('location-status-changed', this.location_enabled);
}
/* GTK theme */
_on_gtk_theme_changed(settings, changed_key) {
log_debug(`GTK theme has changed to '${this.gtk_theme}'.`);
this.emit('gtk-theme-changed', this.gtk_theme);
}
/* Background */
_on_background_changed(settings, changed_key) {
log_debug(`Background has changed to '${this.background}'.`);
this.emit('background-changed', this.background);
}
/* Shell theme */
_on_shell_theme_changed(settings, changed_key) {
log_debug(`Shell theme has changed to '${this.shell_theme}'.`);
this.emit('shell-theme-changed', this.shell_theme);
}
}
Signals.addSignalMethods(SettingsManager.prototype);
+179
View File
@@ -0,0 +1,179 @@
/*
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 } = imports.misc;
const Signals = imports.signals;
const { main } = imports.ui;
const Me = extensionUtils.getCurrentExtension();
const e = Me.imports.extension;
const { log_debug, log_error, get_theme_dirs_paths, get_installed_shell_themes, get_shell_theme_stylesheet, apply_shell_stylesheet } = Me.imports.utils;
const { ShellVariants } = Me.imports.modules.ShellVariants;
const Gettext = imports.gettext.domain(Me.metadata.uuid);
const _ = Gettext.gettext;
/**
* The Shell Themer is responsible for changing the GTK theme according to the
* time. It will use the User Themes extension to do so if it is enabled.
*
* When the user changes its shell theme (for example via GNOME Tweaks), it will
* try to automatically guess the day and night variants for this theme. It
* will warn the user if it is unable to guess.
*
* In manual mode, it will not attempt to update the variants. The user can
* change the shell variants in the extension's preferences.
*/
var ShellThemer = class {
enable() {
log_debug('Enabling Shell Themer...');
try {
this._update_variants();
this._set_variant(e.timer.time);
this._connect_settings();
this._connect_timer();
}
catch(e) {
log_error(e);
}
log_debug('Shell Themer enabled.');
}
disable() {
log_debug('Disabling Shell Themer...');
this._disconnect_timer();
this._disconnect_settings();
this._reset_theme();
log_debug('Shell Themer disabled.');
}
_connect_settings() {
log_debug('Connecting Shell Themer to settings...');
this._shell_variant_changed_connect = e.settingsManager.connect('shell-variant-changed', this._on_shell_variant_changed.bind(this));
this._shell_theme_changed_connect = e.settingsManager.connect('shell-theme-changed', this._on_shell_theme_changed.bind(this));
}
_disconnect_settings() {
if ( this._shell_variant_changed_connect ) {
e.settingsManager.disconnect(this._shell_variant_changed_connect);
this._shell_variant_changed_connect = null;
}
if ( this._shell_theme_changed_connect ) {
e.settingsManager.disconnect(this._shell_theme_changed_connect);
this._shell_theme_changed_connect = null;
}
log_debug('Disconnected Shell Themer from settings.');
}
_connect_timer() {
log_debug('Connecting Shell Themer to Timer...');
this._time_changed_connect = e.timer.connect('time-changed', this._on_time_changed.bind(this));
}
_disconnect_timer() {
if ( this._time_changed_connect ) {
e.timer.disconnect(this._time_changed_connect);
this._time_changed_connect = null;
}
log_debug('Disconnected Shell Themer from Timer.');
}
_on_shell_variant_changed(settings, changed_variant_time) {
if ( changed_variant_time === e.timer.time ) {
this._set_variant(e.timer.time);
}
}
_on_shell_theme_changed(settings, new_theme) {
try {
this._update_variants();
this._set_variant(e.timer.time);
}
catch(e) {
log_error(e);
}
}
_on_time_changed(timer, new_time) {
this._set_variant(new_time);
}
_are_variants_up_to_date() {
return ( e.settingsManager.shell_theme === e.settingsManager.shell_variant_day || e.settingsManager.shell_theme === e.settingsManager.shell_variant_night );
}
_set_variant(time) {
log_debug(`Setting the shell ${time} variant...`);
let shell_theme;
switch (time) {
case 'day':
shell_theme = e.settingsManager.shell_variant_day;
break;
case 'night':
shell_theme = e.settingsManager.shell_variant_night;
break;
case 'original':
shell_theme = e.settingsManager.shell_variant_original;
break;
}
if ( e.settingsManager.use_userthemes ) {
e.settingsManager.shell_theme = shell_theme;
}
else {
const stylesheet = get_shell_theme_stylesheet(shell_theme);
apply_shell_stylesheet(stylesheet);
}
}
_update_variants() {
if ( !e.settingsManager.use_userthemes || e.settingsManager.manual_shell_variants || this._are_variants_up_to_date() ) {
return;
}
log_debug('Updating Shell variants...');
const variants = ShellVariants.guess_from(e.settingsManager.shell_theme);
const installed_themes = get_installed_shell_themes();
if ( !installed_themes.has(variants.get('day')) || !installed_themes.has(variants.get('night')) ) {
e.settingsManager.shell_variant_original = variants.get('original');
const message = _('Unable to automatically detect the day and night variants for the "%s" GNOME Shell theme. Please manually choose them in the extension\'s preferences.').format(variants.get('original'));
throw new Error(message);
}
e.settingsManager.shell_variant_day = variants.get('day');
e.settingsManager.shell_variant_night = variants.get('night');
e.settingsManager.shell_variant_original = variants.get('original');
log_debug(`New Shell variants. { day: '${variants.get('day')}'; night: '${variants.get('night')}' }`);
}
_reset_theme() {
// We don't reset the theme when locking the session to prevent
// flicker on unlocking
if ( !main.screenShield.locked ) {
log_debug('Resetting to the user\'s original Shell theme...');
this._set_variant('original');
}
}
}
Signals.addSignalMethods(ShellThemer.prototype);
+103
View File
@@ -0,0 +1,103 @@
/*
Night Theme Switcher Gnome Shell extension
Copyright (C) 2019, 2020 Romain Vigier
Copyright (C) 2020 Matti Hyttinen
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/>.
*/
/**
* The magic of guessing theme variants happens here.
*
* If the theme doesn't fit a particular case, we'll do the following:
* - Remove any signs of a dark variant to the theme name to get the day
* variant
* - Remove any signs of a light variant to the day variant and add '-dark' to
* get the night variant
*
* For themes that don't work with the general rule, a particular case must be
* written. Day and night variants should be guessed with the most generic light
* and dark variants the theme offer, except if the user explicitly chose a
* specific variant.
*
* Light variants, from the most to the least generic:
* - ''
* - '-light'
* - '-darker'
*
* Dark variants, from the most the least generic:
* - '-dark'
* - '-darkest'
*/
var ShellVariants = class {
static guess_from(name) {
const variants = new Map();
variants.set('original', name);
if ( name.includes('Adapta') ) {
variants.set('day', name.replace('-Nokto', ''));
variants.set('night', variants.get('day').replace('Adapta', 'Adapta-Nokto'));
}
else if ( name.includes('Arc') ) {
variants.set('day', name.replace('-Dark', ''));
variants.set('night', variants.get('day').replace('Arc', 'Arc-Dark'));
}
else if ( name.match(/^(Canta|ChromeOS|Materia|Orchis).*-compact/) ) {
variants.set('day', name.replace('-dark', ''));
variants.set('night', variants.get('day').replace(/(-light)?-compact/, '-dark-compact'));
}
else if ( name.includes('Flat-Remix') ) {
const color = ( name.split('-')[2] && !['Dark', 'Darkest', 'fullPanel'].includes(name.split('-')[2]) ) ? '-' + name.split('-')[2] : '';
const dark_variant = name.includes('Darkest') ? '-Darkest' : '-Dark';
const size = name.includes('fullPanel')? '-fullPanel' : '';
variants.set('day', name.replace(/-Dark(est)?/, ''));
variants.set('night', `Flat-Remix${color}${dark_variant}${size}`);
}
else if ( name.match(/^(Layan|Matcha)/) ) {
const basename = name.split('-')[0];
variants.set('day', name.replace('-dark', ''));
variants.set('night', variants.get('day').replace(new RegExp(`${basename}(-light)?`), `${basename}-dark`));
}
else if ( name.includes('Mojave') ) {
variants.set('day', name.replace('-dark', '-light'));
variants.set('night', variants.get('day').replace('-light', '-dark'));
}
else if ( name.includes('Plata') ) {
variants.set('day', name.replace('-Noir', ''));
variants.set('night', variants.get('day').replace(/Plata(-Lumine)?/, 'Plata-Noir'));
}
else if ( name.includes('Simply_Circles') ) {
variants.set('day', name.replace('_Dark', '_Light'));
variants.set('night', name.replace('_Light', '_Dark'));
}
else if ( name.includes('Teja') ) {
const dark_variant = '_' + (name.replace('_Light').split('_')[1] || 'Dark');
variants.set('day', name.replace(/(_Dark(est)?|_Black)/, ''));
variants.set('night', variants.get('day').replace('_Light', '') + dark_variant);
}
else if ( name.includes('vimix') ) {
variants.set('day', name.replace('-dark', ''));
variants.set('night', variants.get('day').replace(/vimix(-light)?/, 'vimix-dark'));
}
else {
variants.set('day', name.replace(/-dark(?!er)(est)?/, ''));
variants.set('night', variants.get('day').replace(/(-light|-darker)/, '') + (name.includes('-darkest') ? '-darkest' : '-dark'));
}
return variants;
}
}
-115
View File
@@ -1,115 +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 } = imports.misc;
const { main } = imports.ui;
const Me = extensionUtils.getCurrentExtension();
const config = Me.imports.config;
const { log_debug, log_error } = Me.imports.utils;
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 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
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 Timer to disable
themselves.
*/
var Switcher = class {
constructor() {
log_debug('Initializing extension...');
extensionUtils.initTranslations(Me.metadata.uuid);
log_debug('Extension initialized.');
}
enable() {
log_debug('Enabling extension...');
try {
this.theme = new Themer();
this.theme.enable();
this.theme.subscribe(this._on_theme_changed.bind(this));
this.time = new Timer();
this.time.enable();
this.time.subscribe(this._on_time_changed.bind(this));
this.theme.set_variant(this.time.current);
log_debug('Extension enabled.');
}
catch(e) {
log_error(e);
}
}
disable() {
log_debug('Disabling extension...');
try {
this.theme.disable();
this.time.disable();
}
catch(e) {} // Since we're disabling, we'll just ignore errors.
finally {
this.theme = null;
this.time = null;
}
log_debug('Extension disabled.');
}
_on_theme_changed() {
if ( !this.theme || !this.time ) {
return;
}
try {
this.theme.update_variants();
this.theme.set_variant(this.time.current);
}
catch(e) {
log_error(e);
}
}
_on_time_changed() {
if ( !this.theme || !this.time ) {
return;
}
try {
this.theme.set_variant(this.time.current);
}
catch(e) {
log_error(e);
}
}
}
-219
View File
@@ -1,219 +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 } = imports.misc;
const { Gio } = imports.gi;
const { main } = imports.ui;
const Me = extensionUtils.getCurrentExtension();
const config = Me.imports.config;
const utils = Me.imports.utils;
const { log_debug, log_error } = Me.imports.utils;
const { Variants } = Me.imports.modules.Variants;
const Gettext = imports.gettext.domain(Me.metadata.uuid);
const _ = Gettext.gettext;
/*
The Themer communicates with the system to get the current theme or set a new
one, and get the day and night variants of a theme. It listens to theme changes
and reports them.
*/
var Themer = class {
constructor() {
log_debug('Initializing Themer...');
this.settings = extensionUtils.getSettings();
this.theme_gsettings = new Gio.Settings({ schema: config.THEME_GSETTINGS_SCHEMA });
log_debug('Themer initialized.');
}
enable() {
try {
log_debug('Enabling Themer...');
this.ready = false;
this._listen_to_force_manual_status();
if ( this._is_force_manual_enabled() ) {
log_debug('Using manually set variants.');
this._listen_to_prefs_theme_change();
}
else {
log_debug('Automatically detecting variants.');
this._listen_to_theme_changes();
this.update_variants();
}
this.ready = true;
this.emit();
log_debug('Themer enabled.');
}
catch(e) {
log_error(e);
}
}
disable() {
log_debug('Disabling Themer...');
this._stop_listening_to_force_manual_status();
if ( this._is_force_manual_enabled() ) {
this._stop_listening_to_prefs_theme_change();
}
else {
this._stop_listening_to_theme_changes();
// GNOME Shell disables extensions when locking the screen. We'll
// only reset the theme if the user disables the extension to
// prevent flickering when unlocking.
if ( !main.screenShield.locked ) {
this.reset_theme();
}
}
log_debug('Themer disabled.');
}
get current_theme() {
return this.theme_gsettings.get_string(config.THEME_GSETTINGS_PROPERTY);
}
set current_theme(theme) {
if ( theme !== this.current_theme ) {
this.theme_gsettings.set_string(config.THEME_GSETTINGS_PROPERTY, theme);
log_debug(`Theme has been set to "${theme}".`);
}
}
subscribe(callback) {
this.theme_change_callback = callback;
}
emit() {
if ( this.theme_change_callback ) {
this.theme_change_callback();
}
}
set_variant(variant) {
if ( this.ready ) {
log_debug(`Setting theme to the "${variant}" variant...`);
this.current_theme = this.settings.get_string(`theme-${variant}`);
}
}
reset_theme() {
this.set_variant('original');
log_debug('Theme has been reset to the user\'s original variant.')
}
update_variants() {
if ( !this._are_variants_up_to_date() && !this._is_force_manual_enabled() ) {
this._update_variants();
}
}
_update_variants() {
if ( this.current_theme ) {
const variants = Variants.guess_from(this.current_theme);
if ( utils.is_theme_installed(variants.get('day')) && utils.is_theme_installed(variants.get('night')) ) {
variants.forEach( (theme, variant) => this.settings.set_string(`theme-${variant}`, theme) );
log_debug(`Variants updated: {day: "${variants.get('day')}", night: "${variants.get('night')}"}`);
}
else {
const message = _('The extension cannot detect the day and night variants for the "%s" theme. Please choose another theme or manually set variants in the extension preferences.').format(variants.get('original'));
throw new Error(message);
}
}
}
_are_variants_up_to_date() {
return ( this.current_theme === this.settings.get_string('theme-day') || this.current_theme === this.settings.get_string('theme-night') );
}
_is_force_manual_enabled() {
return this.settings.get_boolean('theme-force-manual');
}
_listen_to_force_manual_status() {
if ( !this.force_manual_status_connect ) {
this.force_manual_status_connect = this.settings.connect(
'changed::theme-force-manual',
this._on_force_manual_status_changed.bind(this)
);
log_debug('Listening to manual variants 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 variants status changes.');
}
}
_on_force_manual_status_changed() {
log_debug('Manual variants status has changed.');
this.enable();
}
_listen_to_theme_changes() {
if ( !this.theme_change_connect ) {
this.theme_change_connect = this.theme_gsettings.connect(
'changed::' + config.THEME_GSETTINGS_PROPERTY,
this._on_theme_changed.bind(this)
);
log_debug('Listening for theme changes...');
}
}
_stop_listening_to_theme_changes() {
if ( this.theme_gsettings && this.theme_change_connect ){
this.theme_gsettings.disconnect(this.theme_change_connect);
this.theme_change_connect = null;
log_debug('Stopped listening for theme changes.');
}
}
_on_theme_changed() {
log_debug(`Theme has changed to "${this.current_theme}".`);
this.ready ? this.emit() : this.enable();
}
_listen_to_prefs_theme_change() {
if ( !this.prefs_theme_change_connect ) {
this.prefs_theme_change_connect = new Map();
['day', 'night'].forEach(time => {
this.prefs_theme_change_connect.set(time, this.settings.connect(
`changed::theme-${time}`,
this._on_theme_changed.bind(this)
));
log_debug(`Listening for ${time} theme preference changes...`);
});
}
}
_stop_listening_to_prefs_theme_change() {
if ( this.settings && this.prefs_theme_change_connect ) {
this.prefs_theme_change_connect.forEach(connect => this.settings.disconnect(connect));
this.prefs_theme_change_connect = null;
log_debug('Stopped listening to theme preferences changes.');
}
}
}
+133 -486
View File
@@ -16,527 +16,174 @@ 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 { extensionUtils } = imports.misc;
const Signals = imports.signals;
const Me = extensionUtils.getCurrentExtension();
const config = Me.imports.config;
const { log_debug, log_error } = Me.imports.utils;
const Gettext = imports.gettext.domain(Me.metadata.uuid);
const _ = Gettext.gettext;
const e = Me.imports.extension;
const { log_debug } = Me.imports.utils;
const { TimerNightlight } = Me.imports.modules.TimerNightlight;
const { TimerLocation } = Me.imports.modules.TimerLocation;
const { TimerSchedule } = Me.imports.modules.TimerSchedule;
/*
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.
*/
/**
* 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.
*
* It will try to use one of this three different time sources, in this order of
* preference:
* - Night Light
* - Location Services
* - Manual schedule
*
* The user can manually force a specific time source and set the manual
* schedule in the extensions's preferences.
*/
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('Timer initialized.');
this._source = null;
this._previous_time = null;
}
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) {
log_error(e);
}
this._connect_settings();
this._create_source();
this._connect_source();
this._enable_source();
log_debug('Timer enabled.');
}
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;
}
this._disconnect_source();
this._disable_source();
this._disconnect_settings();
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';
}
get time() {
return this._source.time;
}
_connect_settings() {
log_debug('Connecting Timer to settings...');
this._nightlight_status_changed_connect = e.settingsManager.connect('nightlight-status-changed', this._on_source_changed.bind(this));
this._location_status_changed_connect = e.settingsManager.connect('location-status-changed', this._on_source_changed.bind(this));
this._manual_time_source_changed_connect = e.settingsManager.connect('manual-time-source-changed', this._on_source_changed.bind(this));
this._time_source_changed_connect = e.settingsManager.connect('time-source-changed', this._on_time_source_changed.bind(this));
}
_disconnect_settings() {
if ( this._nightlight_status_changed_connect ) {
e.settingsManager.disconnect(this._nightlight_status_changed_connect);
this._nightlight_status_changed_connect = null;
}
else {
return 'day';
if ( this._location_status_changed_connect ) {
e.settingsManager.disconnect(this._location_status_changed_connect);
this._location_status_changed_connect = null;
}
if ( this._manual_time_source_changed_connect ) {
e.settingsManager.disconnect(this._manual_time_source_changed_connect);
this._manual_time_source_changed_connect = null;
}
if ( this._time_source_changed_connect ) {
e.settingsManager.disconnect(this._time_source_changed_connect);
this._time_source_changed_connect = null;
}
log_debug('Disconnected Timer from settings.');
}
_create_source() {
switch ( this._get_source() ) {
case 'nightlight':
this._source = new TimerNightlight();
break;
case 'location':
this._source = new TimerLocation();
break;
case 'schedule':
this._source = new TimerSchedule();
break;
}
this._source.enable();
}
_enable_source() {
this._source.enable();
}
_disable_source() {
if ( this._source ) {
this._source.disable();
this._source = null;
}
}
subscribe(callback) {
this.time_change_callback = callback;
_connect_source() {
log_debug('Connecting to time source...');
this._time_changed_connect = this._source.connect('time-changed', this._on_time_changed.bind(this));
}
emit() {
if ( this.time_change_callback ) {
this.time_change_callback();
_disconnect_source() {
if ( this._time_changed_connect ) {
this._source.disconnect(this._time_changed_connect);
this._time_changed_connect = null;
}
log_debug('Disconnected from time source.');
}
_on_source_changed() {
this.disable();
this.enable();
}
_on_time_source_changed(settings, new_source) {
if ( e.settingsManager.manual_time_source ) {
this._on_source_changed();
}
}
_on_time_changed(source, new_time) {
if ( new_time !== this._previous_time) {
log_debug(`Time has changed to ${new_time}.`);
this.emit('time-changed', new_time);
this._previous_time = new_time;
}
}
_get_source() {
if ( this._is_force_manual_enabled() ) {
return 'manual';
log_debug('Getting time source...');
if ( e.settingsManager.manual_time_source ) {
log_debug(`Time source is forced to ${e.settingsManager.time_source}.`);
return e.settingsManager.time_source;
}
else if ( this._is_nightlight_enabled() ) {
return 'nightlight';
let source;
if ( e.settingsManager.nightlight_enabled ) {
source = 'nightlight';
}
else if ( this._is_location_enabled() ) {
return 'location';
else if ( e.settingsManager.location_enabled ) {
source = '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);
source = 'schedule';
}
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;
}
log_debug(`Time source is ${source}.`);
e.settingsManager.time_source = source;
return source;
}
}
Signals.addSignalMethods(Timer.prototype);
+322
View File
@@ -0,0 +1,322 @@
/*
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 { Gio, GLib } = imports.gi;
const { extensionUtils } = imports.misc;
const Signals = imports.signals;
const Me = extensionUtils.getCurrentExtension();
const e = Me.imports.extension;
const { log_debug } = Me.imports.utils;
// GeoClue2 interfaces from https://gitlab.freedesktop.org/geoclue/geoclue/
const 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>`;
const 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>`;
const 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>`;
/**
* The Location Timer uses Location Services to get the current sunrise and
* sunset times.
*
* It gets the current user's location with the GeoClue2 DBus proxy and
* calculate the times.
*
* It will recalculate every hour and when the user's location changed to stay
* up to date.
*
* Every second, it will check if the time has changed and signal if that's the
* case.
*/
var TimerLocation = class {
constructor() {
this._previously_daytime = null;
// Before we have the location suntimes, we'll use the manual schedule
// times
this._suntimes = new Map([
['sunrise', e.settingsManager.schedule_sunrise],
['sunset', e.settingsManager.schedule_sunrise]
]);
}
enable() {
log_debug('Enabling Location Timer...');
this._connect_to_geoclue_dbus_proxy();
this._listen_to_location_updates();
this._connect_to_geoclue_location_dbus_proxy();
this._update_location();
this._update_suntimes();
this._watch_for_time_change();
this._regularly_update_suntimes();
log_debug('Location Timer enabled.');
}
disable() {
log_debug('Disabling Location Timer...');
this._stop_regularly_updating_suntimes();
this._stop_watching_for_time_change();
this._stop_listening_to_location_updates();
this._disconnect_from_geoclue_location_dbus_proxy();
this._disconnect_from_geoclue_dbus_proxy();
log_debug('Location Timer disabled.');
}
get time() {
return this._is_daytime() ? 'day' : 'night';
}
_connect_to_geoclue_dbus_proxy() {
log_debug('Connecting to GeoClue manager DBus proxy...');
const GeoClueManagerProxy = Gio.DBusProxy.makeProxyWrapper(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.');
log_debug('Getting a GeoClue client...');
this._geoclue_client = this._geoclue_manager_dbus_proxy.GetClientSync()[0];
log_debug(`Got a GeoClue client at ${this._geoclue_client}`);
log_debug('Connecting to GeoClue client DBus proxy...');
const GeoClueClientProxy = Gio.DBusProxy.makeProxyWrapper(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.');
}
_disconnect_from_geoclue_dbus_proxy() {
log_debug('Disconnecting from GeoClue DBus proxy...')
this._geoclue_manager_dbus_proxy.DeleteClientSync(this._geoclue_client);
this._geoclue_client = null;
this._geoclue_client_dbus_proxy = null;
this._geoclue_manager_dbus_proxy = null;
log_debug('Disconnected from GeoClue DBus proxy.')
}
_listen_to_location_updates() {
log_debug('Listening to location updates...');
this._location_updates_connect = this._geoclue_client_dbus_proxy.connectSignal('LocationUpdated', this._on_location_updated.bind(this));
this._geoclue_client_dbus_proxy.StartSync();
}
_stop_listening_to_location_updates() {
this._geoclue_client_dbus_proxy.disconnectSignal(this._location_updates_connect);
this._location_updates_connect = null;
this._geoclue_client_dbus_proxy.StopSync();
log_debug('Stopped listening to location updates.');
}
_on_location_updated(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_suntimes();
}
_connect_to_geoclue_location_dbus_proxy(path) {
if ( !path ) {
path = this._geoclue_client_dbus_proxy.Location;
}
if ( path !== '/' ) {
log_debug('Connecting to GeoClue location DBus proxy...');
const GeoClueLocationProxy = Gio.DBusProxy.makeProxyWrapper(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.');
}
}
_disconnect_from_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 ) {
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})`);
}
}
_update_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._suntimes.set('sunrise', sunrise);
this._suntimes.set('sunset', sunset);
log_debug(`New sun times: (sunrise: ${sunrise}; sunset: ${sunset})`);
}
_regularly_update_suntimes() {
log_debug('Regularly updating sun times...');
this._regularly_update_suntimes_timer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 3600, () => {
this._update_suntimes();
return true; // Repeat the loop
});
}
_stop_regularly_updating_suntimes() {
GLib.Source.remove(this._regularly_update_suntimes_timer);
this._regularly_update_suntimes_timer = null;
log_debug('Stopped regularly updating sun times.');
}
_is_daytime() {
const time = GLib.DateTime.new_now_local();
const hour = time.get_hour() + time.get_minute() / 60 + time.get_second() / 3600;
return ( hour >= this._suntimes.get('sunrise') && hour <= this._suntimes.get('sunset') );
}
_watch_for_time_change() {
log_debug('Watching for time change...');
this._time_change_timer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => {
if ( !Me.imports.extension.enabled ) {
// The extension doesn't exist anymore, quit the loop
return false;
}
if ( this._previously_daytime !== this._is_daytime() ) {
this._previously_daytime = this._is_daytime();
this.emit('time-changed', this.time);
}
return true; // Repeat the loop
});
}
_stop_watching_for_time_change() {
GLib.Source.remove(this._time_change_timer);
log_debug('Stopped watching for time change.');
}
}
Signals.addSignalMethods(TimerLocation.prototype);
+114
View File
@@ -0,0 +1,114 @@
/*
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 { Gio, GLib } = imports.gi;
const { extensionUtils, fileUtils } = imports.misc;
const Signals = imports.signals;
const Me = extensionUtils.getCurrentExtension();
const { log_debug } = Me.imports.utils;
const shell_minor_version = parseInt(imports.misc.config.PACKAGE_VERSION.split('.')[1]);
if ( shell_minor_version <= 30 ) {
fileUtils.loadInterfaceXML = Me.imports.convenience.loadInterfaceXML;
}
const COLOR_INTERFACE = `
<node>
<interface name="org.gnome.SettingsDaemon.Color">
<property name="NightLightActive" type="b" access="read"/>
</interface>
</node>`;
/**
* The Night Light Timer uses Night Light as a time source.
*
* It connects to the Color SettingsDaemon DBus proxy to listen to the
* 'NightLightActive' property and will signal any change.
*/
var TimerNightlight = class {
enable() {
log_debug('Enabling Night Light Timer...');
this._connect_to_color_dbus_proxy();
this._listen_to_nightlight_state();
this.emit('time-changed', this.time);
log_debug('Night Light Timer enabled.');
}
disable() {
log_debug('Disabling Night Light Timer...');
this._stop_listening_to_nightlight_state();
this._disconnect_from_color_dbus_proxy();
log_debug('Night Light Timer disabled.');
}
get time() {
return this._is_nightlight_active() ? 'night' : 'day';
}
_connect_to_color_dbus_proxy() {
log_debug('Connecting to Color DBus proxy...');
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.');
}
_disconnect_from_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_state() {
log_debug('Listening to Night Light state...');
this._nightlight_state_connect = this._color_dbus_proxy.connect(
'g-properties-changed',
this._on_nightlight_state_changed.bind(this)
);
}
_stop_listening_to_nightlight_state() {
this._color_dbus_proxy.disconnect(this._nightlight_state_connect);
log_debug('Stopped listening to Night Light state.');
}
_on_nightlight_state_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('time-changed', this.time);
}
}
_is_nightlight_active() {
return this._color_dbus_proxy.NightLightActive;
}
}
Signals.addSignalMethods(TimerNightlight.prototype);
+88
View File
@@ -0,0 +1,88 @@
/*
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 { GLib } = imports.gi;
const { extensionUtils } = imports.misc;
const Signals = imports.signals;
const Me = extensionUtils.getCurrentExtension();
const e = Me.imports.extension;
const { log_debug } = Me.imports.utils;
/**
* The Schedule Timer 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 TimerSchedule = class {
constructor() {
this._previously_daytime = null;
}
enable() {
log_debug('Enabling Schedule Timer...');
this._watch_for_time_change();
log_debug('Schedule Timer enabled.');
}
disable() {
log_debug('Disabling Schedule Timer...');
this._stop_watching_for_time_change();
log_debug('Schedule Timer disabled.');
}
get time() {
return this._is_daytime() ? 'day' : 'night';
}
_is_daytime() {
const time = GLib.DateTime.new_now_local();
const hour = time.get_hour() + time.get_minute() / 60 + time.get_second() / 3600;
return ( hour >= e.settingsManager.schedule_sunrise && hour <= e.settingsManager.schedule_sunset );
}
_watch_for_time_change() {
log_debug('Watching for time change...');
this._time_change_timer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => {
if ( !Me.imports.extension.enabled ) {
// The extension doesn't exist anymore, quit the loop
return false;
}
if ( this._previously_daytime !== this._is_daytime() ) {
this._previously_daytime = this._is_daytime();
this.emit('time-changed', this.time);
}
return true; // Repeat the loop
});
}
_stop_watching_for_time_change() {
GLib.Source.remove(this._time_change_timer);
log_debug('Stopped watching for time change.');
}
}
Signals.addSignalMethods(TimerSchedule.prototype);