Extension: Allow using a manual location

This commit is contained in:
Romain Vigier
2026-08-02 00:25:06 +02:00
parent 0939a53411
commit dc9c0240fb
5 changed files with 172 additions and 77 deletions
@@ -63,6 +63,19 @@ SPDX-License-Identifier: GPL-3.0-or-later
<default>0</default> <default>0</default>
</key> </key>
</schema> </schema>
<schema id="org.gnome.shell.extensions.nightthemeswitcher.location" path="/org/gnome/shell/extensions/nightthemeswitcher/location/">
<key name="manual-location" type="b">
<default>false</default>
</key>
<key name="latitude" type="d">
<range min="-90" max="90"/>
<default>0</default>
</key>
<key name="longitude" type="d">
<range min="-180" max="180"/>
<default>0</default>
</key>
</schema>
<schema id="org.gnome.shell.extensions.nightthemeswitcher.color-scheme" path="/org/gnome/shell/extensions/nightthemeswitcher/color-scheme/"> <schema id="org.gnome.shell.extensions.nightthemeswitcher.color-scheme" path="/org/gnome/shell/extensions/nightthemeswitcher/color-scheme/">
<key name="day" enum="org.gnome.shell.extensions.nightthemeswitcher.color-scheme-enum"> <key name="day" enum="org.gnome.shell.extensions.nightthemeswitcher.color-scheme-enum">
<default>"default"</default> <default>"default"</default>
+53 -2
View File
@@ -8,6 +8,53 @@ SPDX-License-Identifier: GPL-3.0-or-later
<property name="name">tweaks</property> <property name="name">tweaks</property>
<property name="title" translatable="yes">Tweaks</property> <property name="title" translatable="yes">Tweaks</property>
<property name="icon-name">nightthemeswitcher-tweaks-symbolic</property> <property name="icon-name">nightthemeswitcher-tweaks-symbolic</property>
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="AdwExpanderRow">
<property name="title" translatable="yes">Manual location</property>
<property name="subtitle" translatable="yes">Use a manual location instead of system the location service.</property>
<child type="suffix">
<object class="GtkSwitch" id="manual_location_switch">
<property name="valign">center</property>
</object>
</child>
<child>
<object class="AdwSpinRow" id="latitude_spin">
<property name="title" translatable="yes">Latitude</property>
<binding name="sensitive">
<lookup name="active">manual_location_switch</lookup>
</binding>
<property name="digits">2</property>
<property name="adjustment">
<object class="GtkAdjustment">
<property name="lower">-90</property>
<property name="upper">90</property>
<property name="step-increment">0.01</property>
</object>
</property>
</object>
</child>
<child>
<object class="AdwSpinRow" id="longitude_spin">
<property name="title" translatable="yes">Longitude</property>
<binding name="sensitive">
<lookup name="active">manual_location_switch</lookup>
</binding>
<property name="digits">2</property>
<property name="adjustment">
<object class="GtkAdjustment">
<property name="lower">-180</property>
<property name="upper">180</property>
<property name="step-increment">0.01</property>
</object>
</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child> <child>
<object class="AdwPreferencesGroup"> <object class="AdwPreferencesGroup">
<child> <child>
@@ -22,7 +69,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
<child> <child>
<object class="AdwActionRow"> <object class="AdwActionRow">
<property name="title" translatable="yes">Sunrise</property> <property name="title" translatable="yes">Sunrise</property>
<property name="sensitive" bind-source="manual_schedule_switch" bind-property="active" bind-flags="sync-create"/> <binding name="sensitive">
<lookup name="active">manual_schedule_switch</lookup>
</binding>
<child> <child>
<object class="TimeChooser" id="schedule_sunrise_time_chooser"> <object class="TimeChooser" id="schedule_sunrise_time_chooser">
<property name="margin-top">12</property> <property name="margin-top">12</property>
@@ -35,7 +84,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
<child> <child>
<object class="AdwActionRow"> <object class="AdwActionRow">
<property name="title" translatable="yes">Sunset</property> <property name="title" translatable="yes">Sunset</property>
<property name="sensitive" bind-source="manual_schedule_switch" bind-property="active" bind-flags="sync-create"/> <binding name="sensitive">
<lookup name="active">manual_schedule_switch</lookup>
</binding>
<child> <child>
<object class="TimeChooser" id="schedule_sunset_time_chooser"> <object class="TimeChooser" id="schedule_sunset_time_chooser">
<property name="margin-top">12</property> <property name="margin-top">12</property>
+78 -63
View File
@@ -27,10 +27,11 @@ import { Time } from "../enums/Time.js";
* forced the manual schedule in the preferences. * forced the manual schedule in the preferences.
*/ */
export class Timer extends GObject.Object { export class Timer extends GObject.Object {
#settings;
#colorSchemeSettings;
#interfaceSettings;
#locationSettings; #locationSettings;
#timeSettings;
#colorSchemeSettings;
#systemInterfaceSettings;
#systemLocationSettings;
/** @type {Time | undefined} */ /** @type {Time | undefined} */
#time; #time;
@@ -57,10 +58,11 @@ export class Timer extends GObject.Object {
constructor() { constructor() {
super(); 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.#colorSchemeSettings = NTS.getSettings(`${NTS.metadata["settings-schema"]}.color-scheme`);
this.#interfaceSettings = new Gio.Settings({ schema: "org.gnome.desktop.interface" }); this.#systemInterfaceSettings = new Gio.Settings({ schema: "org.gnome.desktop.interface" });
this.#locationSettings = new Gio.Settings({ schema: "org.gnome.system.location" }); this.#systemLocationSettings = new Gio.Settings({ schema: "org.gnome.system.location" });
} }
enable() { enable() {
@@ -68,10 +70,13 @@ export class Timer extends GObject.Object {
this.#cancellable = new Gio.Cancellable(); this.#cancellable = new Gio.Cancellable();
this.#connectSettings(); this.#connectSettings();
this.#trackTime(); this.#trackTime();
if (this.#settings.get_boolean("manual-schedule")) { if (this.#timeSettings.get_boolean("manual-schedule")) {
debug.message("Using the 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 { } else {
debug.message("Using location."); debug.message("Using system location.");
this.#trackLocation(); this.#trackLocation();
this.#trackSuntimes(); this.#trackSuntimes();
} }
@@ -116,7 +121,7 @@ export class Timer extends GObject.Object {
debug.message(manual ? `Time manually set to ${time}.` : `Time changed to ${time}.`); debug.message(manual ? `Time manually set to ${time}.` : `Time changed to ${time}.`);
const isMonitorFullscreen = layoutManager.monitors.some((monitor) => monitor.inFullscreen); 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 { try {
layoutManager.screenTransition.run(); layoutManager.screenTransition.run();
} catch {} } catch {}
@@ -127,30 +132,38 @@ export class Timer extends GObject.Object {
#connectSettings() { #connectSettings() {
debug.message("Connecting Timer to settings..."); 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({ this.#settingsConnections.push({
settings: this.#locationSettings, 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({ this.#settingsConnections.push({
settings: this.#settings, settings: this.#locationSettings,
id: this.#settings.connect("changed::location", this.#onLocationSettingsChanged.bind(this)), id: this.#locationSettings.connect("changed::latitude", this.#onLocationChanged.bind(this)),
}); });
this.#settingsConnections.push({ this.#settingsConnections.push({
settings: this.#settings, settings: this.#locationSettings,
id: this.#settings.connect("changed::manual-schedule", this.#onManualScheduleStateChanged.bind(this)), id: this.#locationSettings.connect("changed::longitude", this.#onLocationChanged.bind(this)),
}); });
this.#settingsConnections.push({ this.#settingsConnections.push({
settings: this.#settings, settings: this.#timeSettings,
id: this.#settings.connect( 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", "changed::nightthemeswitcher-ondemand-keybinding",
this.#onOndemandKeybindingChanged.bind(this), this.#onOndemandKeybindingChanged.bind(this),
), ),
}); });
// Only listen to the offset setting when not using a manual schedule // 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({ this.#settingsConnections.push({
settings: this.#settings, settings: this.#timeSettings,
id: this.#settings.connect("changed::offset", this.#onOffsetChanged.bind(this)), id: this.#timeSettings.connect("changed::offset", this.#onOffsetChanged.bind(this)),
}); });
} }
} }
@@ -214,12 +227,12 @@ export class Timer extends GObject.Object {
} }
#addKeybinding() { #addKeybinding() {
this.#previousKeybinding = this.#settings.get_strv("nightthemeswitcher-ondemand-keybinding")[0]; this.#previousKeybinding = this.#timeSettings.get_strv("nightthemeswitcher-ondemand-keybinding")[0];
if (!this.#settings.get_strv("nightthemeswitcher-ondemand-keybinding")[0]) return; if (!this.#timeSettings.get_strv("nightthemeswitcher-ondemand-keybinding")[0]) return;
debug.message("Adding keybinding..."); debug.message("Adding keybinding...");
wm.addKeybinding( wm.addKeybinding(
"nightthemeswitcher-ondemand-keybinding", "nightthemeswitcher-ondemand-keybinding",
this.#settings, this.#timeSettings,
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT, Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW, 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; return colorScheme === this.#colorSchemeSettings.get_string("night") ? Time.NIGHT : Time.DAY;
} }
#onLocationStateChanged() { #onSystemLocationStateChanged() {
this.disable(); this.disable();
this.enable(); this.enable();
} }
#onLocationSettingsChanged() { #onManualLocationStateChanged() {
this.disable();
this.enable();
}
#onLocationChanged() {
this.#updateSuntimes(); this.#updateSuntimes();
} }
@@ -268,56 +286,52 @@ export class Timer extends GObject.Object {
#onGeoclueReady(_geoclue, result) { #onGeoclueReady(_geoclue, result) {
try { try {
this.#geoclue = Geoclue.Simple.new_finish(result); 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."); debug.message("Connected to GeoClue.");
this.#onLocationChanged(); this.#onGeoclueLocationChanged();
} catch (e) { } catch (e) {
const [latitude, longitude] = this.#settings.get_value("location").deepUnpack(); console.error(
if (latitude >= -90 && latitude <= 90 && longitude >= -180 && longitude <= 180) { `[${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 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}`,
);
const source = new MessageTray.Source({ const source = new MessageTray.Source({
title: NTS.metadata.name, title: NTS.metadata.name,
icon: Gio.icon_new_for_string( icon: Gio.icon_new_for_string(
GLib.build_filenamev([NTS.metadata.path, "icons", "nightthemeswitcher-symbolic.svg"]), GLib.build_filenamev([NTS.metadata.path, "icons", "nightthemeswitcher-symbolic.svg"]),
), ),
}); });
messageTray.add(source); messageTray.add(source);
const notification = new MessageTray.Notification({ const notification = new MessageTray.Notification({
source, source,
title: _("Unknown Location"), title: _("Error while getting your location"),
body: _("A manual schedule will be used to switch the dark mode."), 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", "icon-name": "location-services-disabled-symbolic",
}); });
notification.addAction(_("Edit Manual Schedule"), () => NTS.openPreferences()); 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."); debug.message("Location has changed.");
const { latitude, longitude } = this.#geoclue.get_location(); const { latitude, longitude } = this.#geoclue.get_location();
debug.message(`Current location: (${latitude};${longitude})`); 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() { #computeTime() {
const sunrise = this.#settings.get_double("sunrise"); const sunrise = this.#timeSettings.get_double("sunrise");
const sunset = this.#settings.get_double("sunset"); const sunset = this.#timeSettings.get_double("sunset");
const datetime = GLib.DateTime.new_now_local(); const datetime = GLib.DateTime.new_now_local();
const hour = datetime.get_hour() + datetime.get_minute() / 60 + datetime.get_second() / 3600; 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 // Sunset happens on the day after
else if (sunrise > sunset) return hour >= sunrise || hour < sunset ? Time.DAY : Time.NIGHT; else if (sunrise > sunset) return hour >= sunrise || hour < sunset ? Time.DAY : Time.NIGHT;
// Sunset and Sunrise times are identical; preserve current theme // 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() { #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; 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 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 sunrise = modulo(timeSunrise * 24 + offset, 24);
const sunset = modulo(timeSunset * 24 - offset, 24); const sunset = modulo(timeSunset * 24 - offset, 24);
this.#settings.set_double("sunrise", sunrise); this.#timeSettings.set_double("sunrise", sunrise);
this.#settings.set_double("sunset", sunset); this.#timeSettings.set_double("sunset", sunset);
debug.message(`New sun times: (sunrise: ${sunrise}; sunset: ${sunset})`); debug.message(`New sun times: (sunrise: ${sunrise}; sunset: ${sunset})`);
} }
+24 -11
View File
@@ -12,6 +12,9 @@ export class TweaksPage extends Adw.PreferencesPage {
GTypeName: "TweaksPage", GTypeName: "TweaksPage",
Template: "resource:///org/gnome/Shell/Extensions/nightthemeswitcher/preferences/ui/TweaksPage.ui", Template: "resource:///org/gnome/Shell/Extensions/nightthemeswitcher/preferences/ui/TweaksPage.ui",
InternalChildren: [ InternalChildren: [
"manual_location_switch",
"latitude_spin",
"longitude_spin",
"manual_schedule_switch", "manual_schedule_switch",
"keyboard_shortcut_button", "keyboard_shortcut_button",
"schedule_sunrise_time_chooser", "schedule_sunrise_time_chooser",
@@ -24,24 +27,34 @@ export class TweaksPage extends Adw.PreferencesPage {
); );
} }
constructor({ settings, ...params } = {}) { constructor({ locationSettings, timeSettings, ...params } = {}) {
super(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); // We have to manually force this for AdwSpinRow after binding the value property
settings.bind("sunset", this._schedule_sunset_time_chooser, "time", Gio.SettingsBindFlags.DEFAULT); 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); timeSettings.connect("changed::nightthemeswitcher-ondemand-keybinding", () => {
this._keyboard_shortcut_button.keybinding = timeSettings.get_strv("nightthemeswitcher-ondemand-keybinding")[0];
settings.connect("changed::nightthemeswitcher-ondemand-keybinding", () => {
this._keyboard_shortcut_button.keybinding = settings.get_strv("nightthemeswitcher-ondemand-keybinding")[0];
}); });
this._keyboard_shortcut_button.connect("notify::keybinding", () => { 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];
} }
} }
+4 -1
View File
@@ -70,7 +70,10 @@ export default class NightThemeSwitcherPreferences extends ExtensionPreferences
colorSchemeSettings: this.getSettings(`${this.metadata["settings-schema"]}.color-scheme`), colorSchemeSettings: this.getSettings(`${this.metadata["settings-schema"]}.color-scheme`),
}), }),
new CommandsPage({ settings: this.getSettings(`${this.metadata["settings-schema"]}.commands`) }), 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(), new ContributePage(),
].forEach((page) => window.add(page)); ].forEach((page) => window.add(page));
} }