diff --git a/docs/panel_45.0_2023-09-26.js b/docs/panel_45.0_2023-09-26.js new file mode 100644 index 0000000..bb44a90 --- /dev/null +++ b/docs/panel_45.0_2023-09-26.js @@ -0,0 +1,344 @@ +// My annotated and cut down `js/ui/panel.js` from gnome-shell/45.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/45.0/js/ui/panel.js +// On: 2023-09-26 +// License: This code is licensed under GPLv2. + +// Taken from: https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/45.0/js/ui/sessionMode.js +// On: 2023-09-26 +// 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. + + +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +// 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, +}; + +// Annotation: Uses ESM export now. +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_43.2_2023-01-24.js: Nothing changed (except for + // formatting). + _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.connect('showing', () => { + this.add_style_pseudo_class('overview'); + }); + Main.overview.connect('hiding', () => { + this.remove_style_pseudo_class('overview'); + }); + + Main.layoutManager.panelBox.add(this); + Main.ctrlAltTabManager.addGroup(this, + _('Top Bar'), '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` (45.0, 2023-09-26) 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_43.2_2023-01-24.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_43.2_2023-01-24.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_43.2_2023-01-24.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_43.2_2023-01-24.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_43.2_2023-01-24.js: Nothing changed. + _addToPanelBox(role, indicator, position, box) { + let container = indicator.container; + container.show(); + + let parent = container.get_parent(); + if (parent) + parent.remove_actor(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_43.2_2023-01-24.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; + } + + // Compared to panel_43.2_2023-01-24.js: Nothing changed, except for the + // usage of === instead of ==. + _onMenuSet(indicator) { + if (!indicator.menu || indicator.menu._openChangedId) + return; + + this.menuManager.addMenu(indicator.menu); + + indicator.menu._openChangedId = indicator.menu.connect('open-state-changed', + (menu, isOpen) => { + let boxAlignment; + if (this._leftBox.contains(indicator.container)) + boxAlignment = Clutter.ActorAlign.START; + else if (this._centerBox.contains(indicator.container)) + boxAlignment = Clutter.ActorAlign.CENTER; + else if (this._rightBox.contains(indicator.container)) + boxAlignment = Clutter.ActorAlign.END; + + if (boxAlignment === Main.messageTray.bannerAlignment) + Main.messageTray.bannerBlocked = isOpen; + }); + } + + // Annotation: [...] Cut out bunch of stuff here, which isn't relevant for + // this Extension. +});