From b762d6db221f229a69ecd87473259233336ad8a5 Mon Sep 17 00:00:00 2001 From: June Date: Wed, 11 Sep 2024 04:09:29 +0200 Subject: [PATCH 01/25] docs: fix description of release-ZIP upload process The release-ZIP needs to be uploaded in the release notes section, not in the release assets section (as that's apparently not possible). --- docs/Creating_a_New_Release.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Creating_a_New_Release.md b/docs/Creating_a_New_Release.md index 38d7980..049afd2 100644 --- a/docs/Creating_a_New_Release.md +++ b/docs/Creating_a_New_Release.md @@ -43,7 +43,7 @@ To create a new tag, do the following: 2. Select the corresponding tag created earlier. 3. Name the release "Top Bar Organizer vX". 4. Copy the [release notes template](./release_notes_template.md) and fill it out. -5. Upload the release-ZIP created in the previous step as a release asset. +5. Drop the release-ZIP created in the previous step at the end of the release notes. 6. Create the release. ## Uploading to the GNOME Extensions Website From 62fd0141465b81673ab41ce9e6a47eb3d0a53505 Mon Sep 17 00:00:00 2001 From: June Date: Thu, 12 Sep 2024 21:44:26 +0200 Subject: [PATCH 02/25] docs: add newer, cut down and commented panel.js from GNOME Shell 47.rc --- .eslintignore | 1 + docs/panel_47.rc_2024-09-12.js | 317 +++++++++++++++++++++++++++++++++ 2 files changed, 318 insertions(+) create mode 100644 docs/panel_47.rc_2024-09-12.js diff --git a/.eslintignore b/.eslintignore index 2e93655..2b884b5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,3 +3,4 @@ /docs/panel_43.2_2023-01-24.js /docs/panel_45.0_2023-09-26.js /docs/panel_46.4_2024-09-11.js +/docs/panel_47.rc_2024-09-12.js diff --git a/docs/panel_47.rc_2024-09-12.js b/docs/panel_47.rc_2024-09-12.js new file mode 100644 index 0000000..504cbb6 --- /dev/null +++ b/docs/panel_47.rc_2024-09-12.js @@ -0,0 +1,317 @@ +// My annotated and cut down js/ui/panel.js from gnome-shell/47.rc. +// All annotations are what I guessed, interpreted and copied while reading the +// code and comparing to other panel.js versions and might be wrong. They are +// prefixed with "Annotation:" to indicate that they're my comments, not +// comments that orginally existed. + +// Taken from: https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/47.rc/js/ui/panel.js +// On: 2024-09-12 +// License: This code is licensed under GPLv2. + +// Taken from: https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/47.rc/js/ui/sessionMode.js +// On: 2023-09-12 +// License: This code is licensed under GPLv2. + +// I'm using the word "item" to refer to the thing, which gets added to the top +// (menu)bar / panel, where an item has a role/name and an indicator. + +// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this +// Extension. + +import Clutter from 'gi://Clutter'; + +// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this +// Extension. + +import GObject from 'gi://GObject'; + +// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this +// Extension. + +import St from 'gi://St'; + +// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this +// Extension. + +import * as CtrlAltTab from './ctrlAltTab.js'; + +// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this +// Extension. + +import * as PopupMenu from './popupMenu.js'; +import * as PanelMenu from './panelMenu.js'; + +// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this +// Extension. + +import * as Main from './main.js'; + +// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this +// Extension. + +import {DateMenuButton} from './dateMenu.js'; +import {ATIndicator} from './status/accessibility.js'; +import {InputSourceIndicator} from './status/keyboard.js'; +import {DwellClickIndicator} from './status/dwellClick.js'; +import {ScreenRecordingIndicator, ScreenSharingIndicator} from './status/remoteAccess.js'; + +// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this +// Extension. + +// Of note (for PANEL_ITEM_IMPLEMENTATIONS): +// const AppMenuButton = [...] +// const ActivitiesButton = [...] +// const QuickSettings = [...] + +const PANEL_ITEM_IMPLEMENTATIONS = { + 'activities': ActivitiesButton, + 'appMenu': AppMenuButton, + 'quickSettings': QuickSettings, + 'dateMenu': DateMenuButton, + 'a11y': ATIndicator, + 'keyboard': InputSourceIndicator, + 'dwellClick': DwellClickIndicator, + 'screenRecording': ScreenRecordingIndicator, + 'screenSharing': ScreenSharingIndicator, +}; + +export const Panel = GObject.registerClass( +class Panel extends St.Widget { + // Annotation: Initializes the top (menu)bar / panel. + // Does relevant stuff like: + // - Defining this._leftBox, this._centerBox and this._rightBox. + // - Finally calling this._updatePanel(). + // Compared to panel_46.4_2024-09-11.js: Nothing changed. + _init() { + super._init({ + name: 'panel', + reactive: true, + }); + + this.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS); + + this._sessionStyle = null; + + this.statusArea = {}; + + this.menuManager = new PopupMenu.PopupMenuManager(this); + + this._leftBox = new St.BoxLayout({name: 'panelLeft'}); + this.add_child(this._leftBox); + this._centerBox = new St.BoxLayout({name: 'panelCenter'}); + this.add_child(this._centerBox); + this._rightBox = new St.BoxLayout({name: 'panelRight'}); + this.add_child(this._rightBox); + + this.connect('button-press-event', this._onButtonPress.bind(this)); + this.connect('touch-event', this._onTouchEvent.bind(this)); + + Main.overview.connectObject('showing', + () => this.add_style_pseudo_class('overview'), + this); + Main.overview.connectObject('hiding', + () => this.remove_style_pseudo_class('overview'), + this); + + Main.layoutManager.panelBox.add_child(this); + Main.ctrlAltTabManager.addGroup(this, + _('Top Bar'), 'shell-focus-top-bar-symbolic', + {sortGroup: CtrlAltTab.SortGroup.TOP}); + + Main.sessionMode.connect('updated', this._updatePanel.bind(this)); + + global.display.connect('workareas-changed', () => this.queue_relayout()); + this._updatePanel(); + } + + // Annotation: [...] Cut out bunch of stuff here, which isn't relevant for + // this Extension. + + // Annotation: Gets called by this._init() to populate the top (menu)bar / + // panel initially. + // + // It does the following relevant stuff: + // - Calls this._hideIndicators() + // - Calls this._updateBox() for this._leftBox, this._centerBox and + // this._rightBox with panel.left, panel.center and panel.right to + // populate the boxes with items defined in panel.left, panel.center and + // panel.right. + // + // panel.left, panel.center and panel.right get set via the line let panel + // = Main.sessionMode.panel, which uses the panel of Mains (js/ui/main.js) + // instance of SessionMode (js/ui/sessionMode.js). + // + // And in js/ui/sessionMode.js (47.rc, 2024-09-12) you have different modes + // with different panel configuration. For example the "user" mode with: + // panel: { + // left: ['activities'], + // center: ['dateMenu'], + // right: ['screenRecording', 'screenSharing', 'dwellClick', 'a11y', 'keyboard', 'quickSettings'], + // } + // + // This way this function populates the top (menu)bar / panel with the + // default stuff you see on a fresh Gnome. + // + // Compared to panel_46.4_2024-09-11.js: Nothing changed. + _updatePanel() { + let panel = Main.sessionMode.panel; + this._hideIndicators(); + this._updateBox(panel.left, this._leftBox); + this._updateBox(panel.center, this._centerBox); + this._updateBox(panel.right, this._rightBox); + + if (panel.left.includes('dateMenu')) + Main.messageTray.bannerAlignment = Clutter.ActorAlign.START; + else if (panel.right.includes('dateMenu')) + Main.messageTray.bannerAlignment = Clutter.ActorAlign.END; + // Default to center if there is no dateMenu + else + Main.messageTray.bannerAlignment = Clutter.ActorAlign.CENTER; + + if (this._sessionStyle) + this.remove_style_class_name(this._sessionStyle); + + this._sessionStyle = Main.sessionMode.panelStyle; + if (this._sessionStyle) + this.add_style_class_name(this._sessionStyle); + } + + // Annotation: This function hides all items, which are in the top (menu)bar + // panel and in PANEL_ITEM_IMPLEMENTATIONS. + // + // Compared to panel_46.4_2024-09-11.js: Nothing changed. + _hideIndicators() { + for (let role in PANEL_ITEM_IMPLEMENTATIONS) { + let indicator = this.statusArea[role]; + if (!indicator) + continue; + indicator.container.hide(); + } + } + + // Annotation: This function takes a role (of an item) and returns a + // corresponding indicator, if either of two things are true: + // - The indicator is already in this.statusArea. + // Then it just returns the indicator by using this.statusArea. + // - The role is in PANEL_ITEM_IMPLEMENTATIONS. + // Then it creates a new indicator, adds it to this.statusArea and returns + // it. + // + // Compared to panel_46.4_2024-09-11.js: Nothing changed. + _ensureIndicator(role) { + let indicator = this.statusArea[role]; + if (!indicator) { + let constructor = PANEL_ITEM_IMPLEMENTATIONS[role]; + if (!constructor) { + // This icon is not implemented (this is a bug) + return null; + } + indicator = new constructor(this); + this.statusArea[role] = indicator; + } + return indicator; + } + + // Annotation: This function takes a list of items (or rather their roles) + // and adds the indicators of those items to a box (like this._leftBox) + // using this._ensureIndicator() to get the indicator corresponding to the + // given role. + // So only items with roles this._ensureIndicator() knows, get added. + // + // Compared to panel_46.4_2024-09-11.js: Nothing changed. + _updateBox(elements, box) { + let nChildren = box.get_n_children(); + + for (let i = 0; i < elements.length; i++) { + let role = elements[i]; + let indicator = this._ensureIndicator(role); + if (indicator == null) + continue; + + this._addToPanelBox(role, indicator, i + nChildren, box); + } + } + + // Annotation: This function adds the given item to the specified top + // (menu)bar / panel box and connects to "destroy" and "menu-set" events. + // + // It takes the following arguments: + // - role: The name of the item to add. + // - indicator: The indicator of the item to add. + // - position: Where in the box to add the item. + // - box: The box to add the item to. + // Can be one of the following: + // - this._leftBox + // - this._centerBox + // - this._rightBox + // + // Compared to panel_46.4_2024-09-11.js: Nothing changed. + _addToPanelBox(role, indicator, position, box) { + let container = indicator.container; + container.show(); + + let parent = container.get_parent(); + if (parent) + parent.remove_child(container); + + + box.insert_child_at_index(container, position); + this.statusArea[role] = indicator; + let destroyId = indicator.connect('destroy', emitter => { + delete this.statusArea[role]; + emitter.disconnect(destroyId); + }); + indicator.connect('menu-set', this._onMenuSet.bind(this)); + this._onMenuSet(indicator); + } + + // Annotation: This function allows you to add an item to the top (menu)bar + // / panel. + // While per default it adds the item to the status area (the right box of + // the top bar), you can specify the box and add the item to any of the + // three boxes of the top bar. + // To add an item to the top bar, you need to give its role and indicator. + // + // This function takes the following arguments: + // - role: A name for the item to add. + // - indicator: The indicator for the item to add (must be an instance of + // PanelMenu.Button). + // - position: Where in the box to add the item. + // - box: The box to add the item to. + // Can be one of the following: + // - "left": referring to this._leftBox + // - "center": referring to this._centerBox + // - "right": referring to this._rightBox + // These boxes are what you see in the top bar as the left, right and + // center sections. + // + // Finally this function just calls this._addToPanelBox() for the actual + // work, so it basically just makes sure the input to this._addToPanelBox() + // is correct. + // + // Compared to panel_46.4_2024-09-11.js: Nothing changed. + addToStatusArea(role, indicator, position, box) { + if (this.statusArea[role]) + throw new Error(`Extension point conflict: there is already a status indicator for role ${role}`); + + if (!(indicator instanceof PanelMenu.Button)) + throw new TypeError('Status indicator must be an instance of PanelMenu.Button'); + + position ??= 0; + let boxes = { + left: this._leftBox, + center: this._centerBox, + right: this._rightBox, + }; + let boxContainer = boxes[box] || this._rightBox; + this.statusArea[role] = indicator; + this._addToPanelBox(role, indicator, position, boxContainer); + return indicator; + } + + // Of note: + // _onMenuSet(indicator) { [...] } + + // Annotation: [...] Cut out bunch of stuff here, which isn't relevant for + // this Extension. +}); From 07355dcf195815a422e97813329364230fbb7061 Mon Sep 17 00:00:00 2001 From: June Date: Thu, 12 Sep 2024 22:55:05 +0200 Subject: [PATCH 03/25] feature: support GNOME Shell version 47 Checked the source of 47.rc and tested in GNOME OS and there don't seem to be any changes relevant to the functionality of this extension. --- src/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata.json b/src/metadata.json index 4248aa5..be500de 100644 --- a/src/metadata.json +++ b/src/metadata.json @@ -3,7 +3,7 @@ "name": "Top Bar Organizer", "description": "Organize the items of the top (menu)bar.", "version": 11, - "shell-version": [ "45", "46" ], + "shell-version": [ "45", "46", "47" ], "settings-schema": "org.gnome.shell.extensions.top-bar-organizer", "url": "https://gitlab.gnome.org/june/top-bar-organizer" } From 23baa41225356ca6068a24276e7549afe65579cc Mon Sep 17 00:00:00 2001 From: June Date: Thu, 12 Sep 2024 22:56:02 +0200 Subject: [PATCH 04/25] other: bump version to 12 --- src/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata.json b/src/metadata.json index be500de..e7a2fbf 100644 --- a/src/metadata.json +++ b/src/metadata.json @@ -2,7 +2,7 @@ "uuid": "top-bar-organizer@julian.gse.jsts.xyz", "name": "Top Bar Organizer", "description": "Organize the items of the top (menu)bar.", - "version": 11, + "version": 12, "shell-version": [ "45", "46", "47" ], "settings-schema": "org.gnome.shell.extensions.top-bar-organizer", "url": "https://gitlab.gnome.org/june/top-bar-organizer" From cc088f443c7cac4d95682b3ab41745b1f7d06f8c Mon Sep 17 00:00:00 2001 From: June Date: Thu, 26 Sep 2024 02:23:28 +0200 Subject: [PATCH 05/25] refactor: clearly dist. betw. an items role and the id used in settings Clearly distinguish between an items role, which is present in GNOME Shells top bar, call that a role, and the thing that is stored in the settings for an item, call that an items settings identifier. Refactor the code with these new names in mind to make it clearer. Also make the comments clearer. And finally simplify the code handling AppIndicator items by simply using the items settings identifier as the key in the roles map and with that getting rid of the confusing concept of a placeholder role, which is only relevant for the box order stored in settings. Just declaring it as an items settings identifier, which it is, is much clearer. --- src/extension.js | 13 +- src/extensionModules/BoxOrderManager.js | 187 ++++++++++++++---------- 2 files changed, 118 insertions(+), 82 deletions(-) diff --git a/src/extension.js b/src/extension.js index 7caa5b3..94778e9 100644 --- a/src/extension.js +++ b/src/extension.js @@ -16,9 +16,10 @@ export default class TopBarOrganizerExtension extends Extension { // Initially handle new top bar items and order top bar boxes. this.#handleNewItemsAndOrderTopBar(); - // Overwrite `Panel._addToPanelBox` method with one handling new items - // and also handle AppIndicators getting ready, to handle new items. + // Overwrite the `Panel._addToPanelBox` method with one handling new + // items. this.#overwritePanelAddToPanelBox(); + // Handle AppIndicators getting ready, to handle new AppIndicator items. this._boxOrderManager.connect("appIndicatorReady", () => { this.#handleNewItemsAndOrderTopBar(); }); @@ -95,7 +96,7 @@ export default class TopBarOrganizerExtension extends Extension { } // Get the valid box order. - const validBoxOrder = this._boxOrderManager.createValidBoxOrder(box); + const validBoxOrder = this._boxOrderManager.getValidBoxOrder(box); // Get the relevant box of `Main.panel`. let panelBox; @@ -111,10 +112,10 @@ export default class TopBarOrganizerExtension extends Extension { break; } - /// Go through the items (or rather their roles) of the validBoxOrder - /// and order the panelBox accordingly. + /// Go through the items of the validBoxOrder and order the GNOME Shell + /// top bar box accordingly. for (let i = 0; i < validBoxOrder.length; i++) { - const role = validBoxOrder[i]; + const role = validBoxOrder[i].role; // Get the indicator container associated with the current role. const associatedIndicatorContainer = Main.panel.statusArea[role].container; diff --git a/src/extensionModules/BoxOrderManager.js b/src/extensionModules/BoxOrderManager.js index c6f371d..90ad2a9 100644 --- a/src/extensionModules/BoxOrderManager.js +++ b/src/extensionModules/BoxOrderManager.js @@ -5,10 +5,18 @@ import GObject from "gi://GObject"; import * as Main from "resource:///org/gnome/shell/ui/main.js"; /** - * This class provides methods get, set and interact with box orders, while - * taking over the work of translating between what is stored in settings and - * what is really useable by the other extension code. - * It's basically a heavy wrapper around the box orders stored in the settings. + * A resolved box order item containing the items role and settings identifier. + * @typedef {Object} ResolvedBoxOrderItem + * @property {string} settingsId - The settings identifier of the item. + * @property {string} role - The role of the item. + */ + +/** + * This class provides an interfaces to the box orders stored in settings. + * It takes care of handling AppIndicator items and resolving from the internal + * item settings identifiers to roles. + * In the end this results in convenient functions, which are directly useful in + * other extension code. */ export default class BoxOrderManager extends GObject.Object { static { @@ -20,31 +28,33 @@ export default class BoxOrderManager extends GObject.Object { } #appIndicatorReadyHandlerIdMap; - #appIndicatorItemApplicationRoleMap; + #appIndicatorItemSettingsIdToRolesMap; #settings; constructor(params = {}, settings) { super(params); this.#appIndicatorReadyHandlerIdMap = new Map(); - this.#appIndicatorItemApplicationRoleMap = new Map(); + this.#appIndicatorItemSettingsIdToRolesMap = new Map(); this.#settings = settings; } /** - * Handles an AppIndicator/KStatusNotifierItem item by associating the role - * of the given item with the application of the - * AppIndicator/KStatusNotifier item and returning a placeholder role. - * In the case, where the application can't be determined, this method - * throws an error. However it also makes sure that once the app indicators - * "ready" signal emits, this classes "appIndicatorReady" signal emits as - * well. + * Handles an AppIndicator/KStatusNotifierItem item by deriving a settings + * identifier and then associating the role of the given item to the items + * settings identifier. + * It then returns the derived settings identifier. + * In the case, where the settings identifier can't be derived, because the + * application can't be determined, this method throws an error. However it + * then also makes sure that once the app indicators "ready" signal emits, + * this classes "appIndicatorReady" signal emits as well, such that it and + * other methods can be called again to properly handle the item. * @param {string} indicatorContainer - The container of the indicator of the * AppIndicator/KStatusNotifierItem item. * @param {string} role - The role of the AppIndicator/KStatusNotifierItem * item. - * @returns {string} The placeholder role. + * @returns {string} The derived items settings identifier. */ #handleAppIndicatorItem(indicatorContainer, role) { const appIndicator = indicatorContainer.get_child()._indicator; @@ -66,56 +76,75 @@ export default class BoxOrderManager extends GObject.Object { application = "dropbox-client"; } - // Associate the role with the application. - let roles = this.#appIndicatorItemApplicationRoleMap.get(application); + // Derive the items settings identifier from the application name. + const itemSettingsId = `appindicator-kstatusnotifieritem-${application}`; + + // Associate the role with the items settings identifier. + let roles = this.#appIndicatorItemSettingsIdToRolesMap.get(itemSettingsId); if (roles) { - // If the application already has an array of associated roles, just - // add the role to it, if needed. + // If the settings identifier already has an array of associated + // roles, just add the role to it, if needed. if (!roles.includes(role)) { roles.push(role); } } else { // Otherwise create a new array. - this.#appIndicatorItemApplicationRoleMap.set(application, [role]); + this.#appIndicatorItemSettingsIdToRolesMap.set(itemSettingsId, [role]); } - // Return the placeholder. - // A box order containing this placeholder can later be resolved to - // relevant roles using `#resolveAppIndicatorPlaceholders`. - return `appindicator-kstatusnotifieritem-${application}`; + // Return the item settings identifier. + return itemSettingsId; } /** - * Takes a box order and replaces AppIndicator placeholder roles with - * actual roles. - * @param {string[]} - The box order of which to replace placeholder roles. - * @returns {string[]} - A box order with all placeholder roles - * resolved/replaced to/with actual roles. + * Gets a resolved box order for the given top bar box, where all + * AppIndicator items got resolved using their roles, meaning they might be + * present multiple times or not at all depending on the roles stored. + * @param {string} box - The top bar box for which to get the resolved box order. + * Must be one of the following values: + * - "left" + * - "center" + * - "right" + * @returns {ResolvedBoxOrderItem[]} - The resolved box order. */ - #resolveAppIndicatorPlaceholders(boxOrder) { + #getResolvedBoxOrder(box) { + // Get the box order from settings. + let boxOrder = this.#settings.get_strv(`${box}-box-order`); + let resolvedBoxOrder = []; - for (const role of boxOrder) { - // If the role isn't a placeholder, just add it to the resolved box - // order. - if (!role.startsWith("appindicator-kstatusnotifieritem-")) { - resolvedBoxOrder.push(role); + for (const itemSettingsId of boxOrder) { + const resolvedBoxOrderItem = { + settingsId: itemSettingsId, + role: "", + }; + + // If the items settings identifier doesn't indicate that the item + // is an AppIndicator/KStatusNotifierItem item, then its identifier + // is the role and it can just be added to the resolved box order. + if (!itemSettingsId.startsWith("appindicator-kstatusnotifieritem-")) { + resolvedBoxOrderItem.role = resolvedBoxOrderItem.settingsId; + resolvedBoxOrder.push(resolvedBoxOrderItem); continue; } - /// If the role is a placeholder, replace it. - // First get the application this placeholder is associated with. - const application = role.replace("appindicator-kstatusnotifieritem-", ""); + // If the items settings identifier indicates otherwise, then handle + // the item specially. - // Then get the actual roles associated with this application. - let actualRoles = this.#appIndicatorItemApplicationRoleMap.get(application); + // Get the roles roles associated with the items settings id. + let roles = this.#appIndicatorItemSettingsIdToRolesMap.get(resolvedBoxOrderItem.settingsId); - // If there are no actual roles, continue. - if (!actualRoles) { + // If there are no roles associated, continue. + if (!roles) { continue; } - // Otherwise add the actual roles to the resolved box order. - resolvedBoxOrder.push(...actualRoles); + // Otherwise create a new resolved box order item for each role and + // add it to the resolved box order. + for (const role of roles) { + const newResolvedBoxOrderItem = JSON.parse(JSON.stringify(resolvedBoxOrderItem)); + newResolvedBoxOrderItem.role = role; + resolvedBoxOrder.push(newResolvedBoxOrderItem); + } } return resolvedBoxOrder; @@ -136,24 +165,23 @@ export default class BoxOrderManager extends GObject.Object { } /** - * This method returns a valid box order for the given top bar box. - * This means it returns a box order, where only roles are included, which - * have their associated indicator container already in some box of the - * Gnome Shell top bar. + * Gets a valid box order for the given top bar box, where all AppIndicator + * items got resolved and where only items are included, which are in some + * GNOME Shell top bar box. * @param {string} box - The top bar box to return the valid box order for. * Must be one of the following values: * - "left" * - "center" * - "right" - * @returns {string[]} - The valid box order. + * @returns {ResolvedBoxOrderItem[]} - The valid box order. */ - createValidBoxOrder(box) { + getValidBoxOrder(box) { // Get a resolved box order. - let boxOrder = this.#resolveAppIndicatorPlaceholders(this.#settings.get_strv(`${box}-box-order`)); + let resolvedBoxOrder = this.#getResolvedBoxOrder(box); // ToDo: simplify. // Get the indicator containers (of the items) currently present in the - // Gnome Shell top bar. + // GNOME Shell top bar. const indicatorContainers = [ Main.panel._leftBox.get_children(), Main.panel._centerBox.get_children(), @@ -164,16 +192,16 @@ export default class BoxOrderManager extends GObject.Object { // fast easy access. const indicatorContainerSet = new Set(indicatorContainers); - // Go through the box order and only add items to the valid box order, - // where their indicator is present in the Gnome Shell top bar - // currently. + // Go through the resolved box order and only add items to the valid box + // order, where their indicator is currently present in the GNOME Shell + // top bar. let validBoxOrder = []; - for (const role of boxOrder) { - // Get the indicator container associated with the current role. - const associatedIndicatorContainer = Main.panel.statusArea[role]?.container; + for (const item of resolvedBoxOrder) { + // Get the indicator container associated with the items role. + const associatedIndicatorContainer = Main.panel.statusArea[item.role]?.container; if (indicatorContainerSet.has(associatedIndicatorContainer)) { - validBoxOrder.push(role); + validBoxOrder.push(item); } } @@ -181,13 +209,13 @@ export default class BoxOrderManager extends GObject.Object { } /** - * This method saves all new items currently present in the Gnome Shell top - * bar to the correct box orders. + * This method saves all new items currently present in the GNOME Shell top + * bar to the settings. */ saveNewTopBarItems() { // Only run, when the session mode is "user" or the parent session mode // is "user". - if(Main.sessionMode.currentMode !== "user" && Main.sessionMode.parentMode !== "user") { + if (Main.sessionMode.currentMode !== "user" && Main.sessionMode.parentMode !== "user") { return; } @@ -198,7 +226,7 @@ export default class BoxOrderManager extends GObject.Object { right: this.#settings.get_strv("right-box-order"), }; - // Get roles (of items) currently present in the Gnome Shell top bar and + // Get roles (of items) currently present in the GNOME Shell top bar and // index them using their associated indicator container. let indicatorContainerRoleMap = new Map(); for (const role in Main.panel.statusArea) { @@ -206,7 +234,7 @@ export default class BoxOrderManager extends GObject.Object { } // Get the indicator containers (of the items) currently present in the - // Gnome Shell top bar boxes. + // GNOME Shell top bar boxes. const boxIndicatorContainers = { left: Main.panel._leftBox.get_children(), center: Main.panel._centerBox.get_children(), @@ -216,8 +244,8 @@ export default class BoxOrderManager extends GObject.Object { }; // This function goes through the indicator containers of the given box - // and adds roles of new items to the box order. - const addNewItemsToBoxOrder = (indicatorContainers, boxOrder, box) => { + // and adds new item settings identifiers to the given box order. + const addNewItemSettingsIdsToBoxOrder = (indicatorContainers, boxOrder, box) => { for (const indicatorContainer of indicatorContainers) { // First get the role associated with the current indicator // container. @@ -226,36 +254,43 @@ export default class BoxOrderManager extends GObject.Object { continue; } - // Handle an AppIndicator/KStatusNotifierItem item differently. + // Then get a settings identifier for the item. + let itemSettingsId; + // If the role indicates that the item is an + // AppIndicator/KStatusNotifierItem item, then handle it + // differently if (role.startsWith("appindicator-")) { try { - role = this.#handleAppIndicatorItem(indicatorContainer, role); + itemSettingsId = this.#handleAppIndicatorItem(indicatorContainer, role); } catch (e) { if (e.message !== "Application can't be determined.") { throw(e); } continue; } + } else { // Otherwise just use the role as the settings identifier. + itemSettingsId = role; } - // Add the role to the box order, if it isn't in in one already. - if (!boxOrders.left.includes(role) - && !boxOrders.center.includes(role) - && !boxOrders.right.includes(role)) { + // Add the items settings identifier to the box order, if it + // isn't in in one already. + if (!boxOrders.left.includes(itemSettingsId) + && !boxOrders.center.includes(itemSettingsId) + && !boxOrders.right.includes(itemSettingsId)) { if (box === "right") { // Add the items to the beginning for this array, since // its RTL. - boxOrder.unshift(role); + boxOrder.unshift(itemSettingsId); } else { - boxOrder.push(role); + boxOrder.push(itemSettingsId); } } } }; - addNewItemsToBoxOrder(boxIndicatorContainers.left, boxOrders.left, "left"); - addNewItemsToBoxOrder(boxIndicatorContainers.center, boxOrders.center, "center"); - addNewItemsToBoxOrder(boxIndicatorContainers.right, boxOrders.right, "right"); + addNewItemSettingsIdsToBoxOrder(boxIndicatorContainers.left, boxOrders.left, "left"); + addNewItemSettingsIdsToBoxOrder(boxIndicatorContainers.center, boxOrders.center, "center"); + addNewItemSettingsIdsToBoxOrder(boxIndicatorContainers.right, boxOrders.right, "right"); // This function saves the given box order to settings. const saveBoxOrderToSettings = (boxOrder, box) => { From 80f394d2d468def69a8c1c9551275c3742eb5f62 Mon Sep 17 00:00:00 2001 From: June Date: Thu, 26 Sep 2024 02:52:15 +0200 Subject: [PATCH 06/25] refactor: move logic for loading and saving box orders to priv. methods Move logic for loading and saving box orders from/to settings to private methods to have more readable code. --- src/extensionModules/BoxOrderManager.js | 62 +++++++++++++++++-------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/src/extensionModules/BoxOrderManager.js b/src/extensionModules/BoxOrderManager.js index 90ad2a9..5febb5f 100644 --- a/src/extensionModules/BoxOrderManager.js +++ b/src/extensionModules/BoxOrderManager.js @@ -40,6 +40,41 @@ export default class BoxOrderManager extends GObject.Object { this.#settings = settings; } + /** + * Gets a box order for the given top bar box from settings. + * @param {string} box - The top bar box for which to get the box order. + * Must be one of the following values: + * - "left" + * - "center" + * - "right" + * @returns {string[]} - The box order consisting of an array of item + * settings identifiers. + */ + #getBoxOrder(box) { + return this.#settings.get_strv(`${box}-box-order`); + } + + /** + * Save the given box order to settings, making sure to only save a changed + * box order, to avoid loops when listening on settings changes. + * @param {string} box - The top bar box for which to save the box order. + * Must be one of the following values: + * - "left" + * - "center" + * - "right" + * @param {string[]} boxOrder - The box order to save. Must be an array of + * item settings identifiers. + */ + #saveBoxOrder(box, boxOrder) { + const currentBoxOrder = this.#getBoxOrder(box); + + // Only save the given box order to settings, if it is different, to + // avoid loops when listening on settings changes. + if (JSON.stringify(boxOrder) !== JSON.stringify(currentBoxOrder)) { + this.#settings.set_strv(`${box}-box-order`, boxOrder); + } + } + /** * Handles an AppIndicator/KStatusNotifierItem item by deriving a settings * identifier and then associating the role of the given item to the items @@ -108,8 +143,7 @@ export default class BoxOrderManager extends GObject.Object { * @returns {ResolvedBoxOrderItem[]} - The resolved box order. */ #getResolvedBoxOrder(box) { - // Get the box order from settings. - let boxOrder = this.#settings.get_strv(`${box}-box-order`); + let boxOrder = this.#getBoxOrder(box); let resolvedBoxOrder = []; for (const itemSettingsId of boxOrder) { @@ -219,11 +253,11 @@ export default class BoxOrderManager extends GObject.Object { return; } - // Load the configured box orders from settings. + // Get the box orders. const boxOrders = { - left: this.#settings.get_strv("left-box-order"), - center: this.#settings.get_strv("center-box-order"), - right: this.#settings.get_strv("right-box-order"), + left: this.#getBoxOrder("left"), + center: this.#getBoxOrder("center"), + right: this.#getBoxOrder("right"), }; // Get roles (of items) currently present in the GNOME Shell top bar and @@ -292,18 +326,8 @@ export default class BoxOrderManager extends GObject.Object { addNewItemSettingsIdsToBoxOrder(boxIndicatorContainers.center, boxOrders.center, "center"); addNewItemSettingsIdsToBoxOrder(boxIndicatorContainers.right, boxOrders.right, "right"); - // This function saves the given box order to settings. - const saveBoxOrderToSettings = (boxOrder, box) => { - const currentBoxOrder = this.#settings.get_strv(`${box}-box-order`); - // Only save the updated box order to settings, if it is different, - // to avoid loops, when listening on settings changes. - if (JSON.stringify(currentBoxOrder) !== JSON.stringify(boxOrder)) { - this.#settings.set_strv(`${box}-box-order`, boxOrder); - } - }; - - saveBoxOrderToSettings(boxOrders.left, "left"); - saveBoxOrderToSettings(boxOrders.center, "center"); - saveBoxOrderToSettings(boxOrders.right, "right"); + this.#saveBoxOrder("left", boxOrders.left); + this.#saveBoxOrder("center", boxOrders.center); + this.#saveBoxOrder("right", boxOrders.right); } } From f619ce4fa7078d5c3332361e898c9e9767021950 Mon Sep 17 00:00:00 2001 From: June Date: Fri, 27 Sep 2024 03:24:47 +0200 Subject: [PATCH 07/25] feature: make it possible to (forcefully) hide or show top bar items This is only the core extension logic for now, settings UI still needs to follow. The logic only acts on the indicator container, not the indicator itself, meaning that e.g. a screen recording indicator, which is hidden on the indicator level, can be forcefully hidden, but not forcefully shown. Because of that and because forcefully hiding it breaks controls for screen recording, a potential settings implementation should exclude visiblity controls for some elements like e.g. the screen recording indicator. --- ...l.extensions.top-bar-organizer.gschema.xml | 8 +++++ src/extension.js | 30 ++++++++++++------- src/extensionModules/BoxOrderManager.js | 20 ++++++++++++- 3 files changed, 47 insertions(+), 11 deletions(-) diff --git a/data/org.gnome.shell.extensions.top-bar-organizer.gschema.xml b/data/org.gnome.shell.extensions.top-bar-organizer.gschema.xml index 24e17fe..8b143e7 100644 --- a/data/org.gnome.shell.extensions.top-bar-organizer.gschema.xml +++ b/data/org.gnome.shell.extensions.top-bar-organizer.gschema.xml @@ -13,5 +13,13 @@ Order of items in the right box of the top bar. [] + + Top bar items to (forcefully) hide. + [] + + + Top bar items to (forcefully) show. + [] + diff --git a/src/extension.js b/src/extension.js index 94778e9..353cfe9 100644 --- a/src/extension.js +++ b/src/extension.js @@ -24,17 +24,19 @@ export default class TopBarOrganizerExtension extends Extension { this.#handleNewItemsAndOrderTopBar(); }); - // Handle changes of configured box orders. + // Handle changes of settings. this._settingsHandlerIds = []; - const addConfiguredBoxOrderChangeHandler = (box) => { - let handlerId = this._settings.connect(`changed::${box}-box-order`, () => { + const addSettingsChangeHandler = (settingsName) => { + const handlerId = this._settings.connect(`changed::${settingsName}`, () => { this.#handleNewItemsAndOrderTopBar(); }); this._settingsHandlerIds.push(handlerId); }; - addConfiguredBoxOrderChangeHandler("left"); - addConfiguredBoxOrderChangeHandler("center"); - addConfiguredBoxOrderChangeHandler("right"); + addSettingsChangeHandler("left-box-order"); + addSettingsChangeHandler("center-box-order"); + addSettingsChangeHandler("right-box-order"); + addSettingsChangeHandler("hide"); + addSettingsChangeHandler("show"); } disable() { @@ -115,9 +117,9 @@ export default class TopBarOrganizerExtension extends Extension { /// Go through the items of the validBoxOrder and order the GNOME Shell /// top bar box accordingly. for (let i = 0; i < validBoxOrder.length; i++) { - const role = validBoxOrder[i].role; + const item = validBoxOrder[i]; // Get the indicator container associated with the current role. - const associatedIndicatorContainer = Main.panel.statusArea[role].container; + const associatedIndicatorContainer = Main.panel.statusArea[item.role].container; // Save whether or not the indicator container is visible. const isVisible = associatedIndicatorContainer.visible; @@ -141,8 +143,16 @@ export default class TopBarOrganizerExtension extends Extension { panelBox.insert_child_at_index(associatedIndicatorContainer, i); } - // Hide the indicator container again, if it wasn't visible. - if (!isVisible) { + // Hide the indicator container... + // - ...if it wasn't visible before and the hide property of the + // item is "default". + // - if the hide property of the item is "hide". + // In all other cases have the item show. + // An e.g. screen recording indicator still wouldn't show tho, since + // this here acts on the indicator container, but a screen recording + // indicator is hidden on the indicator level. + if ((!isVisible && item.hide === "default") || + item.hide === "hide") { associatedIndicatorContainer.hide(); } } diff --git a/src/extensionModules/BoxOrderManager.js b/src/extensionModules/BoxOrderManager.js index 5febb5f..bb27391 100644 --- a/src/extensionModules/BoxOrderManager.js +++ b/src/extensionModules/BoxOrderManager.js @@ -5,10 +5,13 @@ import GObject from "gi://GObject"; import * as Main from "resource:///org/gnome/shell/ui/main.js"; /** - * A resolved box order item containing the items role and settings identifier. + * A resolved box order item containing the items role, settings identifier and + * additional information. * @typedef {Object} ResolvedBoxOrderItem * @property {string} settingsId - The settings identifier of the item. * @property {string} role - The role of the item. + * @property {string} hide - Whether the item should be (forcefully) hidden + * (hide), shown (show) or just be left as is (default). */ /** @@ -135,6 +138,7 @@ export default class BoxOrderManager extends GObject.Object { * Gets a resolved box order for the given top bar box, where all * AppIndicator items got resolved using their roles, meaning they might be * present multiple times or not at all depending on the roles stored. + * The items of the box order also have additional information stored. * @param {string} box - The top bar box for which to get the resolved box order. * Must be one of the following values: * - "left" @@ -145,13 +149,26 @@ export default class BoxOrderManager extends GObject.Object { #getResolvedBoxOrder(box) { let boxOrder = this.#getBoxOrder(box); + const itemsToHide = this.#settings.get_strv("hide"); + const itemsToShow = this.#settings.get_strv("show"); + let resolvedBoxOrder = []; for (const itemSettingsId of boxOrder) { const resolvedBoxOrderItem = { settingsId: itemSettingsId, role: "", + hide: "", }; + // Set the hide state of the item. + if (itemsToHide.includes(resolvedBoxOrderItem.settingsId)) { + resolvedBoxOrderItem.hide = "hide"; + } else if (itemsToShow.includes(resolvedBoxOrderItem.settingsId)) { + resolvedBoxOrderItem.hide = "show"; + } else { + resolvedBoxOrderItem.hide = "default"; + } + // If the items settings identifier doesn't indicate that the item // is an AppIndicator/KStatusNotifierItem item, then its identifier // is the role and it can just be added to the resolved box order. @@ -202,6 +219,7 @@ export default class BoxOrderManager extends GObject.Object { * Gets a valid box order for the given top bar box, where all AppIndicator * items got resolved and where only items are included, which are in some * GNOME Shell top bar box. + * The items of the box order also have additional information stored. * @param {string} box - The top bar box to return the valid box order for. * Must be one of the following values: * - "left" From 57e7fc51ea2ac0ab65829cc95166dce29573e28d Mon Sep 17 00:00:00 2001 From: June Date: Sun, 8 Jun 2025 20:57:58 +0200 Subject: [PATCH 08/25] docs: add newer, cut down and commented panel.js from GNOME Shell 48.2 --- docs/panel_48.2_2025-06-08.js | 322 ++++++++++++++++++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100644 docs/panel_48.2_2025-06-08.js diff --git a/docs/panel_48.2_2025-06-08.js b/docs/panel_48.2_2025-06-08.js new file mode 100644 index 0000000..7becfb8 --- /dev/null +++ b/docs/panel_48.2_2025-06-08.js @@ -0,0 +1,322 @@ +// My annotated and cut down js/ui/panel.js from gnome-shell/48.2. +// All annotations are what I guessed, interpreted and copied while reading the +// code and comparing to other panel.js versions and might be wrong. They are +// prefixed with "Annotation:" to indicate that they're my comments, not +// comments that orginally existed. + +// Taken from: https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/48.2/js/ui/panel.js +// On: 2025-06-08 +// License: This code is licensed under GPLv2. + +// Taken from: https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/48.2/js/ui/sessionMode.js +// On: 2025-06-08 +// License: This code is licensed under GPLv2. + +// I'm using the word "item" to refer to the thing, which gets added to the top +// (menu)bar / panel, where an item has a role/name and an indicator. + +// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this +// Extension. + +import Clutter from 'gi://Clutter'; + +// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this +// Extension. + +import GObject from 'gi://GObject'; + +// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this +// Extension. + +import St from 'gi://St'; + +// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this +// Extension. + +import * as CtrlAltTab from './ctrlAltTab.js'; + +// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this +// Extension. + +import * as PopupMenu from './popupMenu.js'; +import * as PanelMenu from './panelMenu.js'; + +// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this +// Extension. + +import * as Main from './main.js'; + +// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this +// Extension. + +import {DateMenuButton} from './dateMenu.js'; +import {ATIndicator} from './status/accessibility.js'; +import {InputSourceIndicator} from './status/keyboard.js'; +import {DwellClickIndicator} from './status/dwellClick.js'; +import {ScreenRecordingIndicator, ScreenSharingIndicator} from './status/remoteAccess.js'; + +// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this +// Extension. + +// Of note (for PANEL_ITEM_IMPLEMENTATIONS): +// const AppMenuButton = [...] +// const ActivitiesButton = [...] +// const QuickSettings = [...] + +const PANEL_ITEM_IMPLEMENTATIONS = { + 'activities': ActivitiesButton, + 'appMenu': AppMenuButton, + 'quickSettings': QuickSettings, + 'dateMenu': DateMenuButton, + 'a11y': ATIndicator, + 'keyboard': InputSourceIndicator, + 'dwellClick': DwellClickIndicator, + 'screenRecording': ScreenRecordingIndicator, + 'screenSharing': ScreenSharingIndicator, +}; + +export const Panel = GObject.registerClass( +class Panel extends St.Widget { + // Annotation: Initializes the top (menu)bar / panel. + // Does relevant stuff like: + // - Defining this._leftBox, this._centerBox and this._rightBox. + // - Finally calling this._updatePanel(). + // Compared to panel_47.rc_2024-09-12.js: connectObject instead of connect + // gets used, which shouldn't be relevant for this extension. + _init() { + super._init({ + name: 'panel', + reactive: true, + }); + + this.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS); + + this._sessionStyle = null; + + this.statusArea = {}; + + this.menuManager = new PopupMenu.PopupMenuManager(this); + + this._leftBox = new St.BoxLayout({name: 'panelLeft'}); + this.add_child(this._leftBox); + this._centerBox = new St.BoxLayout({name: 'panelCenter'}); + this.add_child(this._centerBox); + this._rightBox = new St.BoxLayout({name: 'panelRight'}); + this.add_child(this._rightBox); + + this.connect('button-press-event', this._onButtonPress.bind(this)); + this.connect('touch-event', this._onTouchEvent.bind(this)); + + Main.overview.connectObject('showing', + () => this.add_style_pseudo_class('overview'), + this); + Main.overview.connectObject('hiding', + () => this.remove_style_pseudo_class('overview'), + this); + + Main.layoutManager.panelBox.add_child(this); + Main.ctrlAltTabManager.addGroup(this, + _('Top Bar'), 'shell-focus-top-bar-symbolic', + {sortGroup: CtrlAltTab.SortGroup.TOP}); + + Main.sessionMode.connectObject('updated', + this._updatePanel.bind(this), + this); + + global.display.connectObject('workareas-changed', + () => this.queue_relayout(), + this); + this._updatePanel(); + } + + // Annotation: [...] Cut out bunch of stuff here, which isn't relevant for + // this Extension. + + // Annotation: Gets called by this._init() to populate the top (menu)bar / + // panel initially. + // + // It does the following relevant stuff: + // - Calls this._hideIndicators() + // - Calls this._updateBox() for this._leftBox, this._centerBox and + // this._rightBox with panel.left, panel.center and panel.right to + // populate the boxes with items defined in panel.left, panel.center and + // panel.right. + // + // panel.left, panel.center and panel.right get set via the line let panel + // = Main.sessionMode.panel, which uses the panel of Mains (js/ui/main.js) + // instance of SessionMode (js/ui/sessionMode.js). + // + // And in js/ui/sessionMode.js (48.2, 2025-06-08) you have different modes + // with different panel configuration. For example the "user" mode with: + // panel: { + // left: ['activities'], + // center: ['dateMenu'], + // right: ['screenRecording', 'screenSharing', 'dwellClick', 'a11y', 'keyboard', 'quickSettings'], + // } + // + // This way this function populates the top (menu)bar / panel with the + // default stuff you see on a fresh Gnome. + // + // Compared to panel_47.rc_2024-09-12.js: Nothing changed. + _updatePanel() { + let panel = Main.sessionMode.panel; + this._hideIndicators(); + this._updateBox(panel.left, this._leftBox); + this._updateBox(panel.center, this._centerBox); + this._updateBox(panel.right, this._rightBox); + + if (panel.left.includes('dateMenu')) + Main.messageTray.bannerAlignment = Clutter.ActorAlign.START; + else if (panel.right.includes('dateMenu')) + Main.messageTray.bannerAlignment = Clutter.ActorAlign.END; + // Default to center if there is no dateMenu + else + Main.messageTray.bannerAlignment = Clutter.ActorAlign.CENTER; + + if (this._sessionStyle) + this.remove_style_class_name(this._sessionStyle); + + this._sessionStyle = Main.sessionMode.panelStyle; + if (this._sessionStyle) + this.add_style_class_name(this._sessionStyle); + } + + // Annotation: This function hides all items, which are in the top (menu)bar + // panel and in PANEL_ITEM_IMPLEMENTATIONS. + // + // Compared to panel_47.rc_2024-09-12.js: Nothing changed. + _hideIndicators() { + for (let role in PANEL_ITEM_IMPLEMENTATIONS) { + let indicator = this.statusArea[role]; + if (!indicator) + continue; + indicator.container.hide(); + } + } + + // Annotation: This function takes a role (of an item) and returns a + // corresponding indicator, if either of two things are true: + // - The indicator is already in this.statusArea. + // Then it just returns the indicator by using this.statusArea. + // - The role is in PANEL_ITEM_IMPLEMENTATIONS. + // Then it creates a new indicator, adds it to this.statusArea and returns + // it. + // + // Compared to panel_47.rc_2024-09-12.js: Nothing changed. + _ensureIndicator(role) { + let indicator = this.statusArea[role]; + if (!indicator) { + let constructor = PANEL_ITEM_IMPLEMENTATIONS[role]; + if (!constructor) { + // This icon is not implemented (this is a bug) + return null; + } + indicator = new constructor(this); + this.statusArea[role] = indicator; + } + return indicator; + } + + // Annotation: This function takes a list of items (or rather their roles) + // and adds the indicators of those items to a box (like this._leftBox) + // using this._ensureIndicator() to get the indicator corresponding to the + // given role. + // So only items with roles this._ensureIndicator() knows, get added. + // + // Compared to panel_47.rc_2024-09-12.js: Nothing changed. + _updateBox(elements, box) { + let nChildren = box.get_n_children(); + + for (let i = 0; i < elements.length; i++) { + let role = elements[i]; + let indicator = this._ensureIndicator(role); + if (indicator == null) + continue; + + this._addToPanelBox(role, indicator, i + nChildren, box); + } + } + + // Annotation: This function adds the given item to the specified top + // (menu)bar / panel box and connects to "destroy" and "menu-set" events. + // + // It takes the following arguments: + // - role: The name of the item to add. + // - indicator: The indicator of the item to add. + // - position: Where in the box to add the item. + // - box: The box to add the item to. + // Can be one of the following: + // - this._leftBox + // - this._centerBox + // - this._rightBox + // + // Compared to panel_47.rc_2024-09-12.js: Nothing changed. + _addToPanelBox(role, indicator, position, box) { + let container = indicator.container; + container.show(); + + let parent = container.get_parent(); + if (parent) + parent.remove_child(container); + + + box.insert_child_at_index(container, position); + this.statusArea[role] = indicator; + let destroyId = indicator.connect('destroy', emitter => { + delete this.statusArea[role]; + emitter.disconnect(destroyId); + }); + indicator.connect('menu-set', this._onMenuSet.bind(this)); + this._onMenuSet(indicator); + } + + // Annotation: This function allows you to add an item to the top (menu)bar + // / panel. + // While per default it adds the item to the status area (the right box of + // the top bar), you can specify the box and add the item to any of the + // three boxes of the top bar. + // To add an item to the top bar, you need to give its role and indicator. + // + // This function takes the following arguments: + // - role: A name for the item to add. + // - indicator: The indicator for the item to add (must be an instance of + // PanelMenu.Button). + // - position: Where in the box to add the item. + // - box: The box to add the item to. + // Can be one of the following: + // - "left": referring to this._leftBox + // - "center": referring to this._centerBox + // - "right": referring to this._rightBox + // These boxes are what you see in the top bar as the left, right and + // center sections. + // + // Finally this function just calls this._addToPanelBox() for the actual + // work, so it basically just makes sure the input to this._addToPanelBox() + // is correct. + // + // Compared to panel_47.rc_2024-09-12.js: Nothing changed. + addToStatusArea(role, indicator, position, box) { + if (this.statusArea[role]) + throw new Error(`Extension point conflict: there is already a status indicator for role ${role}`); + + if (!(indicator instanceof PanelMenu.Button)) + throw new TypeError('Status indicator must be an instance of PanelMenu.Button'); + + position ??= 0; + let boxes = { + left: this._leftBox, + center: this._centerBox, + right: this._rightBox, + }; + let boxContainer = boxes[box] || this._rightBox; + this.statusArea[role] = indicator; + this._addToPanelBox(role, indicator, position, boxContainer); + return indicator; + } + + // Of note: + // _onMenuSet(indicator) { [...] } + + // Annotation: [...] Cut out bunch of stuff here, which isn't relevant for + // this Extension. +}); From 9c847c0988108bf61256ab61fc920f70ad6c174d Mon Sep 17 00:00:00 2001 From: June Date: Mon, 9 Jun 2025 00:22:48 +0200 Subject: [PATCH 09/25] feature: support GNOME Shell version 48 Checked the source of 48.2 and tested in Fedora 42 and there don't seem to be any changes relevant to the functionality of this extension. --- src/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata.json b/src/metadata.json index e7a2fbf..81e132c 100644 --- a/src/metadata.json +++ b/src/metadata.json @@ -3,7 +3,7 @@ "name": "Top Bar Organizer", "description": "Organize the items of the top (menu)bar.", "version": 12, - "shell-version": [ "45", "46", "47" ], + "shell-version": [ "45", "46", "47", "48" ], "settings-schema": "org.gnome.shell.extensions.top-bar-organizer", "url": "https://gitlab.gnome.org/june/top-bar-organizer" } From b17a805035aa860f7b00a998a9b196d7e1ab895e Mon Sep 17 00:00:00 2001 From: June Date: Mon, 9 Jun 2025 00:24:02 +0200 Subject: [PATCH 10/25] other: bump version to 13 --- src/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata.json b/src/metadata.json index 81e132c..a12d4c0 100644 --- a/src/metadata.json +++ b/src/metadata.json @@ -2,7 +2,7 @@ "uuid": "top-bar-organizer@julian.gse.jsts.xyz", "name": "Top Bar Organizer", "description": "Organize the items of the top (menu)bar.", - "version": 12, + "version": 13, "shell-version": [ "45", "46", "47", "48" ], "settings-schema": "org.gnome.shell.extensions.top-bar-organizer", "url": "https://gitlab.gnome.org/june/top-bar-organizer" From a58ddc6146d082e95e366f58b7a796db480250a5 Mon Sep 17 00:00:00 2001 From: June Date: Mon, 9 Jun 2025 18:33:10 +0200 Subject: [PATCH 11/25] other: add GNOME 48.2 panel source code file to .eslintignore --- .eslintignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintignore b/.eslintignore index 2b884b5..524273b 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,3 +4,4 @@ /docs/panel_45.0_2023-09-26.js /docs/panel_46.4_2024-09-11.js /docs/panel_47.rc_2024-09-12.js +/docs/panel_48.2_2025-06-08.js From 185a48c857aeadaca70bd66a80180459f821ac9c Mon Sep 17 00:00:00 2001 From: June Date: Mon, 9 Jun 2025 19:53:12 +0200 Subject: [PATCH 12/25] fix: use row title to make settings window not break for long item names Use the title of the PrefsBoxOrderItemRow (AdwActionRow) for the item name instead of a label in the prefix. Aside from generally being more correct, item names now wrap correctly, avoiding the settings window breaking (being cut off by default to the right with even the close button not showing, until resizing) with long item names. --- data/ui/prefs-box-order-item-row.ui | 6 ------ src/prefsModules/PrefsBoxOrderItemRow.js | 11 ++++------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/data/ui/prefs-box-order-item-row.ui b/data/ui/prefs-box-order-item-row.ui index 239ac55..0debab6 100644 --- a/data/ui/prefs-box-order-item-row.ui +++ b/data/ui/prefs-box-order-item-row.ui @@ -1,12 +1,6 @@