From 316be3851fd9a6392ea6d7a46790784c176b09ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20T?= Date: Mon, 9 Nov 2026 10:09:51 +0100 Subject: [PATCH 1/2] fix: treat legacy tray icons like other items The AppIndicator extension also adds legacy (XEmbed) tray icons, with roles like "appindicator-legacy::". They have no `_indicator`, so `#handleAppIndicatorItem()` threw "TypeError: appIndicator is undefined". As this runs inside the overwritten `Panel._addToPanelBox`, the error broke whichever extension was adding a top-bar item at the time, or Top Bar Organizer's own `enable()`. Only take the AppIndicator path when there is an `_indicator`. Patch by yuri in #31. Fixes #31 --- src/extensionModules/BoxOrderManager.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/extensionModules/BoxOrderManager.ts b/src/extensionModules/BoxOrderManager.ts index cfb4dbe..664d590 100644 --- a/src/extensionModules/BoxOrderManager.ts +++ b/src/extensionModules/BoxOrderManager.ts @@ -338,10 +338,14 @@ export default class BoxOrderManager extends GObject.Object { // Then get a settings identifier for the item. let itemSettingsId; - if (role.startsWith("appindicator-")) { + if (role.startsWith("appindicator-") && + (indicatorContainer.get_child() as any)?._indicator) { // If the role indicates that the item is an // AppIndicator/KStatusNotifierItem item, then handle it // differently. + // Legacy (XEmbed) tray icons added by the same extension + // also get an "appindicator-" role, but have no + // _indicator, so they're treated like any other item. try { itemSettingsId = this.#handleAppIndicatorItem(indicatorContainer, role); } catch (e) { From 6802c2f0f333e537f036ed46cb4ba3a9dcae1c3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20T?= Date: Mon, 9 Nov 2026 09:52:24 +0100 Subject: [PATCH 2/2] fix: don't wrap Panel._addToPanelBox twice If `enable()` throws after `Panel._addToPanelBox` is overwritten, the extension ends up in the error state and GNOME Shell never calls `disable()`. Extensions are disabled and enabled on every screen lock, so the next `enable()` saved the overwrite itself as `_originalAddToPanelBox`. From then on the method called itself until the stack overflowed, and the top bar came up empty after unlocking. Only save the original method when it isn't saved yet, and make `disable()` safe to call when `enable()` didn't finish. --- src/extension.ts | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 026de7e..1533662 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -54,18 +54,21 @@ export default class TopBarOrganizerExtension extends Extension { } disable(): void { - // Revert the overwrite of `Panel._addToPanelBox`. + // Revert the overwrite of `Panel._addToPanelBox`, if there is one. // @ts-ignore - Panel.Panel.prototype._addToPanelBox = Panel.Panel.prototype._originalAddToPanelBox; - // Set `Panel._originalAddToPanelBox` to `undefined`. - // @ts-ignore - Panel.Panel.prototype._originalAddToPanelBox = undefined; - - // Disconnect signals. - for (const handlerId of this._settingsHandlerIds) { - this._settings.disconnect(handlerId); + if (Panel.Panel.prototype._originalAddToPanelBox) { + // @ts-ignore + Panel.Panel.prototype._addToPanelBox = Panel.Panel.prototype._originalAddToPanelBox; + // Set `Panel._originalAddToPanelBox` to `undefined`. + // @ts-ignore + Panel.Panel.prototype._originalAddToPanelBox = undefined; } - this._boxOrderManager.disconnectSignals(); + + // Disconnect signals. `enable()` may not have got this far. + for (const handlerId of this._settingsHandlerIds ?? []) { + this._settings?.disconnect(handlerId); + } + this._boxOrderManager?.disconnectSignals(); // @ts-ignore this._settings = null; @@ -83,9 +86,16 @@ export default class TopBarOrganizerExtension extends Extension { */ #overwritePanelAddToPanelBox(): void { // Add the original `Panel._addToPanelBox` method as - // `Panel._originalAddToPanelBox`. + // `Panel._originalAddToPanelBox`, unless it's already saved. That is + // the case when an earlier `enable()` failed, so `disable()` never + // ran: `Panel._addToPanelBox` is then still our overwrite, and saving + // that as the original would make it call itself endlessly, which + // leaves the top bar empty. // @ts-ignore - Panel.Panel.prototype._originalAddToPanelBox = Panel.Panel.prototype._addToPanelBox; + if (!Panel.Panel.prototype._originalAddToPanelBox) { + // @ts-ignore + Panel.Panel.prototype._originalAddToPanelBox = Panel.Panel.prototype._addToPanelBox; + } const handleNewItemsAndOrderTopBar = () => { this.#handleNewItemsAndOrderTopBar();