From 8c7724607eb798bc8f61c05f26c8f6f3d5b6835b Mon Sep 17 00:00:00 2001 From: Romain Vigier Date: Sat, 19 Aug 2023 14:51:04 +0200 Subject: [PATCH] Extension: Port to ESM --- lint/eslintrc-extension.yml | 5 + src/data/preferences.gresource.xml | 2 +- src/data/ui/ContributePage.ui | 25 +---- src/debug.js | 12 +- src/enums/Time.js | 2 +- src/extension.js | 46 +++----- src/metadata.json.in | 2 +- src/modules/Switcher.js | 25 ++--- src/modules/SwitcherCommands.js | 26 ++--- src/modules/SwitcherTheme.js | 164 +++++++++++++++++++++------- src/modules/Timer.js | 57 ++++++---- src/preferences/BackgroundButton.js | 84 +++++++------- src/preferences/BackgroundsPage.js | 33 +++--- src/preferences/ClearableEntry.js | 20 ++-- src/preferences/CommandsPage.js | 36 +++--- src/preferences/ContributePage.js | 21 ++-- src/preferences/DropDownChoice.js | 58 +++++----- src/preferences/SchedulePage.js | 38 ++++--- src/preferences/ShortcutButton.js | 111 +++++++++++++++---- src/preferences/ThemesPage.js | 64 +++++------ src/preferences/TimeChooser.js | 44 ++++---- src/prefs.js | 107 ++++++++++-------- src/utils.js | 154 ++------------------------ 23 files changed, 587 insertions(+), 549 deletions(-) diff --git a/lint/eslintrc-extension.yml b/lint/eslintrc-extension.yml index b0ad18a..65bf665 100644 --- a/lint/eslintrc-extension.yml +++ b/lint/eslintrc-extension.yml @@ -7,3 +7,8 @@ rules: - vars: 'local' varsIgnorePattern: (^unused|_$) argsIgnorePattern: ^(unused|_) + object-curly-spacing: off +globals: + NTSMetadata: writable +parserOptions: + sourceType: module diff --git a/src/data/preferences.gresource.xml b/src/data/preferences.gresource.xml index 9c115ff..0e1d54c 100644 --- a/src/data/preferences.gresource.xml +++ b/src/data/preferences.gresource.xml @@ -4,7 +4,7 @@ SPDX-FileCopyrightText: 2022 Romain Vigier SPDX-License-Identifier: GPL-3.0-or-later --> - + icons/code-symbolic.svg icons/translate-symbolic.svg icons/nightthemeswitcher-symbolic.svg diff --git a/src/data/ui/ContributePage.ui b/src/data/ui/ContributePage.ui index 95b1f74..71e923e 100644 --- a/src/data/ui/ContributePage.ui +++ b/src/data/ui/ContributePage.ui @@ -23,25 +23,12 @@ SPDX-License-Identifier: GPL-3.0-or-later - - vertical - 6 - - - Night Theme Switcher - True - - - - - - - - - - + + Night Theme Switcher + True + diff --git a/src/debug.js b/src/debug.js index c5a83fd..aa56aa5 100644 --- a/src/debug.js +++ b/src/debug.js @@ -1,16 +1,12 @@ // SPDX-FileCopyrightText: 2022 Romain Vigier // SPDX-License-Identifier: GPL-3.0-or-later -const { extensionUtils } = imports.misc; - -const Me = extensionUtils.getCurrentExtension(); - /** * Print a message in debug builds. * - * @param {string} msg Message to print. + * @param {string} msg Message to print. */ -function message(msg) { - if (Me.metadata['build-type'] === 'debug') - console.log(`[${Me.metadata.name}] ${msg}`); +export function message(msg) { + if (NTSMetadata['build-type'] === 'debug') + console.log(`[${NTSMetadata.name}] ${msg}`); } diff --git a/src/enums/Time.js b/src/enums/Time.js index bf43dcf..eee8662 100644 --- a/src/enums/Time.js +++ b/src/enums/Time.js @@ -7,7 +7,7 @@ * @readonly * @enum {string} */ -var Time = { +export const Time = { UNKNOWN: 'unknown', DAY: 'day', NIGHT: 'night', diff --git a/src/extension.js b/src/extension.js index 1ce5996..a4baea0 100644 --- a/src/extension.js +++ b/src/extension.js @@ -3,40 +3,33 @@ 'use strict'; -const { Gio } = imports.gi; -const { extensionUtils } = imports.misc; +import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js'; -const Me = extensionUtils.getCurrentExtension(); +import * as debug from './debug.js'; -const debug = Me.imports.debug; - -const { SwitcherCommands } = Me.imports.modules.SwitcherCommands; -const { SwitcherThemeCursor, SwitcherThemeGtk, SwitcherThemeIcon, SwitcherThemeShell } = Me.imports.modules.SwitcherTheme; -const { Timer } = Me.imports.modules.Timer; +import { SwitcherCommands } from './modules/SwitcherCommands.js'; +import { SwitcherThemeCursor, SwitcherThemeGtk, SwitcherThemeIcon, SwitcherThemeShell } from './modules/SwitcherTheme.js'; +import { Timer } from './modules/Timer.js'; -class NightThemeSwitcher { +export default class NightThemeSwitcher extends Extension { #modules = []; - constructor() { - debug.message('Initializing extension...'); - extensionUtils.initTranslations(); - debug.message('Extension initialized.'); - } - enable() { + globalThis.NTSMetadata = this.metadata; + debug.message('Enabling extension...'); - const timer = new Timer(); + const timer = new Timer({ settings: this.getSettings(`${this.metadata['settings-schema']}.time`), openPrefs: this.openPrefs }); this.#modules.push(timer); [ - SwitcherThemeGtk, - SwitcherThemeIcon, - SwitcherThemeShell, - SwitcherThemeCursor, - SwitcherCommands, - ].forEach(SwitcherModule => this.#modules.push(new SwitcherModule({ timer }))); + new SwitcherThemeGtk({ timer, settings: this.getSettings(`${this.metadata['settings-schema']}.gtk-variants`) }), + new SwitcherThemeIcon({ timer, settings: this.getSettings(`${this.metadata['settings-schema']}.icon-variants`) }), + new SwitcherThemeShell({ timer, settings: this.getSettings(`${this.metadata['settings-schema']}.shell-variants`) }), + new SwitcherThemeCursor({ timer, settings: this.getSettings(`${this.metadata['settings-schema']}.cursor-variants`) }), + new SwitcherCommands({ timer, settings: this.getSettings(`${this.metadata['settings-schema']}.commands`) }), + ].forEach(module => this.#modules.push(module)); this.#modules.forEach(module => module.enable()); @@ -53,12 +46,7 @@ class NightThemeSwitcher { this.#modules = []; debug.message('Extension disabled.'); + + delete globalThis.NTSMetadata; } } - -/** - * Extension initialization. - */ -function init() { - return new NightThemeSwitcher(); -} diff --git a/src/metadata.json.in b/src/metadata.json.in index 499755a..e6c875b 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": ["44"], + "shell-version": ["45"], "version": @VERSION@, "build-type": "@BUILD_TYPE@" } diff --git a/src/modules/Switcher.js b/src/modules/Switcher.js index c406dad..09c6d84 100644 --- a/src/modules/Switcher.js +++ b/src/modules/Switcher.js @@ -1,14 +1,7 @@ // SPDX-FileCopyrightText: 2020-2022 Romain Vigier // SPDX-License-Identifier: GPL-3.0-or-later -const { Gio } = imports.gi; -const { extensionUtils } = imports.misc; - -const Me = extensionUtils.getCurrentExtension(); - -const debug = Me.imports.debug; - -const { Time } = Me.imports.enums.Time; +import * as debug from '../debug.js'; /** @@ -22,13 +15,8 @@ const { Time } = Me.imports.enums.Time; /** * The Switcher runs a callback function when the time changes. * - * @param {Object} params Params object. - * @param {string} params.name Name of the switcher. - * @param {Timer} params.timer Timer to listen to. - * @param {Gio.Settings} params.settings Settings with the `enabled` key. - * @param {TimeChangedCallback} params.callback Callback function. */ -var Switcher = class { +export class Switcher { #name; #timer; #settings; @@ -37,6 +25,13 @@ var Switcher = class { #statusConnection = null; #timerConnection = null; + /** + * @param {object} params Params object. + * @param {string} params.name Name of the switcher. + * @param {Timer} params.timer Timer to listen to. + * @param {Gio.Settings} params.settings Settings with the `enabled` key. + * @param {TimeChangedCallback} params.callback Callback function. + */ constructor({ name, timer, settings, callback }) { this.#name = name; this.#timer = timer; @@ -98,4 +93,4 @@ var Switcher = class { #onTimeChanged() { this.#callback(this.#timer.time); } -}; +} diff --git a/src/modules/SwitcherCommands.js b/src/modules/SwitcherCommands.js index 84862f7..0d4af10 100644 --- a/src/modules/SwitcherCommands.js +++ b/src/modules/SwitcherCommands.js @@ -1,29 +1,27 @@ // SPDX-FileCopyrightText: 2022 Romain Vigier // SPDX-License-Identifier: GPL-3.0-or-later -const { GLib } = imports.gi; -const { extensionUtils } = imports.misc; +import GLib from 'gi://GLib'; -const Me = extensionUtils.getCurrentExtension(); +import * as debug from '../debug.js'; -const debug = Me.imports.debug; +import { Time } from '../enums/Time.js'; -const { Switcher } = Me.imports.modules.Switcher; - -const { Time } = Me.imports.enums.Time; +import { Switcher } from './Switcher.js'; /** * The Commands Switcher spawns commands according to the time. - * - * @param {Object} params Params object. - * @param {Timer} params.timer Timer to listen to. */ -var SwitcherCommands = class extends Switcher { +export class SwitcherCommands extends Switcher { #settings; - constructor({ timer }) { - const settings = extensionUtils.getSettings(`${Me.metadata['settings-schema']}.commands`); + /** + * @param {object} params Params object. + * @param {Timer} params.timer Timer to listen to. + * @param {Gio.Settings} params.settings Commands settings. + */ + constructor({ timer, settings }) { super({ name: 'Command', timer, @@ -42,4 +40,4 @@ var SwitcherCommands = class extends Switcher { GLib.spawn_async(null, ['sh', '-c', command], null, GLib.SpawnFlags.SEARCH_PATH, null); debug.message(`Spawned ${time} command.`); } -}; +} diff --git a/src/modules/SwitcherTheme.js b/src/modules/SwitcherTheme.js index 582825d..a568b96 100644 --- a/src/modules/SwitcherTheme.js +++ b/src/modules/SwitcherTheme.js @@ -1,18 +1,18 @@ // SPDX-FileCopyrightText: 2020-2022 Romain Vigier // SPDX-License-Identifier: GPL-3.0-or-later -const { Gio } = imports.gi; -const { extensionUtils } = imports.misc; -const { extensionManager } = imports.ui.main; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; -const Me = extensionUtils.getCurrentExtension(); +import { ExtensionState } from 'resource:///org/gnome/shell/misc/extensionUtils.js'; +import { extensionManager, setThemeStylesheet, loadTheme } from 'resource:///org/gnome/shell/ui/main.js'; -const debug = Me.imports.debug; -const utils = Me.imports.utils; +import * as debug from '../debug.js'; +import * as utils from '../utils.js'; -const { Switcher } = Me.imports.modules.Switcher; +import { Time } from '../enums/Time.js'; -const { Time } = Me.imports.enums.Time; +import { Switcher } from './Switcher.js'; /** @@ -28,16 +28,8 @@ const { Time } = Me.imports.enums.Time; * provided settings or by running a callback function. * * It also listens to system theme changes to update the current variant setting. - * - * @param {Object} params Params object. - * @param {string} params.name Name of the switcher. - * @param {Timer} params.timer Timer to listen to. - * @param {Gio.Settings} params.settings Settings with the `enabled`, `day` and `night` keys. - * @param {Gio.Settings=} params.systemSettings System settings containing the theme name. - * @param {string} params.themeKey Settings key of the theme name. - * @param {noSettingsUpdateSystemThemeCallback} Callback function. */ -var SwitcherTheme = class extends Switcher { +export class SwitcherTheme extends Switcher { #name; #timer; #settings; @@ -47,6 +39,15 @@ var SwitcherTheme = class extends Switcher { #settingsConnections = []; + /** + * @param {object} params Params object. + * @param {string} params.name Name of the switcher. + * @param {Timer} params.timer Timer to listen to. + * @param {Gio.Settings} params.settings Settings with the `enabled`, `day` and `night` keys. + * @param {Gio.Settings} [params.systemSettings] System settings containing the theme name. + * @param {string} params.themeKey Settings key of the theme name. + * @param {noSettingsUpdateSystemThemeCallback} params.noSettingsUpdateSystemThemeCallback function. + */ constructor({ name, timer, settings, systemSettings = null, themeKey, noSettingsUpdateSystemThemeCallback = null }) { super({ name, @@ -141,59 +142,78 @@ var SwitcherTheme = class extends Switcher { else if (this.#noSettingsUpdateSystemThemeCallback) this.#noSettingsUpdateSystemThemeCallback(this.#timer.time); } -}; +} -var SwitcherThemeCursor = class extends SwitcherTheme { - constructor({ timer }) { +export class SwitcherThemeCursor extends SwitcherTheme { + /** + * @param {object} params Params object. + * @param {Timer} params.timer Timer to listen to. + * @param {Gio.Settings} params.settings Cursor theme settings. + */ + constructor({ timer, settings }) { super({ name: 'Cursor theme', timer, - settings: extensionUtils.getSettings(`${Me.metadata['settings-schema']}.cursor-variants`), + settings, systemSettings: new Gio.Settings({ schema: 'org.gnome.desktop.interface' }), themeKey: 'cursor-theme', }); } -}; +} -var SwitcherThemeGtk = class extends SwitcherTheme { - constructor({ timer }) { +export class SwitcherThemeGtk extends SwitcherTheme { + /** + * @param {object} params Params object. + * @param {Timer} params.timer Timer to listen to. + * @param {Gio.Settings} params.settings GTK theme settings. + */ + constructor({ timer, settings }) { super({ name: 'GTK theme', timer, - settings: extensionUtils.getSettings(`${Me.metadata['settings-schema']}.gtk-variants`), + settings, systemSettings: new Gio.Settings({ schema: 'org.gnome.desktop.interface' }), themeKey: 'gtk-theme', }); } -}; +} -var SwitcherThemeIcon = class extends SwitcherTheme { - constructor({ timer }) { +export class SwitcherThemeIcon extends SwitcherTheme { + /** + * @param {object} params Params object. + * @param {Timer} params.timer Timer to listen to. + * @param {Gio.Settings} params.settings Icon theme settings. + */ + constructor({ timer, settings }) { super({ name: 'Icon theme', timer, - settings: extensionUtils.getSettings(`${Me.metadata['settings-schema']}.icon-variants`), + settings, systemSettings: new Gio.Settings({ schema: 'org.gnome.desktop.interface' }), themeKey: 'icon-theme', }); } -}; +} -var SwitcherThemeShell = class extends SwitcherTheme { +export class SwitcherThemeShell extends SwitcherTheme { #settings; #extensionManagerConnection = null; - constructor({ timer }) { - const settings = extensionUtils.getSettings(`${Me.metadata['settings-schema']}.shell-variants`); + /** + * @param {object} params Params object. + * @param {Timer} params.timer Timer to listen to. + * @param {Gio.Settings} params.settings Shell theme settings. + */ + constructor({ timer, settings }) { super({ name: 'Shell theme', timer, settings, - systemSettings: utils.getUserthemesSettings(), + systemSettings: getUserthemesSettings(), themeKey: 'name', noSettingsUpdateSystemThemeCallback: time => this.#noSettingsUpdateSystemThemeCallback(time), }); @@ -215,11 +235,79 @@ var SwitcherThemeShell = class extends SwitcherTheme { #noSettingsUpdateSystemThemeCallback(time) { const shellTheme = this.#settings.get_string(time); - const stylesheet = utils.getShellThemeStylesheet(shellTheme); - utils.applyShellStylesheet(stylesheet); + const stylesheet = getShellThemeStylesheet(shellTheme); + applyShellStylesheet(stylesheet); } #onExtensionStateChanged() { - this.systemSettings = utils.getUserthemesSettings(); + this.systemSettings = getUserthemesSettings(); } -}; +} + + +/** + * Get the User Themes extension. + * + * @returns {object|undefined} The User Themes extension object or undefined if + * it isn't installed. + */ +function getUserthemesExtension() { + try { + return extensionManager.lookup('user-theme@gnome-shell-extensions.gcampax.github.com'); + } catch (_e) { + return undefined; + } +} + +/** + * Get the User Themes extension settings. + * + * @returns {Gio.Settings|null} The User Themes extension settings or null if + * the extension isn't installed. + */ +function getUserthemesSettings() { + let extension = getUserthemesExtension(); + if (!extension || extension.state !== ExtensionState.ENABLED) + return null; + const schemaDir = extension.dir.get_child('schemas'); + const GioSSS = Gio.SettingsSchemaSource; + let schemaSource; + if (schemaDir.query_exists(null)) + schemaSource = GioSSS.new_from_directory(schemaDir.get_path(), GioSSS.get_default(), false); + else + schemaSource = GioSSS.get_default(); + const schemaObj = schemaSource.lookup('org.gnome.shell.extensions.user-theme', true); + return new Gio.Settings({ settings_schema: schemaObj }); +} + +/** + * Get the shell theme stylesheet. + * + * @param {string} theme The shell theme name. + * @returns {string|null} Path to the shell theme stylesheet. + */ +function getShellThemeStylesheet(theme) { + const themeName = theme ? `'${theme}'` : 'default'; + debug.message(`Getting the ${themeName} theme shell stylesheet...`); + let stylesheet = null; + if (theme) { + const stylesheetPaths = utils.getResourcesDirsPaths('themes').map(path => GLib.build_filenamev([path, theme, 'gnome-shell', 'gnome-shell.css'])); + stylesheet = stylesheetPaths.find(path => { + const file = Gio.file_new_for_path(path); + return file.query_exists(null); + }); + } + return stylesheet; +} + +/** + * Apply a stylesheet to the shell. + * + * @param {string} stylesheet The shell stylesheet to apply. + */ +function applyShellStylesheet(stylesheet) { + debug.message('Applying shell stylesheet...'); + setThemeStylesheet(stylesheet); + loadTheme(); + debug.message('Shell stylesheet applied.'); +} diff --git a/src/modules/Timer.js b/src/modules/Timer.js index e46caa5..21ac311 100644 --- a/src/modules/Timer.js +++ b/src/modules/Timer.js @@ -1,18 +1,20 @@ // SPDX-FileCopyrightText: 2020-2022 Romain Vigier // SPDX-License-Identifier: GPL-3.0-or-later -const { Geoclue, Gio, GLib, GObject, Meta, Shell } = imports.gi; -const { extensionUtils } = imports.misc; +import Geoclue from 'gi://Geoclue'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; -const { main, messageTray } = imports.ui; +import { gettext as _ } from 'resource:///org/gnome/shell/extensions/extension.js'; +import { layoutManager, messageTray, wm } from 'resource:///org/gnome/shell/ui/main.js'; +import * as MessageTray from 'resource:///org/gnome/shell/ui/messageTray.js'; -const Me = extensionUtils.getCurrentExtension(); +import * as debug from '../debug.js'; -const _ = extensionUtils.gettext; - -const debug = Me.imports.debug; - -const { Time } = Me.imports.enums.Time; +import { Time } from '../enums/Time.js'; /** @@ -24,10 +26,11 @@ const { Time } = Me.imports.enums.Time; * to a manual schedule if the location services are disabled or if the user * forced the manual schedule in the preferences. */ -var Timer = class extends GObject.Object { +export class Timer extends GObject.Object { #settings; #interfaceSettings; #locationSettings; + #openPrefs; #time; #cancellable = null; @@ -48,11 +51,17 @@ var Timer = class extends GObject.Object { }, this); } - constructor() { + /** + * @param {object} params Params object. + * @param {Gio.Settings} params.settings Timer settings. + * @param {Function} params.openPrefs Function opening the extension preferences. + */ + constructor({ settings, openPrefs }) { super(); - this.#settings = extensionUtils.getSettings(`${Me.metadata['settings-schema']}.time`); + this.#settings = settings; this.#interfaceSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' }); this.#locationSettings = new Gio.Settings({ schema: 'org.gnome.system.location' }); + this.#openPrefs = openPrefs; } enable() { @@ -103,7 +112,7 @@ var Timer = class extends GObject.Object { debug.message(manual ? `Time manually set to ${time}.` : `Time changed to ${time}.`); - main.layoutManager.screenTransition.run(); + layoutManager.screenTransition.run(); this.#interfaceSettings.set_string('color-scheme', time === Time.NIGHT ? 'prefer-dark' : 'default'); this.notify('time'); } @@ -203,7 +212,7 @@ var Timer = class extends GObject.Object { if (!this.#settings.get_strv('nightthemeswitcher-ondemand-keybinding')[0]) return; debug.message('Adding keybinding...'); - main.wm.addKeybinding( + wm.addKeybinding( 'nightthemeswitcher-ondemand-keybinding', this.#settings, Meta.KeyBindingFlags.IGNORE_AUTOREPEAT, @@ -219,7 +228,7 @@ var Timer = class extends GObject.Object { #removeKeybinding() { if (this.#previousKeybinding) { debug.message('Removing keybinding...'); - main.wm.removeKeybinding('nightthemeswitcher-ondemand-keybinding'); + wm.removeKeybinding('nightthemeswitcher-ondemand-keybinding'); debug.message('Removed keybinding.'); } } @@ -258,29 +267,29 @@ var Timer = class extends GObject.Object { } catch (e) { const [latitude, longitude] = this.#settings.get_value('location').deepUnpack(); if (latitude >= -90 && latitude <= 90 && longitude >= -180 && longitude <= 180) { - console.error(`[${Me.metadata.name}] Unable to retrieve the location, using the last known location instead.\n${e}`); + console.error(`[${NTSMetadata.name}] Unable to retrieve the location, using the last known location instead.\n${e}`); this.#updateSuntimes(); } else { - console.error(`[${Me.metadata.name}] Unable to retrieve the location, using the manual schedule times instead.\n${e}`); + console.error(`[${NTSMetadata.name}] Unable to retrieve the location, using the manual schedule times instead.\n${e}`); - const source = new messageTray.Source(Me.metadata.name, 'dialog-information-symbolic'); - main.messageTray.add(source); + const source = new MessageTray.Source(NTSMetadata.name, 'dialog-information-symbolic'); + messageTray.add(source); - const notification = new messageTray.Notification( + const notification = new MessageTray.Notification( source, _('Unknown Location'), _('A manual schedule will be used to switch the dark mode.'), { - gicon: Gio.icon_new_for_string(GLib.build_filenamev([Me.path, 'icons', 'nightthemeswitcher-symbolic.svg'])), + gicon: Gio.icon_new_for_string(GLib.build_filenamev([NTSMetadata.path, 'icons', 'nightthemeswitcher-symbolic.svg'])), } ); - notification.addAction(_('Edit Manual Schedule'), () => extensionUtils.openPrefs()); + notification.addAction(_('Edit Manual Schedule'), () => this.#openPrefs()); const locationSettingsApp = Shell.AppSystem.get_default().lookup_app('gnome-location-panel.desktop'); if (locationSettingsApp) notification.addAction(_('Open Location Settings'), () => locationSettingsApp.activate()); - notification.connect('activated', () => extensionUtils.openPrefs()); + notification.connect('activated', () => this.#openPrefs()); source.showNotification(notification); @@ -364,4 +373,4 @@ var Timer = class extends GObject.Object { debug.message(`New sun times: (sunrise: ${sunrise}; sunset: ${sunset})`); } -}; +} diff --git a/src/preferences/BackgroundButton.js b/src/preferences/BackgroundButton.js index 5aee205..a6029b2 100644 --- a/src/preferences/BackgroundButton.js +++ b/src/preferences/BackgroundButton.js @@ -1,47 +1,55 @@ // SPDX-FileCopyrightText: 2021, 2022 Romain Vigier // SPDX-License-Identifier: GPL-3.0-or-later -const { Adw, Gdk, GdkPixbuf, Gio, GLib, GObject, Gtk } = imports.gi; -const { extensionUtils } = imports.misc; +import Adw from 'gi://Adw'; +import Gdk from 'gi://Gdk'; +import GdkPixbuf from 'gi://GdkPixbuf'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Gtk from 'gi://Gtk'; -const Me = extensionUtils.getCurrentExtension(); -const _ = extensionUtils.gettext; +import { gettext as _ } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'; -var BackgroundButton = GObject.registerClass({ - GTypeName: 'BackgroundButton', - Template: 'resource:///org/gnome/shell/extensions/nightthemeswitcher/preferences/ui/BackgroundButton.ui', - InternalChildren: ['filechooser', 'thumbnail'], - Properties: { - uri: GObject.ParamSpec.string( - 'uri', - 'URI', - 'URI to the background file', - GObject.ParamFlags.READWRITE, - null - ), - thumbWidth: GObject.ParamSpec.int( - 'thumb-width', - 'Thumbnail width', - 'Width of the displayed thumbnail', - GObject.ParamFlags.READWRITE, - 0, 600, - 180 - ), - thumbHeight: GObject.ParamSpec.int( - 'thumb-height', - 'Thumbnail height', - 'Height of the displayed thumbnail', - GObject.ParamFlags.READWRITE, - 0, 600, - 180 - ), - }, -}, class BackgroundButton extends Gtk.Button { +export class BackgroundButton extends Gtk.Button { #uri; - constructor(props = {}) { - super(props); + static { + GObject.registerClass({ + GTypeName: 'BackgroundButton', + Template: 'resource:///org/gnome/Shell/Extensions/nightthemeswitcher/preferences/ui/BackgroundButton.ui', + InternalChildren: ['filechooser', 'thumbnail'], + Properties: { + uri: GObject.ParamSpec.string( + 'uri', + 'URI', + 'URI to the background file', + GObject.ParamFlags.READWRITE, + null + ), + thumbWidth: GObject.ParamSpec.int( + 'thumb-width', + 'Thumbnail width', + 'Width of the displayed thumbnail', + GObject.ParamFlags.READWRITE, + 0, 600, + 180 + ), + thumbHeight: GObject.ParamSpec.int( + 'thumb-height', + 'Thumbnail height', + 'Height of the displayed thumbnail', + GObject.ParamFlags.READWRITE, + 0, 600, + 180 + ), + }, + }, this); + } + + constructor({ ...params } = {}) { + super(params); this.#setupSize(); this.#setupDropTarget(); this.#setupFileChooserFilter(); @@ -151,7 +159,7 @@ var BackgroundButton = GObject.registerClass({ if (!this.#isContentTypeSupported(Gio.content_type_guess(path, null)[0])) throw new Error(); } catch (e) { - console.error(`[${Me.metadata.name}] No suitable background file found in ${file.get_path()}.\n${e}`); + console.error(`No suitable background file found in ${file.get_path()}.\n${e}`); return; } } else { @@ -172,4 +180,4 @@ var BackgroundButton = GObject.registerClass({ this._thumbnail.paintable = Gdk.Texture.new_for_pixbuf(thumbPixbuf); } -}); +} diff --git a/src/preferences/BackgroundsPage.js b/src/preferences/BackgroundsPage.js index 73484b2..cfd1c14 100644 --- a/src/preferences/BackgroundsPage.js +++ b/src/preferences/BackgroundsPage.js @@ -1,25 +1,28 @@ // SPDX-FileCopyrightText: 2020-2022 Romain Vigier // SPDX-License-Identifier: GPL-3.0-or-later -const { Adw, Gio, GLib, GObject } = imports.gi; -const { extensionUtils } = imports.misc; - -const Me = extensionUtils.getCurrentExtension(); +import Adw from 'gi://Adw'; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; -var BackgroundsPage = GObject.registerClass({ - GTypeName: 'BackgroundsPage', - Template: 'resource:///org/gnome/shell/extensions/nightthemeswitcher/preferences/ui/BackgroundsPage.ui', - InternalChildren: [ - 'day_button', - 'night_button', - ], -}, class BackgroundsPage extends Adw.PreferencesPage { - constructor(props = {}) { - super(props); +export class BackgroundsPage extends Adw.PreferencesPage { + static { + GObject.registerClass({ + GTypeName: 'BackgroundsPage', + Template: 'resource:///org/gnome/Shell/Extensions/nightthemeswitcher/preferences/ui/BackgroundsPage.ui', + InternalChildren: [ + 'day_button', + 'night_button', + ], + }, this); + } + + constructor({ ...params } = {}) { + super(params); const settings = new Gio.Settings({ schema: 'org.gnome.desktop.background' }); settings.bind('picture-uri', this._day_button, 'uri', Gio.SettingsBindFlags.DEFAULT); settings.bind('picture-uri-dark', this._night_button, 'uri', Gio.SettingsBindFlags.DEFAULT); } -}); +} diff --git a/src/preferences/ClearableEntry.js b/src/preferences/ClearableEntry.js index c9a8f02..2136d67 100644 --- a/src/preferences/ClearableEntry.js +++ b/src/preferences/ClearableEntry.js @@ -1,18 +1,20 @@ // SPDX-FileCopyrightText: 2021 Romain Vigier // SPDX-License-Identifier: GPL-3.0-or-later -const { Gio, GLib, GObject, Gtk } = imports.gi; -const { extensionUtils } = imports.misc; - -const Me = extensionUtils.getCurrentExtension(); +import GObject from 'gi://GObject'; +import Gtk from 'gi://Gtk'; -var ClearableEntry = GObject.registerClass({ - GTypeName: 'ClearableEntry', - Template: 'resource:///org/gnome/shell/extensions/nightthemeswitcher/preferences/ui/ClearableEntry.ui', -}, class ClearableEntry extends Gtk.Entry { +export class ClearableEntry extends Gtk.Entry { + static { + GObject.registerClass({ + GTypeName: 'ClearableEntry', + Template: 'resource:///org/gnome/Shell/Extensions/nightthemeswitcher/preferences/ui/ClearableEntry.ui', + }, this); + } + onIconReleased(entry, position) { if (position === Gtk.EntryIconPosition.SECONDARY) entry.text = ''; } -}); +} diff --git a/src/preferences/CommandsPage.js b/src/preferences/CommandsPage.js index 6b08f74..9135af3 100644 --- a/src/preferences/CommandsPage.js +++ b/src/preferences/CommandsPage.js @@ -1,27 +1,29 @@ // SPDX-FileCopyrightText: 2020-2022 Romain Vigier // SPDX-License-Identifier: GPL-3.0-or-later -const { Adw, Gio, GLib, GObject } = imports.gi; -const { extensionUtils } = imports.misc; - -const Me = extensionUtils.getCurrentExtension(); +import Adw from 'gi://Adw'; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; -var CommandsPage = GObject.registerClass({ - GTypeName: 'CommandsPage', - Template: 'resource:///org/gnome/shell/extensions/nightthemeswitcher/preferences/ui/CommandsPage.ui', - InternalChildren: [ - 'enabled_switch', - 'sunrise_entry', - 'sunset_entry', - ], -}, class CommandsPage extends Adw.PreferencesPage { - constructor(props = {}) { - super(props); - const settings = extensionUtils.getSettings(`${Me.metadata['settings-schema']}.commands`); +export class CommandsPage extends Adw.PreferencesPage { + static { + GObject.registerClass({ + GTypeName: 'CommandsPage', + Template: 'resource:///org/gnome/Shell/Extensions/nightthemeswitcher/preferences/ui/CommandsPage.ui', + InternalChildren: [ + 'enabled_switch', + 'sunrise_entry', + 'sunset_entry', + ], + }, this); + } + + constructor({ settings, ...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); } -}); +} diff --git a/src/preferences/ContributePage.js b/src/preferences/ContributePage.js index 89a4cf6..d1776b9 100644 --- a/src/preferences/ContributePage.js +++ b/src/preferences/ContributePage.js @@ -1,18 +1,15 @@ // SPDX-FileCopyrightText: 2022 Romain Vigier // SPDX-License-Identifier: GPL-3.0-or-later -const { Adw, GLib, GObject } = imports.gi; -const { extensionUtils } = imports.misc; - -const Me = extensionUtils.getCurrentExtension(); -const _ = extensionUtils.gettext; +import Adw from 'gi://Adw'; +import GObject from 'gi://GObject'; -var ContributePage = GObject.registerClass({ - GTypeName: 'ContributePage', - Template: 'resource:///org/gnome/shell/extensions/nightthemeswitcher/preferences/ui/ContributePage.ui', -}, class ContributePage extends Adw.PreferencesPage { - getVersionString(_page) { - return _('Version %d').format(Me.metadata.version); +export class ContributePage extends Adw.PreferencesPage { + static { + GObject.registerClass({ + GTypeName: 'ContributePage', + Template: 'resource:///org/gnome/Shell/Extensions/nightthemeswitcher/preferences/ui/ContributePage.ui', + }, this); } -}); +} diff --git a/src/preferences/DropDownChoice.js b/src/preferences/DropDownChoice.js index 867d569..3be5846 100644 --- a/src/preferences/DropDownChoice.js +++ b/src/preferences/DropDownChoice.js @@ -1,32 +1,36 @@ // SPDX-FileCopyrightText: 2020, 2021 Romain Vigier // SPDX-License-Identifier: GPL-3.0-or-later -const { GObject } = imports.gi; +import GObject from 'gi://GObject'; -var DropDownChoice = GObject.registerClass({ - GTypeName: 'DropDownChoice', - Properties: { - id: GObject.ParamSpec.string( - 'id', - 'ID', - 'Identifier', - GObject.ParamFlags.READWRITE, - null - ), - title: GObject.ParamSpec.string( - 'title', - 'Title', - 'Displayed title', - GObject.ParamFlags.READWRITE, - null - ), - enabled: GObject.ParamSpec.boolean( - 'enabled', - 'Enabled', - 'If the choice is enabled', - GObject.ParamFlags.READWRITE, - true - ), - }, -}, class DropDownChoice extends GObject.Object {}); +export class DropDownChoice extends GObject.Object { + static { + GObject.registerClass({ + GTypeName: 'DropDownChoice', + Properties: { + id: GObject.ParamSpec.string( + 'id', + 'ID', + 'Identifier', + GObject.ParamFlags.READWRITE, + null + ), + title: GObject.ParamSpec.string( + 'title', + 'Title', + 'Displayed title', + GObject.ParamFlags.READWRITE, + null + ), + enabled: GObject.ParamSpec.boolean( + 'enabled', + 'Enabled', + 'If the choice is enabled', + GObject.ParamFlags.READWRITE, + true + ), + }, + }, this); + } +} diff --git a/src/preferences/SchedulePage.js b/src/preferences/SchedulePage.js index c234b19..47bff59 100644 --- a/src/preferences/SchedulePage.js +++ b/src/preferences/SchedulePage.js @@ -1,25 +1,27 @@ // SPDX-FileCopyrightText: 2020-2022 Romain Vigier // SPDX-License-Identifier: GPL-3.0-or-later -const { Adw, Gio, GLib, GObject, Gtk } = imports.gi; -const { extensionUtils } = imports.misc; - -const Me = extensionUtils.getCurrentExtension(); +import Adw from 'gi://Adw'; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; -var SchedulePage = GObject.registerClass({ - GTypeName: 'SchedulePage', - Template: 'resource:///org/gnome/shell/extensions/nightthemeswitcher/preferences/ui/SchedulePage.ui', - InternalChildren: [ - 'manual_schedule_switch', - 'keyboard_shortcut_button', - 'schedule_sunrise_time_chooser', - 'schedule_sunset_time_chooser', - ], -}, class SchedulePage extends Adw.PreferencesPage { - constructor(props = {}) { - super(props); - const settings = extensionUtils.getSettings(`${Me.metadata['settings-schema']}.time`); +export class SchedulePage extends Adw.PreferencesPage { + static { + GObject.registerClass({ + GTypeName: 'SchedulePage', + Template: 'resource:///org/gnome/Shell/Extensions/nightthemeswitcher/preferences/ui/SchedulePage.ui', + InternalChildren: [ + 'manual_schedule_switch', + 'keyboard_shortcut_button', + 'schedule_sunrise_time_chooser', + 'schedule_sunset_time_chooser', + ], + }, this); + } + + constructor({ settings, ...params } = {}) { + super(params); settings.bind('manual-schedule', this._manual_schedule_switch, 'active', Gio.SettingsBindFlags.DEFAULT); @@ -34,4 +36,4 @@ var SchedulePage = GObject.registerClass({ }); this._keyboard_shortcut_button.keybinding = settings.get_strv('nightthemeswitcher-ondemand-keybinding')[0]; } -}); +} diff --git a/src/preferences/ShortcutButton.js b/src/preferences/ShortcutButton.js index 18879d4..f46c281 100644 --- a/src/preferences/ShortcutButton.js +++ b/src/preferences/ShortcutButton.js @@ -1,28 +1,29 @@ // SPDX-FileCopyrightText: 2021, 2022 Romain Vigier // SPDX-License-Identifier: GPL-3.0-or-later -const { Gdk, GLib, GObject, Gtk } = imports.gi; -const { extensionUtils } = imports.misc; - -const Me = extensionUtils.getCurrentExtension(); - -const utils = Me.imports.utils; +import Gdk from 'gi://Gdk'; +import GObject from 'gi://GObject'; +import Gtk from 'gi://Gtk'; -var ShortcutButton = GObject.registerClass({ - GTypeName: 'ShortcutButton', - Template: 'resource:///org/gnome/shell/extensions/nightthemeswitcher/preferences/ui/ShortcutButton.ui', - InternalChildren: ['choose_button', 'change_button', 'clear_button', 'dialog'], - Properties: { - keybinding: GObject.ParamSpec.string( - 'keybinding', - 'Keybinding', - 'Key sequence', - GObject.ParamFlags.READWRITE, - null - ), - }, -}, class ShortcutButton extends Gtk.Stack { +export class ShortcutButton extends Gtk.Stack { + static { + GObject.registerClass({ + GTypeName: 'ShortcutButton', + Template: 'resource:///org/gnome/Shell/Extensions/nightthemeswitcher/preferences/ui/ShortcutButton.ui', + InternalChildren: ['choose_button', 'change_button', 'clear_button', 'dialog'], + Properties: { + keybinding: GObject.ParamSpec.string( + 'keybinding', + 'Keybinding', + 'Key sequence', + GObject.ParamFlags.READWRITE, + null + ), + }, + }, this); + } + vfunc_mnemonic_activate() { this.activate(); } @@ -65,8 +66,8 @@ var ShortcutButton = GObject.registerClass({ } if ( - !utils.isBindingValid({ mask, keycode, keyval }) || - !utils.isAccelValid({ mask, keyval }) + !isBindingValid({ mask, keycode, keyval }) || + !isAccelValid({ mask, keyval }) ) return Gdk.EVENT_STOP; @@ -79,4 +80,68 @@ var ShortcutButton = GObject.registerClass({ this._dialog.close(); return Gdk.EVENT_STOP; } -}); +} + + +/** + * Check if the given keyval is forbidden. + * + * @param {number} keyval The keyval number. + * @returns {boolean} `true` if the keyval is forbidden. + */ +function isKeyvalForbidden(keyval) { + const forbiddenKeyvals = [ + Gdk.KEY_Home, + Gdk.KEY_Left, + Gdk.KEY_Up, + Gdk.KEY_Right, + Gdk.KEY_Down, + Gdk.KEY_Page_Up, + Gdk.KEY_Page_Down, + Gdk.KEY_End, + Gdk.KEY_Tab, + Gdk.KEY_KP_Enter, + Gdk.KEY_Return, + Gdk.KEY_Mode_switch, + ]; + return forbiddenKeyvals.includes(keyval); +} + +/** + * Check if the given key combo is a valid binding + * + * @param {{mask: number, keycode: number, keyval:number}} combo An object + * representing the key combo. + * @returns {boolean} `true` if the key combo is a valid binding. + */ +function isBindingValid({ mask, keycode, keyval }) { + if ((mask === 0 || mask === Gdk.SHIFT_MASK) && keycode !== 0) { + if ( + (keyval >= Gdk.KEY_a && keyval <= Gdk.KEY_z) || + (keyval >= Gdk.KEY_A && keyval <= Gdk.KEY_Z) || + (keyval >= Gdk.KEY_0 && keyval <= Gdk.KEY_9) || + (keyval >= Gdk.KEY_kana_fullstop && keyval <= Gdk.KEY_semivoicedsound) || + (keyval >= Gdk.KEY_Arabic_comma && keyval <= Gdk.KEY_Arabic_sukun) || + (keyval >= Gdk.KEY_Serbian_dje && keyval <= Gdk.KEY_Cyrillic_HARDSIGN) || + (keyval >= Gdk.KEY_Greek_ALPHAaccent && keyval <= Gdk.KEY_Greek_omega) || + (keyval >= Gdk.KEY_hebrew_doublelowline && keyval <= Gdk.KEY_hebrew_taf) || + (keyval >= Gdk.KEY_Thai_kokai && keyval <= Gdk.KEY_Thai_lekkao) || + (keyval >= Gdk.KEY_Hangul_Kiyeog && keyval <= Gdk.KEY_Hangul_J_YeorinHieuh) || + (keyval === Gdk.KEY_space && mask === 0) || + isKeyvalForbidden(keyval) + ) + return false; + } + return true; +} + +/** + * Check if the given key combo is a valid accelerator. + * + * @param {{mask: number, keyval:number}} combo An object representing the key + * combo. + * @returns {boolean} `true` if the key combo is a valid accelerator. + */ +function isAccelValid({ mask, keyval }) { + return Gtk.accelerator_valid(keyval, mask) || (keyval === Gdk.KEY_Tab && mask !== 0); +} diff --git a/src/preferences/ThemesPage.js b/src/preferences/ThemesPage.js index 14416b6..f881b2b 100644 --- a/src/preferences/ThemesPage.js +++ b/src/preferences/ThemesPage.js @@ -1,41 +1,42 @@ // SPDX-FileCopyrightText: 2020-2022 Romain Vigier // SPDX-License-Identifier: GPL-3.0-or-later -const { Adw, Gio, GLib, GObject, Gtk } = imports.gi; -const { extensionUtils } = imports.misc; +import Adw from 'gi://Adw'; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; +import Gtk from 'gi://Gtk'; -const Me = extensionUtils.getCurrentExtension(); -const _ = extensionUtils.gettext; +import { gettext as _ } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'; -const utils = Me.imports.utils; +import * as utils from '../utils.js'; -const { DropDownChoice } = Me.imports.preferences.DropDownChoice; +import { DropDownChoice } from './DropDownChoice.js'; -var ThemesPage = GObject.registerClass({ - GTypeName: 'ThemesPage', - Template: 'resource:///org/gnome/shell/extensions/nightthemeswitcher/preferences/ui/ThemesPage.ui', - InternalChildren: [ - 'gtk_row', - 'gtk_day_variant_combo_row', - 'gtk_night_variant_combo_row', - 'shell_row', - 'shell_day_variant_combo_row', - 'shell_night_variant_combo_row', - 'icon_row', - 'icon_day_variant_combo_row', - 'icon_night_variant_combo_row', - 'cursor_row', - 'cursor_day_variant_combo_row', - 'cursor_night_variant_combo_row', - ], -}, class ThemesPage extends Adw.PreferencesPage { - constructor(props = {}) { - super(props); - const gtkSettings = extensionUtils.getSettings(`${Me.metadata['settings-schema']}.gtk-variants`); - const shellSettings = extensionUtils.getSettings(`${Me.metadata['settings-schema']}.shell-variants`); - const iconSettings = extensionUtils.getSettings(`${Me.metadata['settings-schema']}.icon-variants`); - const cursorSettings = extensionUtils.getSettings(`${Me.metadata['settings-schema']}.cursor-variants`); +export class ThemesPage extends Adw.PreferencesPage { + static { + GObject.registerClass({ + GTypeName: 'ThemesPage', + Template: 'resource:///org/gnome/Shell/Extensions/nightthemeswitcher/preferences/ui/ThemesPage.ui', + InternalChildren: [ + 'gtk_row', + 'gtk_day_variant_combo_row', + 'gtk_night_variant_combo_row', + 'shell_row', + 'shell_day_variant_combo_row', + 'shell_night_variant_combo_row', + 'icon_row', + 'icon_day_variant_combo_row', + 'icon_night_variant_combo_row', + 'cursor_row', + 'cursor_day_variant_combo_row', + 'cursor_night_variant_combo_row', + ], + }, this); + } + + constructor({ gtkSettings, shellSettings, iconSettings, cursorSettings, ...params } = {}) { + super(params); gtkSettings.bind('enabled', this._gtk_row, 'enable-expansion', Gio.SettingsBindFlags.DEFAULT); @@ -65,7 +66,8 @@ var ThemesPage = GObject.registerClass({ _setupComboRow(this._cursor_day_variant_combo_row, cursorThemesStore, cursorSettings, 'day'); _setupComboRow(this._cursor_night_variant_combo_row, cursorThemesStore, cursorSettings, 'night'); } -}); +} + /** * Set up the model of a combo row. diff --git a/src/preferences/TimeChooser.js b/src/preferences/TimeChooser.js index fc76d72..955601c 100644 --- a/src/preferences/TimeChooser.js +++ b/src/preferences/TimeChooser.js @@ -1,28 +1,30 @@ // SPDX-FileCopyrightText: 2021 Romain Vigier // SPDX-License-Identifier: GPL-3.0-or-later -const { GLib, GObject, Gtk } = imports.gi; -const { extensionUtils } = imports.misc; - -const Me = extensionUtils.getCurrentExtension(); +import GObject from 'gi://GObject'; +import Gtk from 'gi://Gtk'; -var TimeChooser = GObject.registerClass({ - GTypeName: 'TimeChooser', - Template: 'resource:///org/gnome/shell/extensions/nightthemeswitcher/preferences/ui/TimeChooser.ui', - InternalChildren: ['hours', 'minutes'], - Properties: { - time: GObject.ParamSpec.double( - 'time', - 'Time', - 'The time of the chooser', - GObject.ParamFlags.READWRITE, - 0, - 24, - 0 - ), - }, -}, class TimeChooser extends Gtk.Box { +export class TimeChooser extends Gtk.Box { + static { + GObject.registerClass({ + GTypeName: 'TimeChooser', + Template: 'resource:///org/gnome/Shell/Extensions/nightthemeswitcher/preferences/ui/TimeChooser.ui', + InternalChildren: ['hours', 'minutes'], + Properties: { + time: GObject.ParamSpec.double( + 'time', + 'Time', + 'The time of the chooser', + GObject.ParamFlags.READWRITE, + 0, + 24, + 0 + ), + }, + }, this); + } + onTimeChanged(chooser) { const hours = Math.trunc(chooser.time); const minutes = Math.round((chooser.time - hours) * 60); @@ -40,4 +42,4 @@ var TimeChooser = GObject.registerClass({ spin.text = spin.value.toString().padStart(2, '0'); return true; } -}); +} diff --git a/src/prefs.js b/src/prefs.js index 84ff37f..f0364bc 100644 --- a/src/prefs.js +++ b/src/prefs.js @@ -3,57 +3,78 @@ 'use strict'; -const { Adw, Gdk, Gio, GLib, GObject, Gtk } = imports.gi; -const { extensionUtils } = imports.misc; +import Adw from 'gi://Adw'; +import Gdk from 'gi://Gdk'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Gtk from 'gi://Gtk'; -const Me = extensionUtils.getCurrentExtension(); -const _ = extensionUtils.gettext; +import { ExtensionPreferences } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'; -/** - * Initialize the preferences. - */ -function init() { - extensionUtils.initTranslations(); +export default class NightThemeSwitcherPreferences extends ExtensionPreferences { + /** + * Fill the PreferencesWindow. + * + * @param {Adw.PreferencesWindow} window The PreferencesWindow to fill. + */ + async fillPreferencesWindow(window) { + // Load resources + const resource = Gio.Resource.load(GLib.build_filenamev([this.path, 'resources', 'preferences.gresource'])); + Gio.resources_register(resource); - const resource = Gio.Resource.load(GLib.build_filenamev([Me.path, 'resources', 'preferences.gresource'])); - Gio.resources_register(resource); + // Load icons + const iconTheme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default()); + iconTheme.add_resource_path('/org/gnome/Shell/Extensions/nightthemeswitcher/preferences/icons'); - GObject.type_ensure(Me.imports.preferences.BackgroundsPage.BackgroundsPage); - GObject.type_ensure(Me.imports.preferences.CommandsPage.CommandsPage); - GObject.type_ensure(Me.imports.preferences.ContributePage.ContributePage); - GObject.type_ensure(Me.imports.preferences.SchedulePage.SchedulePage); - GObject.type_ensure(Me.imports.preferences.ThemesPage.ThemesPage); + // Set window properties + window.search_enabled = true; + window.set_default_size(640, 600); - GObject.type_ensure(Me.imports.preferences.BackgroundButton.BackgroundButton); - GObject.type_ensure(Me.imports.preferences.ClearableEntry.ClearableEntry); - GObject.type_ensure(Me.imports.preferences.ShortcutButton.ShortcutButton); - GObject.type_ensure(Me.imports.preferences.TimeChooser.TimeChooser); + // Add a dummy page until the dynamics imports are done + const dummyPage = new Adw.PreferencesPage(); + window.add(dummyPage); - const iconTheme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default()); - iconTheme.add_resource_path('/org/gnome/shell/extensions/nightthemeswitcher/preferences/icons'); -} + // Dynamically import all classes + const { BackgroundButton } = await import('./preferences/BackgroundButton.js'); + const { BackgroundsPage } = await import('./preferences/BackgroundsPage.js'); + const { ClearableEntry } = await import('./preferences/ClearableEntry.js'); + const { CommandsPage } = await import('./preferences/CommandsPage.js'); + const { ContributePage } = await import('./preferences/ContributePage.js'); + const { DropDownChoice } = await import('./preferences/DropDownChoice.js'); + const { SchedulePage } = await import('./preferences/SchedulePage.js'); + const { ShortcutButton } = await import('./preferences/ShortcutButton.js'); + const { ThemesPage } = await import('./preferences/ThemesPage.js'); + const { TimeChooser } = await import('./preferences/TimeChooser.js'); -/** - * Fill the PreferencesWindow. - * - * @param {Adw.PreferencesWindow} window The PreferencesWindow to fill. - */ -function fillPreferencesWindow(window) { - const { BackgroundsPage } = Me.imports.preferences.BackgroundsPage; - const { CommandsPage } = Me.imports.preferences.CommandsPage; - const { ContributePage } = Me.imports.preferences.ContributePage; - const { SchedulePage } = Me.imports.preferences.SchedulePage; - const { ThemesPage } = Me.imports.preferences.ThemesPage; + // Make sure all GObjects are registered + GObject.type_ensure(BackgroundButton); + GObject.type_ensure(BackgroundsPage); + GObject.type_ensure(ClearableEntry); + GObject.type_ensure(CommandsPage); + GObject.type_ensure(ContributePage); + GObject.type_ensure(DropDownChoice); + GObject.type_ensure(SchedulePage); + GObject.type_ensure(ShortcutButton); + GObject.type_ensure(ThemesPage); + GObject.type_ensure(TimeChooser); - [ - new SchedulePage(), - new BackgroundsPage(), - new CommandsPage(), - new ThemesPage(), - new ContributePage(), - ].forEach(page => window.add(page)); + // Remove the dummy page + window.remove(dummyPage); - window.search_enabled = true; - window.set_default_size(720, 490); + // Add all pages + [ + new SchedulePage({ settings: this.getSettings(`${this.metadata['settings-schema']}.time`) }), + new BackgroundsPage(), + new CommandsPage({ settings: this.getSettings(`${this.metadata['settings-schema']}.commands`) }), + new ThemesPage({ + gtkSettings: this.getSettings(`${this.metadata['settings-schema']}.gtk-variants`), + shellSettings: this.getSettings(`${this.metadata['settings-schema']}.shell-variants`), + iconSettings: this.getSettings(`${this.metadata['settings-schema']}.icon-variants`), + cursorSettings: this.getSettings(`${this.metadata['settings-schema']}.cursor-variants`), + }), + new ContributePage(), + ].forEach(page => window.add(page)); + } } diff --git a/src/utils.js b/src/utils.js index 7f7ee99..d365e16 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,15 +1,9 @@ // SPDX-FileCopyrightText: 2020, 2021 Romain Vigier // SPDX-License-Identifier: GPL-3.0-or-later -const { Gdk, Gio, GLib, Gtk } = imports.gi; -const { extensionUtils } = imports.misc; - -const Me = extensionUtils.getCurrentExtension(); - -const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']); -const _ = Gettext.gettext; - -const { ExtensionState } = extensionUtils; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import Gtk from 'gi://Gtk'; /** @@ -18,7 +12,7 @@ const { ExtensionState } = extensionUtils; * @param {string} resource The resource to get the directories. * @returns {string[]} An array of paths. */ -function getResourcesDirsPaths(resource) { +export function getResourcesDirsPaths(resource) { return [ GLib.build_filenamev([GLib.get_home_dir(), `.${resource}`]), GLib.build_filenamev([GLib.get_user_data_dir(), resource]), @@ -62,7 +56,7 @@ function getInstalledResources(type) { * * @returns {Set} A set containing all the installed GTK themes names. */ -function getInstalledGtkThemes() { +export function getInstalledGtkThemes() { const themes = new Set(); getInstalledResources('themes').forEach(theme => { const version = [0, Gtk.MINOR_VERSION].find(gtkVersion => { @@ -82,7 +76,7 @@ function getInstalledGtkThemes() { * * @returns {Set} A set containing all the installed shell themes names. */ -function getInstalledShellThemes() { +export function getInstalledShellThemes() { const themes = new Set(['']); getInstalledResources('themes').forEach(theme => { const themeFile = Gio.File.new_for_path(GLib.build_filenamev([theme.get('path'), 'gnome-shell', 'gnome-shell.css'])); @@ -97,7 +91,7 @@ function getInstalledShellThemes() { * * @returns {Set} A set containing all the installed icon themes names. */ -function getInstalledIconThemes() { +export function getInstalledIconThemes() { const themes = new Set(); getInstalledResources('icons').forEach(theme => { const themeFile = Gio.File.new_for_path(GLib.build_filenamev([theme.get('path'), 'index.theme'])); @@ -113,7 +107,7 @@ function getInstalledIconThemes() { * * @returns {Set} A set containing all the installed cursor themes names. */ -function getInstalledCursorThemes() { +export function getInstalledCursorThemes() { const themes = new Set(); getInstalledResources('icons').forEach(theme => { const themeFile = Gio.File.new_for_path(GLib.build_filenamev([theme.get('path'), 'cursors'])); @@ -123,136 +117,6 @@ function getInstalledCursorThemes() { return themes; } -/** - * Get the User Themes extension. - * - * @returns {object|undefined} The User Themes extension object or undefined if - * it isn't installed. - */ -function getUserthemesExtension() { - try { - return imports.ui.main.extensionManager.lookup('user-theme@gnome-shell-extensions.gcampax.github.com'); - } catch (_e) { - return undefined; - } -} - -/** - * Get the User Themes extension settings. - * - * @returns {Gio.Settings|null} The User Themes extension settings or null if - * the extension isn't installed. - */ -function getUserthemesSettings() { - let extension = getUserthemesExtension(); - if (!extension || extension.state !== ExtensionState.ENABLED) - return null; - const schemaDir = extension.dir.get_child('schemas'); - const GioSSS = Gio.SettingsSchemaSource; - let schemaSource; - if (schemaDir.query_exists(null)) - schemaSource = GioSSS.new_from_directory(schemaDir.get_path(), GioSSS.get_default(), false); - else - schemaSource = GioSSS.get_default(); - const schemaObj = schemaSource.lookup('org.gnome.shell.extensions.user-theme', true); - return new Gio.Settings({ settings_schema: schemaObj }); -} - -/** - * Get the shell theme stylesheet. - * - * @param {string} theme The shell theme name. - * @returns {string|null} Path to the shell theme stylesheet. - */ -function getShellThemeStylesheet(theme) { - const themeName = theme ? `'${theme}'` : 'default'; - console.debug(`Getting the ${themeName} theme shell stylesheet...`); - let stylesheet = null; - if (theme) { - const stylesheetPaths = getResourcesDirsPaths('themes').map(path => GLib.build_filenamev([path, theme, 'gnome-shell', 'gnome-shell.css'])); - stylesheet = stylesheetPaths.find(path => { - const file = Gio.file_new_for_path(path); - return file.query_exists(null); - }); - } - return stylesheet; -} - -/** - * Apply a stylesheet to the shell. - * - * @param {string} stylesheet The shell stylesheet to apply. - */ -function applyShellStylesheet(stylesheet) { - console.debug('Applying shell stylesheet...'); - imports.ui.main.setThemeStylesheet(stylesheet); - imports.ui.main.loadTheme(); - console.debug('Shell stylesheet applied.'); -} - -/** - * Check if the given keyval is forbidden. - * - * @param {number} keyval The keyval number. - * @returns {boolean} `true` if the keyval is forbidden. - */ -function isKeyvalForbidden(keyval) { - const forbiddenKeyvals = [ - Gdk.KEY_Home, - Gdk.KEY_Left, - Gdk.KEY_Up, - Gdk.KEY_Right, - Gdk.KEY_Down, - Gdk.KEY_Page_Up, - Gdk.KEY_Page_Down, - Gdk.KEY_End, - Gdk.KEY_Tab, - Gdk.KEY_KP_Enter, - Gdk.KEY_Return, - Gdk.KEY_Mode_switch, - ]; - return forbiddenKeyvals.includes(keyval); -} - -/** - * Check if the given key combo is a valid binding - * - * @param {{mask: number, keycode: number, keyval:number}} combo An object - * representing the key combo. - * @returns {boolean} `true` if the key combo is a valid binding. - */ -function isBindingValid({ mask, keycode, keyval }) { - if ((mask === 0 || mask === Gdk.SHIFT_MASK) && keycode !== 0) { - if ( - (keyval >= Gdk.KEY_a && keyval <= Gdk.KEY_z) || - (keyval >= Gdk.KEY_A && keyval <= Gdk.KEY_Z) || - (keyval >= Gdk.KEY_0 && keyval <= Gdk.KEY_9) || - (keyval >= Gdk.KEY_kana_fullstop && keyval <= Gdk.KEY_semivoicedsound) || - (keyval >= Gdk.KEY_Arabic_comma && keyval <= Gdk.KEY_Arabic_sukun) || - (keyval >= Gdk.KEY_Serbian_dje && keyval <= Gdk.KEY_Cyrillic_HARDSIGN) || - (keyval >= Gdk.KEY_Greek_ALPHAaccent && keyval <= Gdk.KEY_Greek_omega) || - (keyval >= Gdk.KEY_hebrew_doublelowline && keyval <= Gdk.KEY_hebrew_taf) || - (keyval >= Gdk.KEY_Thai_kokai && keyval <= Gdk.KEY_Thai_lekkao) || - (keyval >= Gdk.KEY_Hangul_Kiyeog && keyval <= Gdk.KEY_Hangul_J_YeorinHieuh) || - (keyval === Gdk.KEY_space && mask === 0) || - isKeyvalForbidden(keyval) - ) - return false; - } - return true; -} - -/** - * Check if the given key combo is a valid accelerator. - * - * @param {{mask: number, keyval:number}} combo An object representing the key - * combo. - * @returns {boolean} `true` if the key combo is a valid accelerator. - */ -function isAccelValid({ mask, keyval }) { - return Gtk.accelerator_valid(keyval, mask) || (keyval === Gdk.KEY_Tab && mask !== 0); -} - /** * Find an item in a `Gio.ListModel`. * @@ -260,7 +124,7 @@ function isAccelValid({ mask, keyval }) { * @param {Function} findFunction The function used to find the item. Gets the item as argument. * @returns {(*|undefined)} The found item or `undefined`. */ -function findItemPositionInModel(model, findFunction) { +export function findItemPositionInModel(model, findFunction) { const nItems = model.get_n_items(); for (let i = 0; i < nItems; i++) { if (findFunction(model.get_item(i)))