mirror of
https://gitlab.gnome.org/julianschacher/top-bar-organizer.git
synced 2025-10-27 15:19:09 +00:00
410 lines
18 KiB
JavaScript
410 lines
18 KiB
JavaScript
"use strict";
|
|
/* exported init */
|
|
|
|
const ExtensionUtils = imports.misc.extensionUtils;
|
|
const Me = ExtensionUtils.getCurrentExtension();
|
|
|
|
const Main = imports.ui.main;
|
|
const Panel = imports.ui.panel;
|
|
|
|
const AppIndicatorKStatusNotifierItemManager = Me.imports.extensionModules.AppIndicatorKStatusNotifierItemManager;
|
|
const BoxOrderCreator = Me.imports.extensionModules.BoxOrderCreator;
|
|
|
|
class Extension {
|
|
constructor() {
|
|
}
|
|
|
|
enable() {
|
|
this.settings = ExtensionUtils.getSettings();
|
|
|
|
// Create an instance of AppIndicatorKStatusNotifierItemManager to
|
|
// handle AppIndicator/KStatusNotifierItem items.
|
|
this._appIndicatorKStatusNotifierItemManager = new AppIndicatorKStatusNotifierItemManager.AppIndicatorKStatusNotifierItemManager();
|
|
|
|
// Create an instance of BoxOrderCreator for the creation of special box
|
|
// orders.
|
|
this._boxOrderCreator = new BoxOrderCreator.BoxOrderCreator(this._appIndicatorKStatusNotifierItemManager);
|
|
|
|
this.#addNewItemsToBoxOrders();
|
|
this.#orderTopBarItemsOfAllBoxes();
|
|
this.#overwritePanelAddToPanelBox();
|
|
|
|
// Handle changes of configured box orders.
|
|
this._settingsHandlerIds = [ ];
|
|
|
|
const addConfiguredBoxOrderChangeHandler = (box) => {
|
|
let handlerId = this.settings.connect(`changed::${box}-box-order`, () => {
|
|
this.#orderTopBarItems(box);
|
|
|
|
/// For the case, where the currently saved box order is based
|
|
/// on a permutation of an outdated box order, get an updated
|
|
/// box order and save it, if needed.
|
|
let updatedBoxOrder;
|
|
switch (box) {
|
|
case "left":
|
|
updatedBoxOrder = this.#createUpdatedBoxOrders().left;
|
|
break;
|
|
case "center":
|
|
updatedBoxOrder = this.#createUpdatedBoxOrders().center;
|
|
break;
|
|
case "right":
|
|
updatedBoxOrder = this.#createUpdatedBoxOrders().right;
|
|
break;
|
|
}
|
|
// Only save the updated box order to settings, if it is
|
|
// different, to avoid looping.
|
|
const currentBoxOrder = this.settings.get_strv(`${box}-box-order`);
|
|
if (JSON.stringify(currentBoxOrder) !== JSON.stringify(updatedBoxOrder)) {
|
|
this.settings.set_strv(`${box}-box-order`, updatedBoxOrder);
|
|
}
|
|
});
|
|
this._settingsHandlerIds.push(handlerId);
|
|
};
|
|
|
|
addConfiguredBoxOrderChangeHandler("left");
|
|
addConfiguredBoxOrderChangeHandler("center");
|
|
addConfiguredBoxOrderChangeHandler("right");
|
|
}
|
|
|
|
disable() {
|
|
// Revert the overwrite of `Panel._addToPanelBox`.
|
|
Panel.Panel.prototype._addToPanelBox = Panel.Panel.prototype._originalAddToPanelBox;
|
|
// Set `Panel._originalAddToPanelBox` to `undefined`.
|
|
Panel._originalAddToPanelBox = undefined;
|
|
|
|
// Disconnect signals.
|
|
for (const handlerId of this._settingsHandlerIds) {
|
|
this.settings.disconnect(handlerId);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
/// Methods used on extension enable. ///
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* This method adds all new items currently present in the Gnome Shell top
|
|
* bar to the box orders.
|
|
*/
|
|
#addNewItemsToBoxOrders() {
|
|
const boxOrders = this.#createUpdatedBoxOrders();
|
|
this.settings.set_strv("left-box-order", boxOrders.left);
|
|
this.settings.set_strv("center-box-order", boxOrders.center);
|
|
this.settings.set_strv("right-box-order", boxOrders.right);
|
|
}
|
|
|
|
/**
|
|
* This methods orders the top bar items of all boxes according to the
|
|
* configred box orders using `this.#orderTopBarItems`.
|
|
*/
|
|
#orderTopBarItemsOfAllBoxes() {
|
|
this.#orderTopBarItems("left");
|
|
this.#orderTopBarItems("center");
|
|
this.#orderTopBarItems("right");
|
|
}
|
|
|
|
/**
|
|
* An object containing a position and box overwrite.
|
|
* @typedef PositionAndBoxOverwrite
|
|
* @property {Number} position - The position overwrite.
|
|
* @property {string} box - The position box overwrite.
|
|
*/
|
|
|
|
/**
|
|
* Overwrite `Panel._addToPanelBox` with a custom method, which handles top
|
|
* bar item additions to make sure that they are added in the correct
|
|
* position and box.
|
|
*/
|
|
#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 and box for a new item.
|
|
// It also adds the new item to the relevant box order, if it isn't in
|
|
// it already.
|
|
const getPositionAndBoxOverwrite = (role, box, indicator) => {
|
|
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"),
|
|
};
|
|
let boxOrder;
|
|
|
|
// Handle the case where the new item is a
|
|
// AppIndicator/KStatusNotifierItem.
|
|
if (role.startsWith("appindicator-")) {
|
|
switch (box) {
|
|
case "left":
|
|
boxOrder = this.settings.get_strv("left-box-order");
|
|
this._appIndicatorKStatusNotifierItemManager.handleAppIndicatorKStatusNotifierItemItem(indicator.container, role, boxOrder, boxOrders);
|
|
this.settings.set_strv("left-box-order", boxOrder);
|
|
break;
|
|
case "center":
|
|
boxOrder = this.settings.get_strv("center-box-order");
|
|
this._appIndicatorKStatusNotifierItemManager.handleAppIndicatorKStatusNotifierItemItem(indicator.container, role, boxOrder, boxOrders);
|
|
this.settings.set_strv("center-box-order", boxOrder);
|
|
break;
|
|
case "right":
|
|
boxOrder = this.settings.get_strv("right-box-order");
|
|
this._appIndicatorKStatusNotifierItemManager.handleAppIndicatorKStatusNotifierItemItem(indicator.container, role, boxOrder, boxOrders, true);
|
|
this.settings.set_strv("right-box-order", boxOrder);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Get the resolved box orders for all boxes.
|
|
const resolvedBoxOrders = {
|
|
left: this._appIndicatorKStatusNotifierItemManager.createResolvedBoxOrder(this.settings.get_strv("left-box-order")),
|
|
center: this._appIndicatorKStatusNotifierItemManager.createResolvedBoxOrder(this.settings.get_strv("center-box-order")),
|
|
right: this._appIndicatorKStatusNotifierItemManager.createResolvedBoxOrder(this.settings.get_strv("right-box-order")),
|
|
};
|
|
// Also get the restricted valid box order of the target box.
|
|
const restrictedValidBoxOrderOfTargetBox = this._boxOrderCreator.createRestrictedValidBoxOrder(box);
|
|
|
|
// Get the index of the role for each box order.
|
|
const indices = {
|
|
left: resolvedBoxOrders.left.indexOf(role),
|
|
center: resolvedBoxOrders.center.indexOf(role),
|
|
right: resolvedBoxOrders.right.indexOf(role),
|
|
};
|
|
|
|
// If the role is not already configured in one of the box orders,
|
|
// just add it to the target box order at the end/beginning, save
|
|
// the updated box order and return the relevant position and box.
|
|
if (indices.left === -1
|
|
&& indices.center === -1
|
|
&& indices.right === -1) {
|
|
switch (box) {
|
|
// For the left and center box, insert the role at the end,
|
|
// since they're LTR.
|
|
case "left":
|
|
boxOrders["left"].push(role);
|
|
this.settings.set_strv("left-box-order", boxOrders["left"]);
|
|
return {
|
|
position: restrictedValidBoxOrderOfTargetBox.length - 1,
|
|
box: box
|
|
};
|
|
case "center":
|
|
boxOrders["center"].push(role);
|
|
this.settings.set_strv("center-box-order", boxOrders["center"]);
|
|
return {
|
|
position: restrictedValidBoxOrderOfTargetBox.length - 1,
|
|
box: box
|
|
};
|
|
// For the right box, insert the role at the beginning,
|
|
// since it's RTL.
|
|
case "right":
|
|
boxOrders["right"].unshift(role);
|
|
this.settings.set_strv("right-box-order", boxOrders["right"]);
|
|
return {
|
|
position: 0,
|
|
box: box
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Since the role is already configured in one of the box orders,
|
|
/// determine the correct insertion index for the position.
|
|
const determineInsertionIndex = (index, restrictedValidBoxOrder, boxOrder) => {
|
|
// 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 = restrictedValidBoxOrder.indexOf(boxOrder[i]);
|
|
if (potentialClosestItemIndex !== -1) {
|
|
insertionIndex = potentialClosestItemIndex + 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return insertionIndex;
|
|
};
|
|
|
|
if (indices.left !== -1) {
|
|
return {
|
|
position: determineInsertionIndex(indices.left, this._boxOrderCreator.createRestrictedValidBoxOrder("left"), resolvedBoxOrders.left),
|
|
box: "left"
|
|
};
|
|
}
|
|
|
|
if (indices.center !== -1) {
|
|
return {
|
|
position: determineInsertionIndex(indices.center, this._boxOrderCreator.createRestrictedValidBoxOrder("center"), resolvedBoxOrders.center),
|
|
box: "center"
|
|
};
|
|
}
|
|
|
|
if (indices.right !== -1) {
|
|
return {
|
|
position: determineInsertionIndex(indices.right, this._boxOrderCreator.createRestrictedValidBoxOrder("right"), resolvedBoxOrders.right),
|
|
box: "right"
|
|
};
|
|
}
|
|
};
|
|
|
|
// Overwrite `Panel._addToPanelBox`.
|
|
Panel.Panel.prototype._addToPanelBox = function (role, indicator, position, box) {
|
|
// Get the position and box overwrite.
|
|
let positionBoxOverwrite;
|
|
switch (box) {
|
|
case this._leftBox:
|
|
positionBoxOverwrite = getPositionAndBoxOverwrite(role, "left", indicator);
|
|
break;
|
|
case this._centerBox:
|
|
positionBoxOverwrite = getPositionAndBoxOverwrite(role, "center", indicator);
|
|
break;
|
|
case this._rightBox:
|
|
positionBoxOverwrite = getPositionAndBoxOverwrite(role, "right", indicator);
|
|
break;
|
|
}
|
|
|
|
// Call the original `Panel._addToPanelBox` with the position
|
|
// overwrite as the position argument and the box determined by the
|
|
// box overwrite as the box argument.
|
|
switch (positionBoxOverwrite.box) {
|
|
case "left":
|
|
this._originalAddToPanelBox(role, indicator, positionBoxOverwrite.position, Main.panel._leftBox);
|
|
break;
|
|
case "center":
|
|
this._originalAddToPanelBox(role, indicator, positionBoxOverwrite.position, Main.panel._centerBox);
|
|
break;
|
|
case "right":
|
|
this._originalAddToPanelBox(role, indicator, positionBoxOverwrite.position, Main.panel._rightBox);
|
|
break;
|
|
}
|
|
};
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
/// Helper methods holding logic needed by other methods. ///
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* An object containing a box order for the left, center and right top bar
|
|
* box.
|
|
* @typedef {Object} BoxOrders
|
|
* @property {string[]} left - The box order for the left top bar box.
|
|
* @property {string[]} center - The box order for the center top bar box.
|
|
* @property {string[]} right - The box order for the right top bar box.
|
|
*/
|
|
|
|
/**
|
|
* This method adds all new items currently present in the Gnome Shell top
|
|
* bar to the correct box order and returns the new box orders.
|
|
* @returns {BoxOrders} - The updated box orders.
|
|
*/
|
|
#createUpdatedBoxOrders() {
|
|
// Load the configured box orders from settings.
|
|
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"),
|
|
};
|
|
|
|
// 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 boxes.
|
|
const boxOrderIndicatorContainers = {
|
|
left: Main.panel._leftBox.get_children(),
|
|
center: 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.
|
|
right: Main.panel._rightBox.get_children().reverse()
|
|
};
|
|
|
|
// This function goes through the items (or rather their indicator
|
|
// containers) of the given box and adds new items (or rather their
|
|
// roles) to the box order.
|
|
const addNewItemsToBoxOrder = (boxIndicatorContainers, boxOrder, box) => {
|
|
for (const indicatorContainer of boxIndicatorContainers) {
|
|
// First get the role associated with the current indicator
|
|
// container.
|
|
const associatedRole = indicatorContainerRoleMap.get(indicatorContainer);
|
|
if (!associatedRole) continue;
|
|
|
|
// Handle an AppIndicator/KStatusNotifierItem item differently.
|
|
if (associatedRole.startsWith("appindicator-")) {
|
|
this._appIndicatorKStatusNotifierItemManager.handleAppIndicatorKStatusNotifierItemItem(indicatorContainer, associatedRole, boxOrder, boxOrders, box === "right");
|
|
continue;
|
|
}
|
|
|
|
// Add the role to the box order, if it isn't in in one already.
|
|
if (!boxOrders.left.includes(associatedRole)
|
|
&& !boxOrders.center.includes(associatedRole)
|
|
&& !boxOrders.right.includes(associatedRole)) {
|
|
if (box === "right") {
|
|
// Add the items to the beginning for this array, since
|
|
// its RTL.
|
|
boxOrder.unshift(associatedRole);
|
|
} else {
|
|
boxOrder.push(associatedRole);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
addNewItemsToBoxOrder(boxOrderIndicatorContainers.left, boxOrders.left, "left");
|
|
addNewItemsToBoxOrder(boxOrderIndicatorContainers.center, boxOrders.center, "center");
|
|
addNewItemsToBoxOrder(boxOrderIndicatorContainers.right, boxOrders.right, "right");
|
|
|
|
return boxOrders;
|
|
}
|
|
|
|
/**
|
|
* This method orders the top bar items of the specified box according to
|
|
* the configured box orders.
|
|
* @param {string} box - The box to order.
|
|
*/
|
|
#orderTopBarItems(box) {
|
|
// Get the valid box order.
|
|
const validBoxOrder = this._boxOrderCreator.createValidBoxOrder(box);
|
|
|
|
// Get the relevant box of `Main.panel`.
|
|
let panelBox;
|
|
switch (box) {
|
|
case "left":
|
|
panelBox = Main.panel._leftBox;
|
|
break;
|
|
case "center":
|
|
panelBox = Main.panel._centerBox;
|
|
break;
|
|
case "right":
|
|
panelBox = Main.panel._rightBox;
|
|
break;
|
|
}
|
|
|
|
/// Go through the items (or rather their roles) of the validBoxOrder
|
|
/// and order the panelBox accordingly.
|
|
for (let i = 0; i < validBoxOrder.length; i++) {
|
|
const role = validBoxOrder[i];
|
|
// Get the indicator container associated with the current role.
|
|
const associatedIndicatorContainer = Main.panel.statusArea[role].container;
|
|
|
|
associatedIndicatorContainer.get_parent().remove_child(associatedIndicatorContainer);
|
|
panelBox.insert_child_at_index(associatedIndicatorContainer, i);
|
|
}
|
|
// To handle the case, where the box order got set to a permutation
|
|
// of an outdated box order, it would be wise, if the caller updated the
|
|
// box order now to include the items present in the top bar.
|
|
}
|
|
}
|
|
|
|
function init() {
|
|
return new Extension();
|
|
}
|