diff --git a/package-lock.json b/package-lock.json index 49889aa..7b81294 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@girs/gsk-4.0": "^4.1.0", "@girs/gtk-4.0": "^4.1.0", "@girs/meta-18": "^4.1.0", + "@girs/pango-1.0": "^4.1.0", "@girs/shell-18": "^4.1.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-jsdoc": "^63.2.0", diff --git a/package.json b/package.json index 515cd93..5dc56ba 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@girs/gsk-4.0": "^4.1.0", "@girs/gtk-4.0": "^4.1.0", "@girs/meta-18": "^4.1.0", + "@girs/pango-1.0": "^4.1.0", "@girs/shell-18": "^4.1.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-jsdoc": "^63.2.0", diff --git a/src/data/icons/add-symbolic.svg b/src/data/icons/add-symbolic.svg new file mode 100644 index 0000000..fee4e37 --- /dev/null +++ b/src/data/icons/add-symbolic.svg @@ -0,0 +1,26 @@ + + + + plus + + + + + + + + Jakub Steiner + + + + + list-add add plus create + + + + + + diff --git a/src/data/icons/trash-symbolic.svg b/src/data/icons/trash-symbolic.svg new file mode 100644 index 0000000..1f038cd --- /dev/null +++ b/src/data/icons/trash-symbolic.svg @@ -0,0 +1,29 @@ + + + + user-trash + + + + + + + + + + + trash bin deleted wastebasket user folder + + + + + Jakub Steiner + + + + + + diff --git a/src/data/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml b/src/data/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml index e0414a6..3e11e71 100644 --- a/src/data/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml +++ b/src/data/org.gnome.shell.extensions.nightthemeswitcher.gschema.xml @@ -28,9 +28,15 @@ SPDX-License-Identifier: GPL-3.0-or-later "" + + [] + "" + + [] + diff --git a/src/data/preferences.gresource.xml b/src/data/preferences.gresource.xml index a158eb7..4c5813f 100644 --- a/src/data/preferences.gresource.xml +++ b/src/data/preferences.gresource.xml @@ -7,8 +7,10 @@ SPDX-License-Identifier: GPL-3.0-or-later assets/preview-window-dark.svg assets/preview-window-light.svg + icons/add-symbolic.svg icons/code-symbolic.svg icons/translate-symbolic.svg + icons/trash-symbolic.svg icons/nightthemeswitcher-symbolic.svg icons/appearance-symbolic.svg icons/commands-symbolic.svg @@ -19,6 +21,7 @@ SPDX-License-Identifier: GPL-3.0-or-later ui/AppearanceChooser.ui ui/AppearancePage.ui ui/BackgroundButton.ui + ui/CommandsEditor.ui ui/CommandsPage.ui ui/ContributePage.ui ui/ShortcutButton.ui diff --git a/src/data/ui/CommandsEditor.ui b/src/data/ui/CommandsEditor.ui new file mode 100644 index 0000000..7637a6f --- /dev/null +++ b/src/data/ui/CommandsEditor.ui @@ -0,0 +1,23 @@ + + + + + diff --git a/src/data/ui/CommandsPage.ui b/src/data/ui/CommandsPage.ui index 9469013..d441258 100644 --- a/src/data/ui/CommandsPage.ui +++ b/src/data/ui/CommandsPage.ui @@ -10,27 +10,22 @@ SPDX-License-Identifier: GPL-3.0-or-later nightthemeswitcher-commands-symbolic - Run commands - - - center - - - - Sunrise - terminal - 0 -1 font-desc "Mono" - - - - - Sunset - terminal - 0 -1 font-desc "Mono" + + Run commands + + + Sunrise commands + + + + + Sunset commands + + diff --git a/src/meson.build b/src/meson.build index 3a17d6d..1e54d90 100644 --- a/src/meson.build +++ b/src/meson.build @@ -40,6 +40,7 @@ preferences = [ 'preferences/AppearancePreview.js', 'preferences/BackgroundButton.js', 'preferences/ColorChooser.js', + 'preferences/CommandsEditor.js', 'preferences/CommandsPage.js', 'preferences/ContributePage.js', 'preferences/ShortcutButton.js', diff --git a/src/metadata.json.in b/src/metadata.json.in index 01a8126..8166c36 100644 --- a/src/metadata.json.in +++ b/src/metadata.json.in @@ -6,7 +6,7 @@ "settings-schema": "@DNS@", "url": "https://nightthemeswitcher.romainvigier.fr", "session-modes": ["unlock-dialog", "user"], - "shell-version": ["50"], + "shell-version": ["51"], "version": @VERSION@, "build-type": "@BUILD_TYPE@" } diff --git a/src/migrations.js b/src/migrations.js index 6f096c7..8836337 100644 --- a/src/migrations.js +++ b/src/migrations.js @@ -9,6 +9,7 @@ import Gio from "gi://Gio"; */ export function migrate(extension) { initializeAccentColorSettings(extension); + migrateCommandsSettings(extension); } /** @@ -25,3 +26,24 @@ function initializeAccentColorSettings(extension) { if (accentColorSettings.get_string("night") === "unset") accentColorSettings.set_string("night", interfaceSettings.get_string("accent-color")); } + +/** + * Move the commands from single strings to arrays of strings. + * @param {ExtensionBase} extension The extension base class. + */ +function migrateCommandsSettings(extension) { + const commandsSettings = extension.getSettings(`${extension.metadata["settings-schema"]}.commands`); + + const oldSunriseCommand = commandsSettings.get_string("sunrise"); + const oldSunsetCommand = commandsSettings.get_string("sunset"); + + if (oldSunriseCommand) { + commandsSettings.set_strv("sunrise-commands", [oldSunriseCommand]); + commandsSettings.set_string("sunrise", ""); + } + + if (oldSunsetCommand) { + commandsSettings.set_strv("sunset-commands", [oldSunsetCommand]); + commandsSettings.set_string("sunset", ""); + } +} diff --git a/src/modules/CommandsSwitcher.js b/src/modules/CommandsSwitcher.js index 10924f3..3ba49f3 100644 --- a/src/modules/CommandsSwitcher.js +++ b/src/modules/CommandsSwitcher.js @@ -33,9 +33,11 @@ export class CommandsSwitcher extends Switcher { #onTimeChanged(time) { if (time === Time.UNKNOWN) return; - const command = this.#settings.get_string(time === Time.DAY ? "sunrise" : "sunset"); - if (!command) return; - GLib.spawn_async(null, ["sh", "-c", command], null, GLib.SpawnFlags.SEARCH_PATH, null); - debug.message(`Spawned ${time} command.`); + const commands = this.#settings.get_strv(time === Time.DAY ? "sunrise-commands" : "sunset-commands"); + if (commands.length === 0) return; + commands.forEach((command) => { + GLib.spawn_async(null, ["sh", "-c", command], null, GLib.SpawnFlags.SEARCH_PATH, null); + }); + debug.message(`Spawned ${time} commands.`); } } diff --git a/src/preferences/CommandsEditor.js b/src/preferences/CommandsEditor.js new file mode 100644 index 0000000..9e49ffe --- /dev/null +++ b/src/preferences/CommandsEditor.js @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: Night Theme Switcher Contributors +// SPDX-License-Identifier: GPL-3.0-or-later + +import Adw from "gi://Adw"; +import GObject from "gi://GObject"; +import Gtk from "gi://Gtk"; +import Pango from "gi://Pango"; + +import { gettext as _ } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js"; + +export class CommandsEditor extends Adw.PreferencesGroup { + #commands = new Gtk.StringList(); + + static { + GObject.registerClass( + { + GTypeName: "CommandsEditor", + Template: "resource:///org/gnome/Shell/Extensions/nightthemeswitcher/preferences/ui/CommandsEditor.ui", + Properties: { + commands: GObject.ParamSpec.jsobject( + "commands", + "Commands", + "Array of commands", + GObject.ParamFlags.READWRITE, + ), + }, + }, + this, + ); + } + + constructor({ ...params } = {}) { + super(params); + this.bind_model(this.#commands, (item) => this.#createCommandEntryRow(item)); + } + + get commands() { + const commands = []; + for (let i = 0; i < this.#commands.get_n_items(); i++) { + const command = this.#commands.get_string(i); + if (command) commands.push(command); + } + return commands; + } + + set commands(commands) { + this.#commands.splice(0, this.#commands.get_n_items(), commands); + this.notify("commands"); + } + + /** + * Create an entry row from the given string object. + * @param {Gtk.StringObject} strObject The string object. + * @returns {Adw.EntryRow} The created entry row. + */ + #createCommandEntryRow(strObject) { + const findItemPositionInCommands = () => { + let position = -1; + for (let i = 0; i < this.#commands.get_n_items(); i++) { + const listItem = this.#commands.get_item(i); + if (listItem === strObject) { + position = i; + break; + } + } + return position; + }; + + const attrList = Pango.AttrList.new(); + attrList.insert(Pango.attr_family_new("monospace")); + + const entryRow = new Adw.EntryRow({ + title: _("Command"), + text: strObject.string, + showApplyButton: true, + inputHints: Gtk.InputHints.NO_SPELLCHECK, + inputPurpose: Gtk.InputPurpose.TERMINAL, + attributes: attrList, + }); + entryRow.connect("apply", () => { + const position = findItemPositionInCommands(strObject); + if (position === -1) return; + this.#commands.splice(position, 1, [entryRow.text]); + this.notify("commands"); + }); + + const removeButton = new Gtk.Button({ + tooltipText: _("Remove command"), + iconName: "nightthemeswitcher-trash-symbolic", + valign: Gtk.Align.CENTER, + cssClasses: ["circular", "flat"], + }); + removeButton.connect("clicked", () => { + const position = findItemPositionInCommands(strObject); + if (position === -1) return; + this.#commands.remove(position); + this.notify("commands"); + }); + + entryRow.add_suffix(removeButton); + + return entryRow; + } + + onAddButtonClicked() { + this.#commands.append(""); + } +} diff --git a/src/preferences/CommandsPage.js b/src/preferences/CommandsPage.js index c8ebc9f..7e9b24b 100644 --- a/src/preferences/CommandsPage.js +++ b/src/preferences/CommandsPage.js @@ -11,17 +11,31 @@ export class CommandsPage extends Adw.PreferencesPage { { GTypeName: "CommandsPage", Template: "resource:///org/gnome/Shell/Extensions/nightthemeswitcher/preferences/ui/CommandsPage.ui", - InternalChildren: ["enabled_switch", "sunrise_entry", "sunset_entry"], + InternalChildren: ["enabled_switch", "sunrise_commands_editor", "sunset_commands_editor"], }, this, ); } - constructor({ settings, ...params } = {}) { + constructor({ commandsSettings, ...params } = {}) { super(params); - settings.bind("enabled", this._enabled_switch, "active", Gio.SettingsBindFlags.DEFAULT); - settings.bind("sunrise", this._sunrise_entry, "text", Gio.SettingsBindFlags.DEFAULT); - settings.bind("sunset", this._sunset_entry, "text", Gio.SettingsBindFlags.DEFAULT); + commandsSettings.bind("enabled", this._enabled_switch, "active", Gio.SettingsBindFlags.DEFAULT); + + this._sunrise_commands_editor.commands = commandsSettings.get_strv("sunrise-commands"); + commandsSettings.connect("changed::sunrise-commands", () => { + this._sunrise_commands_editor.commands = commandsSettings.get_strv("sunrise-commands"); + }); + this._sunrise_commands_editor.connect("notify::commands", () => { + commandsSettings.set_strv("sunrise-commands", this._sunrise_commands_editor.commands); + }); + + this._sunset_commands_editor.commands = commandsSettings.get_strv("sunset-commands"); + commandsSettings.connect("changed::sunset-commands", () => { + this._sunset_commands_editor.commands = commandsSettings.get_strv("sunset-commands"); + }); + this._sunset_commands_editor.connect("notify::commands", () => { + commandsSettings.set_strv("sunset-commands", this._sunset_commands_editor.commands); + }); } } diff --git a/src/prefs.js b/src/prefs.js index 24d6c18..2ed3376 100644 --- a/src/prefs.js +++ b/src/prefs.js @@ -48,6 +48,7 @@ export default class NightThemeSwitcherPreferences extends ExtensionPreferences const { AppearancePreview } = await import("./preferences/AppearancePreview.js"); const { BackgroundButton } = await import("./preferences/BackgroundButton.js"); const { ColorChooser } = await import("./preferences/ColorChooser.js"); + const { CommandsEditor } = await import("./preferences/CommandsEditor.js"); const { CommandsPage } = await import("./preferences/CommandsPage.js"); const { ContributePage } = await import("./preferences/ContributePage.js"); const { ShortcutButton } = await import("./preferences/ShortcutButton.js"); @@ -60,6 +61,7 @@ export default class NightThemeSwitcherPreferences extends ExtensionPreferences GObject.type_ensure(AppearancePreview); GObject.type_ensure(BackgroundButton); GObject.type_ensure(ColorChooser); + GObject.type_ensure(CommandsEditor); GObject.type_ensure(CommandsPage); GObject.type_ensure(ContributePage); GObject.type_ensure(ShortcutButton); @@ -71,7 +73,7 @@ export default class NightThemeSwitcherPreferences extends ExtensionPreferences new AppearancePage({ accentColorSettings: this.getSettings(`${this.metadata["settings-schema"]}.accent-color`), }), - new CommandsPage({ settings: this.getSettings(`${this.metadata["settings-schema"]}.commands`) }), + new CommandsPage({ commandsSettings: this.getSettings(`${this.metadata["settings-schema"]}.commands`) }), new TweaksPage({ locationSettings: this.getSettings(`${this.metadata["settings-schema"]}.location`), timeSettings: this.getSettings(`${this.metadata["settings-schema"]}.time`),