diff --git a/prefs.js b/prefs.js
index 37e6c68..7154ce5 100644
--- a/prefs.js
+++ b/prefs.js
@@ -29,6 +29,7 @@ const _ = imports.gettext.domain('burn-my-windows').gettext;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const utils = Me.imports.src.utils;
+const ProfileManager = Me.imports.src.ProfileManager.ProfileManager;
// This template widget class is defined at the bottom of this file.
var BurnMyWindowsEffectPage = null;
@@ -102,10 +103,13 @@ var PreferencesDialog = class PreferencesDialog {
// Store a reference to the general settings object.
this._settings = ExtensionUtils.getSettings();
+ // This tracks all available effect profiles.
+ this._profileManager = new ProfileManager();
+ this._profileSettingsConnections = [];
+
// Load the current effect profile. If there is none, we create a default profile.
this._settings.connect('changed::active-profile', () => {
this._loadActiveProfile();
- this._updateProfileButton();
});
// Check whether the power profiles daemon is available - if not, we hide the
@@ -266,7 +270,7 @@ var PreferencesDialog = class PreferencesDialog {
window.set_title(`Burn-My-Windows ${Me.metadata.version}`);
this._loadActiveProfile();
- this._updateProfileButton();
+ this._updateProfileMenu();
// Populate the menu with actions.
const group = Gio.SimpleActionGroup.new();
@@ -417,14 +421,21 @@ var PreferencesDialog = class PreferencesDialog {
// Setup all the profile-related actions.
{
- const profileAction = this._settings.create_action('active-profile');
- group.add_action(profileAction);
+ const activeProfileAction = this._settings.create_action('active-profile');
+ group.add_action(activeProfileAction);
const newProfileAction = Gio.SimpleAction.new('profile-new', null);
newProfileAction.connect('activate', () => {
- const profile = utils.createProfile();
- this._settings.set_string('active-profile', profile);
+ // Create a new profile and make it the active one.
+ const profile = this._profileManager.createProfile();
+ this._settings.set_string('active-profile', profile.path);
+
+ // Start editing the profile.
this._builder.get_object('edit-profile-button').active = true;
+
+ // The number of profiles has changed, so we have to update the profile
+ // selection menu.
+ this._updateProfileMenu();
});
group.add_action(newProfileAction);
@@ -437,11 +448,19 @@ var PreferencesDialog = class PreferencesDialog {
deleteProfileDialog.set_transient_for(window);
deleteProfileDialog.connect('response', (d, response) => {
if (response == 'delete') {
+ // Stop editing the current profile.
this._builder.get_object('edit-profile-button').active = false;
- const profile = this._settings.get_string('active-profile');
- utils.deleteProfile(profile);
- this._loadActiveProfile();
- this._updateProfileButton();
+
+ // Delete the currently active profile.
+ this._profileManager.deleteProfile(this._activeProfile.path);
+
+ // Select another one.
+ this._settings.set_string('active-profile',
+ this._profileManager.getProfiles()[0].path);
+
+ // The number of profiles has changed, so we have to update the profile
+ // selection menu.
+ this._updateProfileMenu();
}
});
@@ -472,7 +491,7 @@ var PreferencesDialog = class PreferencesDialog {
// Returns a Gio.Settings object for the current effect profile.
getProfileSettings() {
- return this._profileSettings;
+ return this._activeProfile.settings;
}
// Returns the widget used for the settings of this extension.
@@ -508,18 +527,23 @@ var PreferencesDialog = class PreferencesDialog {
if (button) {
// Update the settings when the color is modified.
- button.connect('color-set', () => {
- this._profileSettings.set_string(settingsKey, button.get_rgba().to_string());
- });
+ if (!button._isConnected) {
+ button.connect('color-set', () => {
+ this.getProfileSettings().set_string(settingsKey,
+ button.get_rgba().to_string());
+ });
+
+ button._isConnected = true;
+ }
// Update the button state when the settings change.
const settingSignalHandler = () => {
const rgba = new Gdk.RGBA();
- rgba.parse(this._profileSettings.get_string(settingsKey));
+ rgba.parse(this.getProfileSettings().get_string(settingsKey));
button.rgba = rgba;
};
- this._profileSettings.connect('changed::' + settingsKey, settingSignalHandler);
+ this._connectProfileSetting('changed::' + settingsKey, settingSignalHandler);
// Initialize the button with the state in the settings.
settingSignalHandler();
@@ -543,26 +567,27 @@ var PreferencesDialog = class PreferencesDialog {
_loadActiveProfile() {
- let profiles = utils.listProfiles();
- if (profiles.length == 0) {
- profiles = [utils.createProfile()];
- }
+ this._disconnectProfileSettings();
- let activeProfile = this._settings.get_string('active-profile');
- if (!profiles.includes(activeProfile)) {
- activeProfile = profiles[0];
- this._settings.set_string('active-profile', activeProfile);
+ const allProfiles = this._profileManager.getProfiles();
+ const path = this._settings.get_string('active-profile');
+ this._activeProfile = allProfiles.find(p => p.path == path);
+
+ if (!this._activeProfile) {
+ utils.debug('active profile not found!!!');
+ this._activeProfile = allProfiles[0];
+ this._settings.set_string('active-profile', this._activeProfile.path);
return;
}
- utils.debug('loading ' + activeProfile);
-
- this._profileSettings = utils.getProfileSettings(activeProfile);
+ utils.debug('loading ' + this._activeProfile.path);
const setupProfileOption = (settingsKey) => {
this.bindComboRow(settingsKey);
- this._profileSettings.connect('changed::' + settingsKey,
- () => this._updateProfileButton());
+ this._connectProfileSetting('changed::' + settingsKey, () => {
+ this._updateProfileButton();
+ this._updateProfileMenu();
+ });
};
setupProfileOption('profile-animation-type');
@@ -579,36 +604,51 @@ var PreferencesDialog = class PreferencesDialog {
}
});
+ this._updateProfileButton();
+
utils.debug('loading done');
}
_updateProfileButton() {
- const activeProfile = this._settings.get_string('active-profile');
-
+ utils.debug('updating profile button');
this._builder.get_object('choose-profile-button').label =
- utils.getProfileName(activeProfile);
+ this._profileManager.getProfileName(this.getProfileSettings());
+ }
+ _updateProfileMenu() {
+ utils.debug('updating profile menu');
const profileSection = this._builder.get_object('profile-section');
profileSection.remove_all();
- const profiles = utils.listProfiles();
+ const profiles = this._profileManager.getProfiles();
profiles.forEach(profile => {
- const label = utils.getProfileName(profile);
+ const label = this._profileManager.getProfileName(profile.settings);
const item = Gio.MenuItem.new(label, 'prefs.active-profile');
item.set_action_and_target_value('prefs.active-profile',
- GLib.Variant.new_string(profile));
+ GLib.Variant.new_string(profile.path));
profileSection.append_item(item);
});
}
+ _connectProfileSetting(key, callback) {
+ this._profileSettingsConnections.push(
+ this.getProfileSettings().connect(key, callback));
+ }
+
+ _disconnectProfileSettings() {
+ this._profileSettingsConnections.forEach(c =>
+ this.getProfileSettings().disconnect(c));
+ this._profileSettingsConnections = [];
+ }
+
// Searches for a reset button for the given settings key and make it reset the settings
// key when clicked.
_bindResetButton(settingsKey) {
const resetButton = this._builder.get_object('reset-' + settingsKey);
if (resetButton && !resetButton._isConnected) {
resetButton.connect('clicked', () => {
- this._profileSettings.reset(settingsKey);
+ this.getProfileSettings().reset(settingsKey);
});
resetButton._isConnected = true;
}
@@ -621,8 +661,8 @@ var PreferencesDialog = class PreferencesDialog {
if (object) {
Gio.Settings.unbind(object, property);
- this._profileSettings.bind(settingsKey, object, property,
- Gio.SettingsBindFlags.DEFAULT);
+ this.getProfileSettings().bind(settingsKey, object, property,
+ Gio.SettingsBindFlags.DEFAULT);
}
this._bindResetButton(settingsKey);
diff --git a/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml b/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml
index de978d0..c8a7593 100644
--- a/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml
+++ b/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml
@@ -15,7 +15,7 @@ SPDX-License-Identifier: CC0-1.0
""
- Profile
+ Active Profile
The currently active effect profile.
diff --git a/src/ProfileManager.js b/src/ProfileManager.js
new file mode 100644
index 0000000..ce1ea3f
--- /dev/null
+++ b/src/ProfileManager.js
@@ -0,0 +1,190 @@
+//////////////////////////////////////////////////////////////////////////////////////////
+// ) ( //
+// ( /( ( ( ) ( ( ( ( )\ ) ( ( //
+// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( //
+// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ //
+// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) //
+// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< //
+// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ //
+// |__/ //
+//////////////////////////////////////////////////////////////////////////////////////////
+
+// SPDX-FileCopyrightText: Simon Schneegans
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+'use strict';
+
+const {Gio, GLib, GObject} = imports.gi;
+
+const _ = imports.gettext.domain('burn-my-windows').gettext;
+
+const ExtensionUtils = imports.misc.extensionUtils;
+const Me = imports.misc.extensionUtils.getCurrentExtension();
+const utils = Me.imports.src.utils;
+
+// extension.js:
+// - get a list of profile settings objects
+
+// prefs.js:
+// - get a (sorted) list of profile settings objects
+// - add a new profile
+// - delete a profile
+// - create a name for a given profile
+
+var ProfileManager = class {
+ // ------------------------------------------------------------ constructor / destructor
+
+ constructor() {
+ this._makeProfilesDir();
+ this._loadProfiles();
+ }
+
+ // -------------------------------------------------------------------- public interface
+
+ // Returns an array of effect profiles. Each effect profile is an object with two
+ // properties: The 'path' points to the file which contains the effect profile
+ // settings. The 'settings' property contains a Gio.Settings object for the profile.
+ // The former can be used to uniquely identify the profile, the latter can be used to
+ // access the profile's settings.
+ // This method will always return at least one profile. If none is configured, a default
+ // profile will be created.
+ getProfiles() {
+ if (this._profiles.length == 0) {
+ this._profiles.push(this._createProfile());
+ }
+
+ return this._profiles;
+ }
+
+ // This will create a new profile with all values initialized to their defaults. The
+ // method will return a effect profile object (as described above). It will also
+ // trigger an emission of the 'profiles-updated' signal.
+ createProfile() {
+ const path = `${GLib.get_user_config_dir()}/burn-my-windows/profiles/${
+ GLib.get_real_time()}.conf`;
+ const file = Gio.File.new_for_path(path);
+ file.create(Gio.FileCreateFlags.NONE, null);
+
+ const profile = {'path': path, 'settings': this._getProfileSettings(path)};
+
+ this._profiles.push(profile);
+
+ return profile;
+ }
+
+ deleteProfile(profilePath) {
+ const profileDir = `${GLib.get_user_config_dir()}/burn-my-windows/profiles`;
+ const file = Gio.File.new_for_path(profilePath);
+ if (file.has_prefix(Gio.File.new_for_path(profileDir)) && file.query_exists(null)) {
+ file.delete(null);
+ }
+
+ this._profiles = this._profiles.filter(p => p.path != profilePath);
+ }
+
+ getProfileName(settings) {
+ let items = [];
+
+ const addComponent = (settingsKey, options) => {
+ const option = settings.get_int(settingsKey);
+ if (option > 0) {
+ items.push(options[option - 1]);
+ }
+ };
+
+ // clang-format off
+ addComponent('profile-animation-type', [_('Opening Windows'),
+ _('Closing Windows')]);
+ addComponent('profile-window-type', [_('Normal Windows'),
+ _('Dialog Windows')]);
+ addComponent('profile-desktop-style', [_('Bright Mode'),
+ _('Dark Mode')]);
+ addComponent('profile-power-mode', [_('On Battery'),
+ _('Plugged In')]);
+ addComponent('profile-power-profile', [_('Power-Saver Mode'),
+ _('Balanced Mode'),
+ _('Performance Mode'),
+ _('Power Saver or Balanced'),
+ _('Balanced or Performance')]);
+ // clang-format on
+
+ let label = '';
+
+ if (items.length == 0) {
+ label = _('Default Profile');
+ } else {
+ label = items.join(' · ');
+ }
+
+ return label;
+ }
+
+ // ----------------------------------------------------------------------- private stuff
+
+ _makeProfilesDir() {
+ const path = `${GLib.get_user_config_dir()}/burn-my-windows/profiles`;
+ const dir = Gio.File.new_for_path(path);
+
+ if (!dir.query_exists(null)) {
+ dir.make_directory_with_parents(null);
+ }
+ }
+
+ _loadProfiles() {
+ const dir =
+ Gio.File.new_for_path(GLib.get_user_config_dir() + '/burn-my-windows/profiles');
+
+ const files =
+ dir.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, null);
+
+ let profilePaths = [];
+
+ while (true) {
+ const presetInfo = files.next_file(null);
+
+ if (presetInfo == null) {
+ break;
+ }
+
+ if (presetInfo.get_file_type() == Gio.FileType.REGULAR) {
+ const suffixPos = presetInfo.get_display_name().indexOf('.conf');
+ if (suffixPos > 0) {
+ profilePaths.push(dir.get_child(presetInfo.get_name()).get_path());
+ }
+ }
+ }
+
+
+ profilePaths.sort();
+
+ this._profiles = [];
+
+ profilePaths.forEach(path => {
+ this._profiles.push({'path': path, 'settings': this._getProfileSettings(path)});
+ });
+ }
+
+ // Based on
+ // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/misc/extensionUtils.js#L213
+ _getProfileSettings(profilePath) {
+
+ // Expect USER extensions to have a schemas/ subfolder, otherwise assume a
+ // SYSTEM extension that has been installed in the same prefix as the shell
+ const schemaDir = Me.dir.get_child('schemas');
+ let source;
+ if (schemaDir.query_exists(null)) {
+ source = Gio.SettingsSchemaSource.new_from_directory(
+ schemaDir.get_path(), Gio.SettingsSchemaSource.get_default(), false);
+ } else {
+ source = Gio.SettingsSchemaSource.get_default();
+ }
+
+ const schema =
+ source.lookup('org.gnome.shell.extensions.burn-my-windows-profile', true);
+
+ const backend =
+ Gio.keyfile_settings_backend_new(profilePath, '/org/gnome/shell/extensions/', null);
+
+ return new Gio.Settings({settings_schema: schema, backend: backend});
+ }
+}
\ No newline at end of file
diff --git a/src/utils.js b/src/utils.js
index 6c1e0c8..01853af 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -14,8 +14,8 @@
'use strict';
-const {Gtk, Gio, GLib} = imports.gi;
-const ByteArray = imports.byteArray;
+const {Gtk, Gio} = imports.gi;
+const ByteArray = imports.byteArray;
// libadwaita is available starting with GNOME Shell 42.
let Adw = null;
@@ -110,121 +110,3 @@ function getStringResource(path) {
const data = Gio.resources_lookup_data(path, 0);
return ByteArray.toString(ByteArray.fromGBytes(data));
}
-
-function createProfile() {
- const path =
- GLib.get_user_config_dir() + `/burn-my-windows/profiles/${GLib.get_real_time()}.conf`;
- const file = Gio.File.new_for_path(path);
-
- if (!file.get_parent().query_exists(null)) {
- file.get_parent().make_directory_with_parents(null);
- }
-
- file.create(Gio.FileCreateFlags.NONE, null);
-
- return path;
-}
-
-function deleteProfile(profilePath) {
- const profileDir = GLib.get_user_config_dir() + `/burn-my-windows/profiles`;
- const file = Gio.File.new_for_path(profilePath);
- if (file.has_prefix(Gio.File.new_for_path(profileDir)) && file.query_exists(null)) {
- file.delete(null);
- }
-}
-
-
-function listProfiles() {
-
- const profileDir =
- Gio.File.new_for_path(GLib.get_user_config_dir() + '/burn-my-windows/profiles');
-
- if (!profileDir.query_exists(null)) {
- return [];
- }
-
- const files =
- profileDir.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, null);
-
- let profiles = [];
-
- while (true) {
- const presetInfo = files.next_file(null);
-
- if (presetInfo == null) {
- break;
- }
-
- if (presetInfo.get_file_type() == Gio.FileType.REGULAR) {
- const suffixPos = presetInfo.get_display_name().indexOf('.conf');
- if (suffixPos > 0) {
- profiles.push(profileDir.get_child(presetInfo.get_name()).get_path());
- }
- }
- }
-
-
- return profiles.sort();
-}
-
-// Based on
-// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/misc/extensionUtils.js#L213
-function getProfileSettings(profilePath) {
-
- // Expect USER extensions to have a schemas/ subfolder, otherwise assume a
- // SYSTEM extension that has been installed in the same prefix as the shell
- const schemaDir = Me.dir.get_child('schemas');
- let source;
- if (schemaDir.query_exists(null)) {
- source = Gio.SettingsSchemaSource.new_from_directory(
- schemaDir.get_path(), Gio.SettingsSchemaSource.get_default(), false);
- } else {
- source = Gio.SettingsSchemaSource.get_default();
- }
-
- const schema =
- source.lookup('org.gnome.shell.extensions.burn-my-windows-profile', true);
-
- const backend =
- Gio.keyfile_settings_backend_new(profilePath, '/org/gnome/shell/extensions/', null);
-
- return new Gio.Settings({settings_schema: schema, backend: backend});
-}
-
-function getProfileName(profilePath) {
- let items = [];
- const settings = getProfileSettings(profilePath);
-
- const addItems = (settingsKey, options) => {
- const option = settings.get_int(settingsKey);
- if (option > 0) {
- items.push(options[option - 1]);
- }
- };
-
- // clang-format off
- addItems('profile-animation-type', [_('Opening Windows'),
- _('Closing Windows')]);
- addItems('profile-window-type', [_('Normal Windows'),
- _('Dialog Windows')]);
- addItems('profile-desktop-style', [_('Bright Mode'),
- _('Dark Mode')]);
- addItems('profile-power-mode', [_('On Battery'),
- _('Plugged In')]);
- addItems('profile-power-profile', [_('Power-Saver Mode'),
- _('Balanced Mode'),
- _('Performance Mode'),
- _('Power Saver or Balanced'),
- _('Balanced or Performance')]);
- // clang-format on
-
- let label = '';
-
- if (items.length == 0) {
- label = _('Default Profile');
- } else {
- label = items.join(' · ');
- }
-
- return label;
-}
\ No newline at end of file