diff --git a/package.json b/package.json index 292d246..788e59c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "gnome-gradienttopbar", "title": "Gradient Top Bar", "description": "Makes GNOME's panel's background gradient. You can edit the colour scheme from the extension's settings in Gnome 45.", - "version": "22", + "version": "23-rc", "engines": { "gnome": ">=47" }, diff --git a/src/config.js b/src/config.js index d65f836..1013447 100644 --- a/src/config.js +++ b/src/config.js @@ -20,6 +20,7 @@ export const MAXIMIZED_COLORS = 'maximized-colors'; * @returns {Object} The complete configuration object */ export const getConfig = settings => { + const maximizationType = settings.get_string('maximization-type'); const maximizedBehavior = settings.get_string(MAXIMIZED_BEHAVIOR); const colors = settings.get_value(COLORS).deep_unpack(); const gradientDirection = settings.get_string(GRADIENT_DIRECTION); @@ -28,6 +29,7 @@ export const getConfig = settings => { return { maximizedBehavior, + maximizationType, gradientDirection, colors: { start: colors[0], @@ -152,6 +154,7 @@ export const saveMaximizedColors = (settings, startRgba, endRgba) => { export const attachSettingsListeners = (settings, listener) => { settings.connect(`changed::${GRADIENT_DIRECTION}`, listener); settings.connect(`changed::${MAXIMIZED_BEHAVIOR}`, listener); + settings.connect('changed::maximization-type', listener); settings.connect(`changed::${COLORS}`, listener); settings.connect(`changed::${MAXIMIZED_COLORS}`, listener); settings.connect(`changed::${MAXIMIZED_GRADIENT_DIRECTION}`, listener); @@ -166,6 +169,7 @@ export const attachSettingsListeners = (settings, listener) => { export const detachSettingsListeners = (settings, listener) => { settings.disconnect(`changed::${GRADIENT_DIRECTION}`, listener); settings.disconnect(`changed::${MAXIMIZED_BEHAVIOR}`, listener); + settings.disconnect('changed::maximization-type', listener); settings.disconnect(`changed::${COLORS}`, listener); settings.disconnect(`changed::${MAXIMIZED_COLORS}`, listener); settings.disconnect(`changed::${MAXIMIZED_GRADIENT_DIRECTION}`, listener); diff --git a/src/constants.js b/src/constants.js index 4e99d52..d43964b 100644 --- a/src/constants.js +++ b/src/constants.js @@ -9,6 +9,14 @@ export const MAXIMIZED_BEHAVIOR = { APPLY_STYLE: 'apply-style' }; +// Maximization type constants +export const MAXIMIZATION_TYPE = { + BOTH: 'both', + VERTICAL: 'vertical', + HORIZONTAL: 'horizontal', + ANY: 'any' +}; + // Gradient directions export const GRADIENT_DIRECTION = { VERTICAL: 'vertical', diff --git a/src/events/windowEvents.js b/src/events/windowEvents.js index d85b4f0..5f43ea1 100644 --- a/src/events/windowEvents.js +++ b/src/events/windowEvents.js @@ -3,8 +3,9 @@ import { overview } from 'resource:///org/gnome/shell/ui/main.js'; import EventManager from './eventManager.js'; import { areSameState } from './states.js'; +import { MAXIMIZATION_TYPE } from '../constants.js'; -const { VERTICAL, BOTH } = Meta.MaximizeFlags; +const { VERTICAL, HORIZONTAL, BOTH } = Meta.MaximizeFlags; const SIZE_CHANGE_EVENT = 'size-changed'; const WORKSPACE_CHANGE_EVENT = 'workspace-switched'; @@ -24,26 +25,37 @@ const OVERVIEW_HIDING = 'hiding'; // FIXME: this causes the overview to close on login const isDesktopIconsNG = window => window.customJS_ding !== undefined; // this is to ignore "Desktop Icons NG"'s window hacks +// Check if window is full screen or monitor sized +const isFullScreen = window => window.is_monitor_sized() || window.is_screen_sized(); + /** * Determines if a window is maximized or full-screen * * @param {Meta.Window} window - The window to check + * @param {string} maximizationType - Maximization type definition * @returns {boolean} True if the window is maximized or full-screen */ -const isMaximized = window => { +const isMaximized = (window, maximizationType = MAXIMIZATION_TYPE.BOTH) => { // Ignore Desktop Icons NG windows if (isDesktopIconsNG(window)) return false; - - // Check if window is full-screen - if (window.is_monitor_sized() || window.is_screen_sized()) - return true; - - - // Check if window is maximized (either vertically or both dimensions) - const maximizeFlags = window.get_maximized(); - return [BOTH, VERTICAL].includes(maximizeFlags); + const windowMaximizeState = window.get_maximized(); + + switch (maximizationType) { + case MAXIMIZATION_TYPE.BOTH: + return windowMaximizeState === BOTH || isFullScreen(window); + case MAXIMIZATION_TYPE.VERTICAL: + return windowMaximizeState === VERTICAL || windowMaximizeState === BOTH; + case MAXIMIZATION_TYPE.HORIZONTAL: + return windowMaximizeState === HORIZONTAL || windowMaximizeState === BOTH; + case MAXIMIZATION_TYPE.ANY: + return windowMaximizeState === VERTICAL || + windowMaximizeState === HORIZONTAL || + windowMaximizeState === BOTH || isFullScreen(window); + default: + return windowMaximizeState === BOTH; + } }; /** @@ -70,6 +82,7 @@ export default class WindowEvents { this.workspace = null; this.inOverview = false; this.maximizedWindows = new Set(); + this.maximizationType = MAXIMIZATION_TYPE.BOTH; // Default value this.lastState = null; } @@ -82,6 +95,23 @@ export default class WindowEvents { this.stateChangeCallback = callback; } + setMaximizationType(type) { + this.maximizationType = type; + this.forceStateUpdate(); + } + + forceStateUpdate() { + // Update the maximized windows set based on the current maximization type + this.maximizedWindows = this.getMaximizedWindowIds(); + + // Force a state change emission + this.stateChangeCallback({ + maximizedWindows: this.maximizedWindows, + currentWorkspace: this.workspace, + inOverview: this.inOverview + }); + } + /** * Gets the current window state * @@ -130,7 +160,7 @@ export default class WindowEvents { * @param {Meta.Window} window - The window that changed size */ const onWindowSizeChange = window => { - if (isMaximized(window)) + if (isMaximized(window, this.maximizationType)) this.maximizedWindows.add(window.get_id()); else this.maximizedWindows.delete(window.get_id()); @@ -179,7 +209,7 @@ export default class WindowEvents { ); } - if (isMaximized(window)) + if (isMaximized(window, this.maximizationType)) this.maximizedWindows.add(window.get_id()); this.eventManager.attachWindowEventOnce( @@ -209,11 +239,13 @@ export default class WindowEvents { */ const onWindowRaise = (_, windowActor) => { const window = windowActor.get_meta_window(); - if (isMaximized(window)) + if (isMaximized(window, this.maximizationType)) this.maximizedWindows.add(window.get_id()); this.emitStateChange(); }; + const forceStateChangeEmission = () => emitStateChange(true); + this.workspace = this.workspaceManager.get_active_workspace(); // TODO: what if the window starts as maximized dimensions but is not "snapped"? @@ -310,7 +342,7 @@ export default class WindowEvents { return new Set( this.display .list_all_windows() - .filter(isMaximized) + .filter(window => isMaximized(window, this.maximizationType)) .map(window => window.get_id()) ); } diff --git a/src/extension.js b/src/extension.js index 0a2139d..616e897 100755 --- a/src/extension.js +++ b/src/extension.js @@ -23,6 +23,15 @@ export default class GradientTopBar extends Extension { const config = getConfig(settings); applyGradientStyle(config, this.path); + // For other behaviors, we need to track window states + if (!this.windowEvents) { + // If window events were not initialized, initialize them now + this.initializeWindowEvents(); + } + // Update maximization type if it changed + if (this.windowEvents) + this.windowEvents.setMaximizationType(config.maximizationType); + const maximizedBehavior = getMaximizedBehavior(settings); // If set to keep-gradient, disable window events to save resources @@ -34,12 +43,9 @@ export default class GradientTopBar extends Extension { return; } - // For other behaviors, we need to track window states - if (!this.windowEvents) { - // If window events were not initialized, initialize them now - this.initializeWindowEvents(); - } this.windowEvents.enable(); + + // Force update to apply the current behavior this.windowEvents.forceStateUpdate(); }; } @@ -62,6 +68,10 @@ export default class GradientTopBar extends Extension { global.window_manager, global.get_workspace_manager() ); + const config = getConfig(this._settings); + + // Set the maximization type from settings + this.windowEvents.setMaximizationType(config.maximizationType); this.windowEvents.setStateChangeCallback( ({ maximizedWindows, currentWorkspace, inOverview }) => { @@ -134,6 +144,7 @@ export default class GradientTopBar extends Extension { this.toggleGradient(false); detachSettingsListeners(this._settings, this.onSettingsChanged); + this.windowEvents = null; this.isEffectApplied = false; this._settings = null; } diff --git a/src/metadata.json b/src/metadata.json index c7bd787..bafb49c 100644 --- a/src/metadata.json +++ b/src/metadata.json @@ -9,7 +9,7 @@ }, "shell-version": ["47", "48"], "url": "https://github.com/petar-v/gradienttopbar", - "version": 22, + "version": 23, "gettext-domain": "org.pshow.gradienttopbar", "settings-schema": "org.gnome.shell.extensions.org.pshow.gradienttopbar", "hasPrefs": true diff --git a/src/schemas/gschemas.compiled b/src/schemas/gschemas.compiled index 1d9ebc6..d63b810 100644 Binary files a/src/schemas/gschemas.compiled and b/src/schemas/gschemas.compiled differ diff --git a/src/schemas/org.gnome.shell.extensions.org.pshow.gradienttopbar.gschema.xml b/src/schemas/org.gnome.shell.extensions.org.pshow.gradienttopbar.gschema.xml index 90cd266..27963f8 100644 --- a/src/schemas/org.gnome.shell.extensions.org.pshow.gradienttopbar.gschema.xml +++ b/src/schemas/org.gnome.shell.extensions.org.pshow.gradienttopbar.gschema.xml @@ -9,12 +9,23 @@ + + + + + + 'keep-gradient' Behavior when windows are maximized Determines what style to apply to the top bar when there is a maximized window: keep the normal gradient, use the default theme, or apply a custom style. + + 'both' + Definition of a maximized window + Determines what constitutes a maximized window: both horizontally and vertically maximized, only vertically maximized, only horizontally maximized, or any of these. + ["rgba(0, 0, 0, 1)", "rgba(0, 0, 0, 0)"] Array of the gradient colours diff --git a/src/settings/behavioursPage.js b/src/settings/behavioursPage.js index ccc4a56..6c375ec 100644 --- a/src/settings/behavioursPage.js +++ b/src/settings/behavioursPage.js @@ -12,6 +12,7 @@ import { getMaximizedBehavior, setMaximizedBehavior } from '../config.js'; +import { MAXIMIZED_BEHAVIOR, MAXIMIZATION_TYPE } from '../constants.js'; import { MAXIMIZED_BEHAVIOR } from '../constants.js'; @@ -41,6 +42,32 @@ const MaximizedBehavior = GObject.registerClass( } ); +const MaximizationType = GObject.registerClass( + { + Properties: { + name: GObject.ParamSpec.string( + 'name', + 'name', + 'name', + GObject.ParamFlags.READWRITE, + null + ), + value: GObject.ParamSpec.string( + 'value', + 'value', + 'value', + GObject.ParamFlags.READWRITE, + null + ) + } + }, + class MaximizationType extends GObject.Object { + _init(name, value) { + super._init({ name, value }); + } + } +); + class Behavior extends Adw.PreferencesPage { _init(window, settings) { super._init({ @@ -90,18 +117,88 @@ class Behavior extends Adw.PreferencesPage { // Set initial value setMaximizedBehaviorOnRow(maximizedBehaviorRow, getMaximizedBehavior(this._settings)); - // Connect to changes + // Create a group for maximization detection settings + const maximizationDetectionGroup = new Adw.PreferencesGroup({ + title: gettext('Maximization Detection') + }); + + // Create model for maximization type dropdown + const maximizationTypeModel = new Gio.ListStore({ + item_type: MaximizationType + }); + + [ + new MaximizationType(gettext('Both horizontally and vertically'), MAXIMIZATION_TYPE.BOTH), + new MaximizationType(gettext('Vertically only'), MAXIMIZATION_TYPE.VERTICAL), + new MaximizationType(gettext('Horizontally only'), MAXIMIZATION_TYPE.HORIZONTAL), + new MaximizationType(gettext('Any of these'), MAXIMIZATION_TYPE.ANY) + ].forEach(type => maximizationTypeModel.append(type)); + + // Create dropdown for maximization type + const maximizationTypeRow = new Adw.ComboRow({ + title: gettext('Definition of a maximized window'), + subtitle: gettext( + 'Choose what constitutes a maximized window for the extension' + ), + model: maximizationTypeModel, + expression: new Gtk.PropertyExpression(MaximizationType, null, 'name') + }); + + // Set the selected item based on the current setting + const setMaximizationTypeOnRow = (row, typeValue) => { + const { model } = row; + for (let i = 0; i < model.get_n_items(); i++) { + if (model.get_item(i).value === typeValue) { + row.set_selected(i); + break; + } + } + }; + + // Set initial value + setMaximizationTypeOnRow(maximizationTypeRow, this._settings.get_string('maximization-type')); + + // Function to update the sensitivity of maximization detection settings + const updateMaximizationDetectionSensitivity = () => { + const maximizedBehavior = settings.get_string('maximized-behavior'); + // Only enable the maximization detection settings if we're not using 'keep-gradient' + const needsMaximizationDetection = maximizedBehavior !== MAXIMIZED_BEHAVIOR.KEEP_GRADIENT; + + // Keep the group visible but set sensitivity based on the setting + maximizationDetectionGroup.set_sensitive(needsMaximizationDetection); + }; + + // Set initial sensitivity + updateMaximizationDetectionSensitivity(); + + // Connect to changes in maximized behavior maximizedBehaviorRow.connect('notify::selected', () => { const { selectedItem } = maximizedBehaviorRow; setMaximizedBehavior(this._settings, selectedItem.value); }); + // Listen for changes to maximized-behavior setting + settings.connect('changed::maximized-behavior', updateMaximizationDetectionSensitivity); + + // Connect to changes in maximization type + maximizationTypeRow.connect('notify::selected', () => { + const { selectedItem } = maximizationTypeRow; + settings.set_string('maximization-type', selectedItem.value); + }); + + // Add rows to groups behaviorGroup.add(maximizedBehaviorRow); + maximizationDetectionGroup.add(maximizationTypeRow); + + // Add groups to page this.add(behaviorGroup); + this.add(maximizationDetectionGroup); const onSettingsChanged = s => { const config = getConfig(s); setMaximizedBehaviorOnRow(maximizedBehaviorRow, config.maximizedBehavior); + setMaximizationTypeOnRow(maximizationTypeRow, config.maximizationType); + updateMaximizationDetectionSensitivity(); }; attachSettingsListeners(settings, onSettingsChanged); window.connect('close-request', () => {