From dc9c0240fbc5010ce61902261fbc5ad0e4a9d487 Mon Sep 17 00:00:00 2001 From: Romain Vigier Date: Sat, 1 Aug 2026 23:54:26 +0200 Subject: [PATCH] Extension: Allow using a manual location --- ....extensions.nightthemeswitcher.gschema.xml | 13 ++ src/data/ui/TweaksPage.ui | 55 ++++++- src/modules/Timer.js | 141 ++++++++++-------- src/preferences/TweaksPage.js | 35 +++-- src/prefs.js | 5 +- 5 files changed, 172 insertions(+), 77 deletions(-) diff --git a/src/data/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml b/src/data/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml index e0ab318..76070f7 100644 --- a/src/data/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml +++ b/src/data/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml @@ -63,6 +63,19 @@ SPDX-License-Identifier: GPL-3.0-or-later 0 + + + false + + + + 0 + + + + 0 + + "default" diff --git a/src/data/ui/TweaksPage.ui b/src/data/ui/TweaksPage.ui index ab47969..b115d90 100644 --- a/src/data/ui/TweaksPage.ui +++ b/src/data/ui/TweaksPage.ui @@ -8,6 +8,53 @@ SPDX-License-Identifier: GPL-3.0-or-later tweaks Tweaks nightthemeswitcher-tweaks-symbolic + + + + + Manual location + Use a manual location instead of system the location service. + + + center + + + + + Latitude + + manual_location_switch + + 2 + + + -90 + 90 + 0.01 + + + + + + + Longitude + + manual_location_switch + + 2 + + + -180 + 180 + 0.01 + + + + + + + + @@ -22,7 +69,9 @@ SPDX-License-Identifier: GPL-3.0-or-later Sunrise - + + manual_schedule_switch + 12 @@ -35,7 +84,9 @@ SPDX-License-Identifier: GPL-3.0-or-later Sunset - + + manual_schedule_switch + 12 diff --git a/src/modules/Timer.js b/src/modules/Timer.js index 3e20475..7fdef46 100644 --- a/src/modules/Timer.js +++ b/src/modules/Timer.js @@ -27,10 +27,11 @@ import { Time } from "../enums/Time.js"; * forced the manual schedule in the preferences. */ export class Timer extends GObject.Object { - #settings; - #colorSchemeSettings; - #interfaceSettings; #locationSettings; + #timeSettings; + #colorSchemeSettings; + #systemInterfaceSettings; + #systemLocationSettings; /** @type {Time | undefined} */ #time; @@ -57,10 +58,11 @@ export class Timer extends GObject.Object { constructor() { super(); - this.#settings = NTS.getSettings(`${NTS.metadata["settings-schema"]}.time`); + this.#locationSettings = NTS.getSettings(`${NTS.metadata["settings-schema"]}.location`); + this.#timeSettings = NTS.getSettings(`${NTS.metadata["settings-schema"]}.time`); this.#colorSchemeSettings = NTS.getSettings(`${NTS.metadata["settings-schema"]}.color-scheme`); - this.#interfaceSettings = new Gio.Settings({ schema: "org.gnome.desktop.interface" }); - this.#locationSettings = new Gio.Settings({ schema: "org.gnome.system.location" }); + this.#systemInterfaceSettings = new Gio.Settings({ schema: "org.gnome.desktop.interface" }); + this.#systemLocationSettings = new Gio.Settings({ schema: "org.gnome.system.location" }); } enable() { @@ -68,10 +70,13 @@ export class Timer extends GObject.Object { this.#cancellable = new Gio.Cancellable(); this.#connectSettings(); this.#trackTime(); - if (this.#settings.get_boolean("manual-schedule")) { + if (this.#timeSettings.get_boolean("manual-schedule")) { debug.message("Using the manual schedule."); + } else if (this.#locationSettings.get_boolean("manual-location")) { + debug.message("Using the manual location."); + this.#trackSuntimes(); } else { - debug.message("Using location."); + debug.message("Using system location."); this.#trackLocation(); this.#trackSuntimes(); } @@ -116,7 +121,7 @@ export class Timer extends GObject.Object { debug.message(manual ? `Time manually set to ${time}.` : `Time changed to ${time}.`); const isMonitorFullscreen = layoutManager.monitors.some((monitor) => monitor.inFullscreen); - if (this.#settings.get_boolean("fullscreen-transition") || !isMonitorFullscreen) { + if (this.#timeSettings.get_boolean("fullscreen-transition") || !isMonitorFullscreen) { try { layoutManager.screenTransition.run(); } catch {} @@ -127,30 +132,38 @@ export class Timer extends GObject.Object { #connectSettings() { debug.message("Connecting Timer to settings..."); + this.#settingsConnections.push({ + settings: this.#systemLocationSettings, + id: this.#systemLocationSettings.connect("changed::enabled", this.#onSystemLocationStateChanged.bind(this)), + }); this.#settingsConnections.push({ settings: this.#locationSettings, - id: this.#locationSettings.connect("changed::enabled", this.#onLocationStateChanged.bind(this)), + id: this.#locationSettings.connect("changed::manual-location", this.#onManualLocationStateChanged.bind(this)), }); this.#settingsConnections.push({ - settings: this.#settings, - id: this.#settings.connect("changed::location", this.#onLocationSettingsChanged.bind(this)), + settings: this.#locationSettings, + id: this.#locationSettings.connect("changed::latitude", this.#onLocationChanged.bind(this)), }); this.#settingsConnections.push({ - settings: this.#settings, - id: this.#settings.connect("changed::manual-schedule", this.#onManualScheduleStateChanged.bind(this)), + settings: this.#locationSettings, + id: this.#locationSettings.connect("changed::longitude", this.#onLocationChanged.bind(this)), }); this.#settingsConnections.push({ - settings: this.#settings, - id: this.#settings.connect( + settings: this.#timeSettings, + id: this.#timeSettings.connect("changed::manual-schedule", this.#onManualScheduleStateChanged.bind(this)), + }); + this.#settingsConnections.push({ + settings: this.#timeSettings, + id: this.#timeSettings.connect( "changed::nightthemeswitcher-ondemand-keybinding", this.#onOndemandKeybindingChanged.bind(this), ), }); // Only listen to the offset setting when not using a manual schedule - if (!this.#settings.get_boolean("manual-schedule")) { + if (!this.#timeSettings.get_boolean("manual-schedule")) { this.#settingsConnections.push({ - settings: this.#settings, - id: this.#settings.connect("changed::offset", this.#onOffsetChanged.bind(this)), + settings: this.#timeSettings, + id: this.#timeSettings.connect("changed::offset", this.#onOffsetChanged.bind(this)), }); } } @@ -214,12 +227,12 @@ export class Timer extends GObject.Object { } #addKeybinding() { - this.#previousKeybinding = this.#settings.get_strv("nightthemeswitcher-ondemand-keybinding")[0]; - if (!this.#settings.get_strv("nightthemeswitcher-ondemand-keybinding")[0]) return; + this.#previousKeybinding = this.#timeSettings.get_strv("nightthemeswitcher-ondemand-keybinding")[0]; + if (!this.#timeSettings.get_strv("nightthemeswitcher-ondemand-keybinding")[0]) return; debug.message("Adding keybinding..."); wm.addKeybinding( "nightthemeswitcher-ondemand-keybinding", - this.#settings, + this.#timeSettings, Meta.KeyBindingFlags.IGNORE_AUTOREPEAT, Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW, () => { @@ -242,12 +255,17 @@ export class Timer extends GObject.Object { return colorScheme === this.#colorSchemeSettings.get_string("night") ? Time.NIGHT : Time.DAY; } - #onLocationStateChanged() { + #onSystemLocationStateChanged() { this.disable(); this.enable(); } - #onLocationSettingsChanged() { + #onManualLocationStateChanged() { + this.disable(); + this.enable(); + } + + #onLocationChanged() { this.#updateSuntimes(); } @@ -268,56 +286,52 @@ export class Timer extends GObject.Object { #onGeoclueReady(_geoclue, result) { try { this.#geoclue = Geoclue.Simple.new_finish(result); - this.#geoclueLocationConnectionId = this.#geoclue.connect("notify::location", this.#onLocationChanged.bind(this)); + this.#geoclueLocationConnectionId = this.#geoclue.connect( + "notify::location", + this.#onGeoclueLocationChanged.bind(this), + ); debug.message("Connected to GeoClue."); - this.#onLocationChanged(); + this.#onGeoclueLocationChanged(); } catch (e) { - const [latitude, longitude] = this.#settings.get_value("location").deepUnpack(); - if (latitude >= -90 && latitude <= 90 && longitude >= -180 && longitude <= 180) { - console.error( - `[${NTS.metadata.name}] Unable to retrieve the location, using the last known location instead.\n${e}`, - ); - this.#updateSuntimes(); - } else { - console.error( - `[${NTS.metadata.name}] Unable to retrieve the location, using the manual schedule times instead.\n${e}`, - ); + console.error( + `[${NTS.metadata.name}] Unable to retrieve the location, using the manual schedule times instead.\n${e}`, + ); - const source = new MessageTray.Source({ - title: NTS.metadata.name, - icon: Gio.icon_new_for_string( - GLib.build_filenamev([NTS.metadata.path, "icons", "nightthemeswitcher-symbolic.svg"]), - ), - }); - messageTray.add(source); + const source = new MessageTray.Source({ + title: NTS.metadata.name, + icon: Gio.icon_new_for_string( + GLib.build_filenamev([NTS.metadata.path, "icons", "nightthemeswitcher-symbolic.svg"]), + ), + }); + messageTray.add(source); - const notification = new MessageTray.Notification({ - source, - title: _("Unknown Location"), - body: _("A manual schedule will be used to switch the dark mode."), - "icon-name": "location-services-disabled-symbolic", - }); - notification.addAction(_("Edit Manual Schedule"), () => NTS.openPreferences()); + const notification = new MessageTray.Notification({ + source, + title: _("Error while getting your location"), + body: _("A manual schedule will be used. You can edit it or manually set your location in the preferences."), + "icon-name": "location-services-disabled-symbolic", + }); + notification.addAction(_("Open Preferences"), () => NTS.openPreferences()); - notification.connect("activated", () => NTS.openPreferences()); + notification.connect("activated", () => NTS.openPreferences()); - source.addNotification(notification); + source.addNotification(notification); - this.#settings.set_boolean("manual-schedule", true); - } + this.#timeSettings.set_boolean("manual-schedule", true); } } - #onLocationChanged(_geoclue, _location) { + #onGeoclueLocationChanged(_geoclue, _location) { debug.message("Location has changed."); const { latitude, longitude } = this.#geoclue.get_location(); debug.message(`Current location: (${latitude};${longitude})`); - this.#settings.set_value("location", new GLib.Variant("(dd)", [latitude, longitude])); + this.#locationSettings.set_double("latitude", latitude); + this.#locationSettings.set_double("longitude", longitude); } #computeTime() { - const sunrise = this.#settings.get_double("sunrise"); - const sunset = this.#settings.get_double("sunset"); + const sunrise = this.#timeSettings.get_double("sunrise"); + const sunset = this.#timeSettings.get_double("sunset"); const datetime = GLib.DateTime.new_now_local(); const hour = datetime.get_hour() + datetime.get_minute() / 60 + datetime.get_second() / 3600; @@ -326,11 +340,12 @@ export class Timer extends GObject.Object { // Sunset happens on the day after else if (sunrise > sunset) return hour >= sunrise || hour < sunset ? Time.DAY : Time.NIGHT; // Sunset and Sunrise times are identical; preserve current theme - else return this.#time || this.#colorSchemeToTime(this.#interfaceSettings.get_string("color-scheme")); + else return this.#time || this.#colorSchemeToTime(this.#systemInterfaceSettings.get_string("color-scheme")); } #updateSuntimes() { - const [latitude, longitude] = this.#settings.get_value("location").deepUnpack(); + const latitude = this.#locationSettings.get_double("latitude"); + const longitude = this.#locationSettings.get_double("longitude"); if (latitude < -90 && latitude > 90 && longitude < -180 && longitude > 180) return; @@ -386,12 +401,12 @@ export class Timer extends GObject.Object { const modulo = (n, m) => ((n % m) + m) % m; - const offset = this.#settings.get_double("offset"); + const offset = this.#timeSettings.get_double("offset"); const sunrise = modulo(timeSunrise * 24 + offset, 24); const sunset = modulo(timeSunset * 24 - offset, 24); - this.#settings.set_double("sunrise", sunrise); - this.#settings.set_double("sunset", sunset); + this.#timeSettings.set_double("sunrise", sunrise); + this.#timeSettings.set_double("sunset", sunset); debug.message(`New sun times: (sunrise: ${sunrise}; sunset: ${sunset})`); } diff --git a/src/preferences/TweaksPage.js b/src/preferences/TweaksPage.js index 40a5c5e..dcc12ef 100644 --- a/src/preferences/TweaksPage.js +++ b/src/preferences/TweaksPage.js @@ -12,6 +12,9 @@ export class TweaksPage extends Adw.PreferencesPage { GTypeName: "TweaksPage", Template: "resource:///org/gnome/Shell/Extensions/nightthemeswitcher/preferences/ui/TweaksPage.ui", InternalChildren: [ + "manual_location_switch", + "latitude_spin", + "longitude_spin", "manual_schedule_switch", "keyboard_shortcut_button", "schedule_sunrise_time_chooser", @@ -24,24 +27,34 @@ export class TweaksPage extends Adw.PreferencesPage { ); } - constructor({ settings, ...params } = {}) { + constructor({ locationSettings, timeSettings, ...params } = {}) { super(params); - settings.bind("manual-schedule", this._manual_schedule_switch, "active", Gio.SettingsBindFlags.DEFAULT); + locationSettings.bind("manual-location", this._manual_location_switch, "active", Gio.SettingsBindFlags.DEFAULT); + locationSettings.bind("latitude", this._latitude_spin, "value", Gio.SettingsBindFlags.DEFAULT); + locationSettings.bind("longitude", this._longitude_spin, "value", Gio.SettingsBindFlags.DEFAULT); - settings.bind("sunrise", this._schedule_sunrise_time_chooser, "time", Gio.SettingsBindFlags.DEFAULT); - settings.bind("sunset", this._schedule_sunset_time_chooser, "time", Gio.SettingsBindFlags.DEFAULT); + // We have to manually force this for AdwSpinRow after binding the value property + this._latitude_spin.sensitive = this._manual_location_switch.active; + this._longitude_spin.sensitive = this._manual_location_switch.active; - settings.bind("offset", this._schedule_offset_spin_button, "value", Gio.SettingsBindFlags.DEFAULT); + timeSettings.bind("manual-schedule", this._manual_schedule_switch, "active", Gio.SettingsBindFlags.DEFAULT); + timeSettings.bind("sunrise", this._schedule_sunrise_time_chooser, "time", Gio.SettingsBindFlags.DEFAULT); + timeSettings.bind("sunset", this._schedule_sunset_time_chooser, "time", Gio.SettingsBindFlags.DEFAULT); + timeSettings.bind("offset", this._schedule_offset_spin_button, "value", Gio.SettingsBindFlags.DEFAULT); + timeSettings.bind( + "fullscreen-transition", + this._fullscreen_transition_switch, + "active", + Gio.SettingsBindFlags.DEFAULT, + ); - settings.bind("fullscreen-transition", this._fullscreen_transition_switch, "active", Gio.SettingsBindFlags.DEFAULT); - - settings.connect("changed::nightthemeswitcher-ondemand-keybinding", () => { - this._keyboard_shortcut_button.keybinding = settings.get_strv("nightthemeswitcher-ondemand-keybinding")[0]; + timeSettings.connect("changed::nightthemeswitcher-ondemand-keybinding", () => { + this._keyboard_shortcut_button.keybinding = timeSettings.get_strv("nightthemeswitcher-ondemand-keybinding")[0]; }); this._keyboard_shortcut_button.connect("notify::keybinding", () => { - settings.set_strv("nightthemeswitcher-ondemand-keybinding", [this._keyboard_shortcut_button.keybinding]); + timeSettings.set_strv("nightthemeswitcher-ondemand-keybinding", [this._keyboard_shortcut_button.keybinding]); }); - this._keyboard_shortcut_button.keybinding = settings.get_strv("nightthemeswitcher-ondemand-keybinding")[0]; + this._keyboard_shortcut_button.keybinding = timeSettings.get_strv("nightthemeswitcher-ondemand-keybinding")[0]; } } diff --git a/src/prefs.js b/src/prefs.js index dc5c3a9..4406bff 100644 --- a/src/prefs.js +++ b/src/prefs.js @@ -70,7 +70,10 @@ export default class NightThemeSwitcherPreferences extends ExtensionPreferences colorSchemeSettings: this.getSettings(`${this.metadata["settings-schema"]}.color-scheme`), }), new CommandsPage({ settings: this.getSettings(`${this.metadata["settings-schema"]}.commands`) }), - new TweaksPage({ settings: this.getSettings(`${this.metadata["settings-schema"]}.time`) }), + new TweaksPage({ + locationSettings: this.getSettings(`${this.metadata["settings-schema"]}.location`), + timeSettings: this.getSettings(`${this.metadata["settings-schema"]}.time`), + }), new ContributePage(), ].forEach((page) => window.add(page)); }