From 0d51b810415b1183b25bbfa1832a9cea79c64dde Mon Sep 17 00:00:00 2001 From: June Date: Thu, 12 Jun 2025 03:24:18 +0200 Subject: [PATCH 1/8] refactor: more nicely get the data for panelBox in #orderTopBarItems --- src/extension.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 4d99243..026de7e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -121,18 +121,7 @@ export default class TopBarOrganizerExtension extends Extension { const validBoxOrder = this._boxOrderManager.getValidBoxOrder(box); // Get the relevant box of `Main.panel`. - let panelBox; - switch (box) { - case "left": - panelBox = (Main.panel as CustomPanel)._leftBox; - break; - case "center": - panelBox = (Main.panel as CustomPanel)._centerBox; - break; - case "right": - panelBox = (Main.panel as CustomPanel)._rightBox; - break; - } + let panelBox = (Main.panel as CustomPanel)[`_${box}Box`]; /// Go through the items of the validBoxOrder and order the GNOME Shell /// top bar box accordingly. From fdbacdd6837b9263bb3f472a43f891514afa14f0 Mon Sep 17 00:00:00 2001 From: June Date: Tue, 8 Jul 2025 02:55:57 +0200 Subject: [PATCH 2/8] feature: add settings UI for control. how to affect an items visibility Add settings UI for controlling how the extension should affect a top bar items visibility (whether to try to forcefully hide or show an item or not affect its visibility at all). --- .../ui/prefs-box-order-item-options-dialog.ui | 38 ++++++++++++ data/ui/prefs-box-order-item-row.ui | 6 ++ .../PrefsBoxOrderItemOptionsDialog.ts | 58 +++++++++++++++++++ src/prefsModules/PrefsBoxOrderItemRow.ts | 10 ++++ 4 files changed, 112 insertions(+) create mode 100644 data/ui/prefs-box-order-item-options-dialog.ui create mode 100644 src/prefsModules/PrefsBoxOrderItemOptionsDialog.ts diff --git a/data/ui/prefs-box-order-item-options-dialog.ui b/data/ui/prefs-box-order-item-options-dialog.ui new file mode 100644 index 0000000..d953d24 --- /dev/null +++ b/data/ui/prefs-box-order-item-options-dialog.ui @@ -0,0 +1,38 @@ + + + + diff --git a/data/ui/prefs-box-order-item-row.ui b/data/ui/prefs-box-order-item-row.ui index 0debab6..dd06b32 100644 --- a/data/ui/prefs-box-order-item-row.ui +++ b/data/ui/prefs-box-order-item-row.ui @@ -46,6 +46,12 @@ row.move-down +
+ + Options + row.options + +
Forget diff --git a/src/prefsModules/PrefsBoxOrderItemOptionsDialog.ts b/src/prefsModules/PrefsBoxOrderItemOptionsDialog.ts new file mode 100644 index 0000000..e3d87d6 --- /dev/null +++ b/src/prefsModules/PrefsBoxOrderItemOptionsDialog.ts @@ -0,0 +1,58 @@ +"use strict"; + +import GObject from "gi://GObject"; +import Adw from "gi://Adw"; +import GLib from "gi://GLib"; +import type Gio from "gi://Gio"; +import type Gtk from "gi://Gtk"; + +import { ExtensionPreferences } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js"; + +export default class PrefsBoxOrderItemOptionsDialog extends Adw.Dialog { + static { + GObject.registerClass({ + GTypeName: "PrefsBoxOrderItemOptionsDialog", + Template: GLib.uri_resolve_relative(import.meta.url, "../ui/prefs-box-order-item-options-dialog.ui", GLib.UriFlags.NONE), + InternalChildren: [ + "visibility-row", + ], + }, this); + } + + declare _visibility_row: Adw.ComboRow; + #settings: Gio.Settings; + item: string; + + constructor(params = {}, item: string) { + super(params); + + // Associate `this` with an item. + this.item = item; + // Load the settings. + this.#settings = ExtensionPreferences.lookupByURL(import.meta.url)!.getSettings(); + } + + onVisibilityRowSelectionChanged(): void { + const visibility = (this._visibility_row.get_selected_item() as Gtk.StringObject).get_string(); + const itemsToHide = new Set(this.#settings.get_strv("hide")); + const itemsToShow = new Set(this.#settings.get_strv("show")); + + switch (visibility) { + case "Forcefully Hide": + itemsToHide.add(this.item) + itemsToShow.delete(this.item); + break; + case "Forcefully Show": + itemsToHide.delete(this.item) + itemsToShow.add(this.item); + break; + case "Default": + itemsToHide.delete(this.item) + itemsToShow.delete(this.item); + break; + } + + this.#settings.set_strv("hide", Array.from(itemsToHide)); + this.#settings.set_strv("show", Array.from(itemsToShow)); + } +} diff --git a/src/prefsModules/PrefsBoxOrderItemRow.ts b/src/prefsModules/PrefsBoxOrderItemRow.ts index fa691f8..3da4d6b 100644 --- a/src/prefsModules/PrefsBoxOrderItemRow.ts +++ b/src/prefsModules/PrefsBoxOrderItemRow.ts @@ -6,6 +6,7 @@ import GObject from "gi://GObject"; import Adw from "gi://Adw"; import GLib from "gi://GLib"; +import PrefsBoxOrderItemOptionsDialog from "./PrefsBoxOrderItemOptionsDialog.js"; import type PrefsBoxOrderListBox from "./PrefsBoxOrderListBox.js"; export default class PrefsBoxOrderItemRow extends Adw.ActionRow { @@ -25,6 +26,15 @@ export default class PrefsBoxOrderItemRow extends Adw.ActionRow { parentListBox.saveBoxOrderToSettings(); parentListBox.determineRowMoveActionEnable(); }); + this.install_action("row.options", null, (self, _actionName, _param) => { + const itemOptionsDialog = new PrefsBoxOrderItemOptionsDialog({ + // Get the title from self as the constructor of + // PrefsBoxOrderItemRow already processes the item name into a + // nice title. + title: (self as PrefsBoxOrderItemRow).get_title() + }, (self as PrefsBoxOrderItemRow).item); + itemOptionsDialog.present(self); + }); this.install_action("row.move-up", null, (self, _actionName, _param) => self.emit("move", "up")); this.install_action("row.move-down", null, (self, _actionName, _param) => self.emit("move", "down")); } From 943d0d1fe7c8f0ebdfadce4b2f7110a5e8836fb6 Mon Sep 17 00:00:00 2001 From: June Date: Wed, 9 Jul 2025 01:23:10 +0200 Subject: [PATCH 3/8] other: add GitLab issue template for reporting a bug Having this template hopefully results in bug reports with more (relevant) information from the get-go. --- .gitlab/issue_templates/Bug.md | 49 ++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .gitlab/issue_templates/Bug.md diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md new file mode 100644 index 0000000..2caef4d --- /dev/null +++ b/.gitlab/issue_templates/Bug.md @@ -0,0 +1,49 @@ +### Bug Description + + + +### Steps to Reproduce + + + +### What behavior did you observe? + + + +### What behavior did you expect? + + + +### Further Information + + + + + +### System Information + +- Operating System: +- GNOME Version: + +Enabled Extensions: + + From bcb61b51ac7cfe24e5903ed18e37b56a1529f933 Mon Sep 17 00:00:00 2001 From: June Date: Wed, 9 Jul 2025 02:09:59 +0200 Subject: [PATCH 4/8] other: bump to version 14 --- src/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata.json b/src/metadata.json index a12d4c0..12e216f 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": 13, + "version": 14, "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 a477d3b95ad3ce42e4d6652405e1ba6beca44b81 Mon Sep 17 00:00:00 2001 From: June Date: Tue, 15 Jul 2025 19:35:48 +0200 Subject: [PATCH 5/8] fix: set selected visibility option to settings value on options open --- src/prefsModules/PrefsBoxOrderItemOptionsDialog.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/prefsModules/PrefsBoxOrderItemOptionsDialog.ts b/src/prefsModules/PrefsBoxOrderItemOptionsDialog.ts index e3d87d6..736a773 100644 --- a/src/prefsModules/PrefsBoxOrderItemOptionsDialog.ts +++ b/src/prefsModules/PrefsBoxOrderItemOptionsDialog.ts @@ -30,6 +30,17 @@ export default class PrefsBoxOrderItemOptionsDialog extends Adw.Dialog { this.item = item; // Load the settings. this.#settings = ExtensionPreferences.lookupByURL(import.meta.url)!.getSettings(); + + // Set the selected visibility row choice to the settings value. + const itemsToHide = new Set(this.#settings.get_strv("hide")); + const itemsToShow = new Set(this.#settings.get_strv("show")); + if (itemsToHide.has(this.item)) { + this._visibility_row.set_selected(1); + } else if (itemsToShow.has(this.item)) { + this._visibility_row.set_selected(2); + } else { + this._visibility_row.set_selected(0); + } } onVisibilityRowSelectionChanged(): void { From 4071b79974f7a76d9e05c8f10915504aca01d004 Mon Sep 17 00:00:00 2001 From: June Date: Fri, 3 Oct 2025 16:36:09 +0200 Subject: [PATCH 6/8] docs: add newer, cut down and commented panel.js from GNOME Shell 49.0 --- docs/panel_49.0_2025-10-03.js | 321 ++++++++++++++++++++++++++++++++++ 1 file changed, 321 insertions(+) create mode 100644 docs/panel_49.0_2025-10-03.js diff --git a/docs/panel_49.0_2025-10-03.js b/docs/panel_49.0_2025-10-03.js new file mode 100644 index 0000000..449c038 --- /dev/null +++ b/docs/panel_49.0_2025-10-03.js @@ -0,0 +1,321 @@ +// My annotated and cut down js/ui/panel.js from gnome-shell/49.0. +// 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/49.0/js/ui/panel.js +// On: 2025-10-03 +// License: This code is licensed under GPLv2. + +// Taken from: https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/49.0/js/ui/sessionMode.js +// On: 2025-10-03 +// 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 ActivitiesButton = [...] +// const QuickSettings = [...] +// Compared to panel_48.2_2025-06-08.js: AppMenuButton got removed. + +// Compared to panel_48.2_2025-06-08.js: AppMenuButton got removed. +const PANEL_ITEM_IMPLEMENTATIONS = { + 'activities': ActivitiesButton, + '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_48.2_2025-06-08.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.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 (49.0, 2025-10-03) 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_48.2_2025-06-08.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_48.2_2025-06-08.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_48.2_2025-06-08.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_48.2_2025-06-08.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_48.2_2025-06-08.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_48.2_2025-06-08.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 a92ef520340b09ad3cb38421c9ed0d2fb7d27281 Mon Sep 17 00:00:00 2001 From: June Date: Fri, 3 Oct 2025 17:22:03 +0200 Subject: [PATCH 7/8] feature: support GNOME Shell version 49 Checked the source of 49.0 and tested in Fedora 43 Beta 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 12e216f..4256a6b 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": 14, - "shell-version": [ "45", "46", "47", "48" ], + "shell-version": [ "45", "46", "47", "48", "49" ], "settings-schema": "org.gnome.shell.extensions.top-bar-organizer", "url": "https://gitlab.gnome.org/june/top-bar-organizer" } From 140c635081c7a67eb15eed8c2406dd04052a5c00 Mon Sep 17 00:00:00 2001 From: June Date: Fri, 3 Oct 2025 17:23:05 +0200 Subject: [PATCH 8/8] other: bump to version 15 --- src/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata.json b/src/metadata.json index 4256a6b..11b0cce 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": 14, + "version": 15, "shell-version": [ "45", "46", "47", "48", "49" ], "settings-schema": "org.gnome.shell.extensions.top-bar-organizer", "url": "https://gitlab.gnome.org/june/top-bar-organizer"