"use strict"; const ExtensionUtils = imports.misc.extensionUtils; const Main = imports.ui.main; const Panel = imports.ui.panel; class Extension { constructor() { this.settings = ExtensionUtils.getSettings(); } enable() { this._addNewItemsToBoxOrders(); this._orderTopBarItems(); this._overwritePanelAddToPanelBox(); } disable() { // Revert the overwrite of `Panel._addToPanelBox`. Panel.Panel.prototype._addToPanelBox = Panel.Panel.prototype._originalAddToPanelBox; } /** * This method adds all new items currently present in the Gnome Shell top * bar to the box orders. */ _addNewItemsToBoxOrders() { // Load the configured box orders from settings. let leftBoxOrder = this.settings.get_strv("left-box-order"); let centerBoxOrder = this.settings.get_strv("center-box-order"); let rightBoxOrder = this.settings.get_strv("right-box-order"); // Get items (or rather their roles) 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) { indicatorContainerRoleMap.set(Main.panel.statusArea[role].container, role); } // Get the indicator containers (of the items) currently present in the // Gnome Shell top bar. const leftBoxIndicatorContainers = Main.panel._leftBox.get_children(); const centerBoxIndicatorContainers = Main.panel._centerBox.get_children(); // Reverse this array, since the items in the left and center box are // logically LTR, while the items in the right box are RTL. const rightBoxIndicatorContainers = Main.panel._rightBox.get_children().reverse(); // Go through the items (or rather their indicator containers) of each // box and add new items (or rather their roles) to the box orders. const addNewRolesToBoxOrder = (boxIndicatorContainers, boxOrder, atToBeginning = false) => { // Create a box order set from the box order for fast easy access. const boxOrderSet = new Set(boxOrder); for (const indicatorContainer of boxIndicatorContainers) { // First get the role associated with the current indicator // container. const associatedRole = indicatorContainerRoleMap.get(indicatorContainer); // Add the role to the box order, if it isn't in it already. if (!boxOrderSet.has(associatedRole)) { if (atToBeginning) { boxOrder.unshift(associatedRole); } else { boxOrder.push(associatedRole); } } } } // Add new items (or rather their roles) to the box orders and save // them. addNewRolesToBoxOrder(leftBoxIndicatorContainers, leftBoxOrder); addNewRolesToBoxOrder(centerBoxIndicatorContainers, centerBoxOrder); // Add the items to the beginning for this array, since its RTL. addNewRolesToBoxOrder(rightBoxIndicatorContainers, rightBoxOrder, true); this.settings.set_strv("left-box-order", leftBoxOrder); this.settings.set_strv("center-box-order", centerBoxOrder); this.settings.set_strv("right-box-order", rightBoxOrder); } /** * This method orders the top bar items according to the configured box * orders. */ _orderTopBarItems() { // Get valid box orders. const validLeftBoxOrder = this._createValidBoxOrder("left"); const validCenterBoxOrder = this._createValidBoxOrder("center"); const validRightBoxOrder = this._createValidBoxOrder("right"); // Go through the items (or rather their roles) of a box and order the // box accordingly. const orderBox = (boxOrder, box) => { for (let i = 0; i < boxOrder.length; i++) { const role = boxOrder[i]; // Get the indicator container associated with the current role. const associatedIndicatorContainer = Main.panel.statusArea[role].container; box.set_child_at_index(associatedIndicatorContainer, i); } } // Order the top bar items. orderBox(validLeftBoxOrder, Main.panel._leftBox); orderBox(validCenterBoxOrder, Main.panel._centerBox); orderBox(validRightBoxOrder, Main.panel._rightBox); } /** * This function creates a valid box order for the given box. * @param {string} box - The box to return the valid box order for. * Must be one of the following values: * - "left" * - "center" * - "right" * @returns {string[]} - The valid box order. */ _createValidBoxOrder(box) { // Load the configured box order from settings and get the indicator // containers (of the items) currently present in the Gnome Shell top // bar. let boxOrder; let boxIndicatorContainers; switch (box) { case "left": boxOrder = this.settings.get_strv("left-box-order"); boxIndicatorContainers = Main.panel._leftBox.get_children(); break; case "center": boxOrder = this.settings.get_strv("center-box-order"); boxIndicatorContainers = Main.panel._centerBox.get_children(); break; case "right": boxOrder = this.settings.get_strv("right-box-order"); boxIndicatorContainers = Main.panel._rightBox.get_children(); break; } // Create an indicator containers set from the indicator containers for // fast easy access. const boxIndicatorContainersSet = new Set(boxIndicatorContainers); // 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. let validBoxOrder = [ ]; for (const role of boxOrder) { // Get the indicator container associated with the current role. const associatedIndicatorContainer = Main.panel.statusArea[role]?.container; if (boxIndicatorContainersSet.has(associatedIndicatorContainer)) validBoxOrder.push(role); } return validBoxOrder; } /** * Overwrite `Panel._addToPanelBox` with a custom method, which handles top * bar item additions to make sure that they are added in the correct * position. */ _overwritePanelAddToPanelBox() { // Add the original `Panel._addToPanelBox` method as // `Panel._originalAddToPanelBox`. Panel.Panel.prototype._originalAddToPanelBox = Panel.Panel.prototype._addToPanelBox; // This function gets used by the `Panel._addToPanelBox` overwrite to // determine the position for a new item. // It also adds the new item to the relevant box order, if it isn't in // it already. const getPositionOverwrite = (role, box) => { let boxOrder; let validBoxOrder; switch (box) { case "left": boxOrder = this.settings.get_strv("left-box-order"); validBoxOrder = this._createValidBoxOrder("left"); break; case "center": boxOrder = this.settings.get_strv("center-box-order"); validBoxOrder = this._createValidBoxOrder("center"); break; case "right": boxOrder = this.settings.get_strv("right-box-order"); validBoxOrder = this._createValidBoxOrder("right"); break; } // Get the index of the role in the box order. const index = boxOrder.indexOf(role); // If the role is not already configured in the box order, just add // it to the box order at the end/beginning, save the updated box // order and return the relevant position. if (index === -1) { switch (box) { // For the left and center box, insert the role at the end, // since they're LTR. case "left": boxOrder.push(role); this.settings.set_strv("left-box-order", boxOrder); return validBoxOrder.length - 1; case "center": boxOrder.push(role); this.settings.set_strv("center-box-order", boxOrder); return validBoxOrder.length - 1; // For the right box, insert the role at the beginning, // since it's RTL. case "right": boxOrder.unshift(role); this.settings.set_strv("right-box-order", boxOrder); return 0; } } // Since the role is already configured in the box order, determine // the correct insertion index for the position. // Set the insertion index initially to 0, so that if no closest // item can be found, the new item just gets inserted at the // beginning. let insertionIndex = 0; // Find the index of the closest item, which is also in the valid // box order and before the new item. // This way, we can insert the new item just after the index of this // closest item. for (let i = index - 1; i >= 0; i--) { let potentialClosestItemIndex = validBoxOrder.indexOf(boxOrder[i]); if (potentialClosestItemIndex !== -1) { insertionIndex = potentialClosestItemIndex + 1; break; } } return insertionIndex; } // Overwrite `Panel._addToPanelBox`. Panel.Panel.prototype._addToPanelBox = function (role, indicator, position, box) { // Get the position overwrite. let positionOverwrite; switch (box) { case this._leftBox: positionOverwrite = getPositionOverwrite(role, "left"); break; case this._centerBox: positionOverwrite = getPositionOverwrite(role, "center"); break; case this._rightBox: positionOverwrite = getPositionOverwrite(role, "right"); break; } // Call the original `Panel._addToPanelBox` with the position // overwrite as the position argument. this._originalAddToPanelBox(role, indicator, positionOverwrite, box); } } } function init() { return new Extension(); }