/* * This file is part of the Dash-To-Panel extension for Gnome 3 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Credits: * This file is based on code from the Dash to Dock extension by micheleg * and code from the Taskbar extension by Zorin OS * * Code to re-anchor the panel was taken from Thoma5 BottomPanel: * https://github.com/Thoma5/gnome-shell-extension-bottompanel * * Pattern for moving clock based on Frippery Move Clock by R M Yorston * http://frippery.org/extensions/ * * Some code was also adapted from the upstream Gnome Shell source code. */ const Me = imports.misc.extensionUtils.getCurrentExtension(); const Clutter = imports.gi.Clutter; const Config = imports.misc.config; const Gtk = imports.gi.Gtk; const Gi = imports._gi; const AppIcons = Me.imports.appIcons; const Utils = Me.imports.utils; const Taskbar = Me.imports.taskbar; const Pos = Me.imports.panelPositions; const PanelSettings = Me.imports.panelSettings; const PanelStyle = Me.imports.panelStyle; const Lang = imports.lang; const Main = imports.ui.main; const Mainloop = imports.mainloop; const Dash = imports.ui.dash; const CtrlAltTab = imports.ui.ctrlAltTab; const Panel = imports.ui.panel; const PanelMenu = imports.ui.panelMenu; const St = imports.gi.St; const GLib = imports.gi.GLib; const Meta = imports.gi.Meta; const Pango = imports.gi.Pango; const DND = imports.ui.dnd; const Shell = imports.gi.Shell; const PopupMenu = imports.ui.popupMenu; const IconGrid = imports.ui.iconGrid; const DateMenu = imports.ui.dateMenu; const Volume = imports.ui.status.volume; const Progress = Me.imports.progress; const Intellihide = Me.imports.intellihide; const Transparency = Me.imports.transparency; const _ = imports.gettext.domain(Me.imports.utils.TRANSLATION_DOMAIN).gettext; let tracker = Shell.WindowTracker.get_default(); var panelBoxes = ['_leftBox', '_centerBox', '_rightBox']; //timeout names const T1 = 'startDynamicTransparencyTimeout'; const T2 = 'startIntellihideTimeout'; const T3 = 'allocationThrottleTimeout'; const T4 = 'showDesktopTimeout'; const T5 = 'trackerFocusAppTimeout'; const T6 = 'scrollPanelDelayTimeout'; const T7 = 'waitPanelBoxAllocation'; var dtpPanel = Utils.defineClass({ Name: 'DashToPanel-Panel', Extends: St.Widget, _init: function(panelManager, monitor, panelBox, isStandalone) { this.callParent('_init', { layout_manager: new Clutter.BinLayout() }); this._timeoutsHandler = new Utils.TimeoutsHandler(); this._signalsHandler = new Utils.GlobalSignalsHandler(); this.panelManager = panelManager; this.panelStyle = new PanelStyle.dtpPanelStyle(); this.monitor = monitor; this.panelBox = panelBox; // when the original gnome-shell top panel is kept, all panels are "standalone", // so in this case use isPrimary to get the panel on the primary dtp monitor, which // might be different from the system's primary monitor. this.isStandalone = isStandalone; this.isPrimary = !isStandalone || (Me.settings.get_boolean('stockgs-keep-top-panel') && monitor == panelManager.dtpPrimaryMonitor); this._sessionStyle = null; this._unmappedButtons = []; this._elementGroups = []; this.cornerSize = 0; let position = this.getPosition(); if (isStandalone) { this.panel = new dtpSecondaryPanel({ name: 'panel', reactive: true }); this.statusArea = this.panel.statusArea = {}; Utils.wrapActor(this.panel); //next 3 functions are needed by other extensions to add elements to the secondary panel this.panel.addToStatusArea = function(role, indicator, position, box) { return Main.panel.addToStatusArea.call(this, role, indicator, position, box); }; this.panel._addToPanelBox = function(role, indicator, position, box) { Main.panel._addToPanelBox.call(this, role, indicator, position, box); }; this.panel._onMenuSet = function(indicator) { Main.panel._onMenuSet.call(this, indicator); }; this._leftBox = this.panel._leftBox = new St.BoxLayout({ name: 'panelLeft' }); this._centerBox = this.panel._centerBox = new St.BoxLayout({ name: 'panelCenter' }); this._rightBox = this.panel._rightBox = new St.BoxLayout({ name: 'panelRight' }); this.menuManager = this.panel.menuManager = new PopupMenu.PopupMenuManager(this.panel); this._setPanelMenu('aggregateMenu', dtpSecondaryAggregateMenu, this.panel.actor); this._setPanelMenu('dateMenu', DateMenu.DateMenuButton, this.panel.actor); this._setPanelMenu('activities', Panel.ActivitiesButton, this.panel.actor); this.panel.add_child(this._leftBox); this.panel.add_child(this._centerBox); this.panel.add_child(this._rightBox); } else { this.panel = Main.panel; this.statusArea = Main.panel.statusArea; this.menuManager = Main.panel.menuManager; panelBoxes.forEach(p => this[p] = Main.panel[p]); ['activities', 'aggregateMenu', 'dateMenu'].forEach(b => { let container = this.statusArea[b].container; let parent = container.get_parent(); container._dtpOriginalParent = parent; parent ? parent.remove_child(container) : null; this.panel.actor.add_child(container); }); } // Create a wrapper around the real showAppsIcon in order to add a popupMenu. Most of // its behavior is handled by the taskbar, but its positioning is done at the panel level this.showAppsIconWrapper = new AppIcons.ShowAppsIconWrapper(this); this.panel.actor.add_child(this.showAppsIconWrapper.realShowAppsIcon); this.panel.actor._delegate = this; Utils.wrapActor(this.statusArea.activities); this.add_child(this.panel.actor); if (Main.panel._onButtonPress || Main.panel._tryDragWindow) { this._signalsHandler.add([ this.panel.actor, [ 'button-press-event', 'touch-event' ], this._onButtonPress.bind(this) ]); } if (Main.panel._onKeyPress) { this._signalsHandler.add([this.panel.actor, 'key-press-event', Main.panel._onKeyPress.bind(this)]); } Main.ctrlAltTabManager.addGroup(this, _("Top Bar")+" "+ monitor.index, 'focus-top-bar-symbolic', { sortGroup: CtrlAltTab.SortGroup.TOP }); }, enable : function() { let position = this.getPosition(); if (this.statusArea.aggregateMenu) { Utils.getIndicators(this.statusArea.aggregateMenu._volume)._dtpIgnoreScroll = 1; } this.geom = this.getGeometry(); // The overview uses the panel height as a margin by way of a "ghost" transparent Clone // This pushes everything down, which isn't desired when the panel is moved to the bottom // I'm adding a 2nd ghost panel and will resize the top or bottom ghost depending on the panel position this._myPanelGhost = new Clutter.Actor({ x: this.geom.x, y: this.geom.y , reactive: false, opacity: 0 }); let isTop = this.geom.position == St.Side.TOP; if (isTop) { this.panel._leftCorner = this.panel._leftCorner || new Panel.PanelCorner(St.Side.LEFT); this.panel._rightCorner = this.panel._rightCorner || new Panel.PanelCorner(St.Side.RIGHT); Main.overview._overview.insert_child_at_index(this._myPanelGhost, 0); } else { let overviewControls = Main.overview._overview._controls || Main.overview._controls; Main.overview._overview.add_actor(this._myPanelGhost); } if (this.panel._leftCorner) { Utils.wrapActor(this.panel._leftCorner); Utils.wrapActor(this.panel._rightCorner); if (isTop) { if (this.isStandalone) { this.panel.actor.add_child(this.panel._leftCorner.actor); this.panel.actor.add_child(this.panel._rightCorner.actor); } } else if (Config.PACKAGE_VERSION >= '3.32') { this.panel.actor.remove_child(this.panel._leftCorner.actor); this.panel.actor.remove_child(this.panel._rightCorner.actor); } } this._setPanelPosition(); if (!this.isStandalone) { if (this.panel.vfunc_allocate) { this._panelConnectId = 0; Utils.hookVfunc(this.panel.__proto__, 'allocate', (box, flags) => this._mainPanelAllocate(0, box, flags)); } else { this._panelConnectId = this.panel.actor.connect('allocate', (actor, box, flags) => this._mainPanelAllocate(actor, box, flags)); } // remove the extra space before the clock when the message-indicator is displayed if (DateMenu.IndicatorPad) { Utils.hookVfunc(DateMenu.IndicatorPad.prototype, 'get_preferred_width', () => [0,0]); Utils.hookVfunc(DateMenu.IndicatorPad.prototype, 'get_preferred_height', () => [0,0]); } } if (!DateMenu.IndicatorPad && this.statusArea.dateMenu) { //3.36 switched to a size constraint applied on an anonymous child let indicatorPad = this.statusArea.dateMenu.get_first_child().get_first_child(); this._dateMenuIndicatorPadContraints = indicatorPad.get_constraints(); indicatorPad.clear_constraints(); } // The main panel's connection to the "allocate" signal is competing with this extension // trying to move the centerBox over to the right, creating a never-ending cycle. // Since we don't have the ID to disconnect that handler, wrap the allocate() function // it calls instead. If the call didn't originate from this file, ignore it. panelBoxes.forEach(b => { this[b].allocate = (box, flags, isFromDashToPanel) => { if (isFromDashToPanel) { Utils.allocate(this[b], box, flags, true); } } }); this.menuManager._oldChangeMenu = this.menuManager._changeMenu; this.menuManager._changeMenu = (menu) => { if (!Me.settings.get_boolean('stockgs-panelbtn-click-only')) { this.menuManager._oldChangeMenu(menu); } }; this.dynamicTransparency = new Transparency.DynamicTransparency(this); this.taskbar = new Taskbar.taskbar(this); this.panel.actor.add_child(this.taskbar.actor); this._setAppmenuVisible(Me.settings.get_boolean('show-appmenu')); this._setShowDesktopButton(true); this._setAllocationMap(); this.panel.actor.add_style_class_name('dashtopanelMainPanel ' + this.getOrientation()); this._setPanelGhostSize(); this._timeoutsHandler.add([T2, Me.settings.get_int('intellihide-enable-start-delay'), () => this.intellihide = new Intellihide.Intellihide(this)]); this._signalsHandler.add( // this is to catch changes to the theme or window scale factor [ Utils.getStageTheme(), 'changed', () => (this._resetGeometry(), this._setShowDesktopButtonStyle()), ], [ // sync hover after a popupmenu is closed this.taskbar, 'menu-closed', Lang.bind(this, function(){this.panel.actor.sync_hover();}) ], [ Main.overview, [ 'showing', 'hiding' ], () => this._adjustForOverview() ], [ Main.overview, 'hidden', () => { if (this.isPrimary) { //reset the primary monitor when exiting the overview this.panelManager.setFocusedMonitor(this.monitor, true); } } ], [ this.statusArea.activities.actor, 'captured-event', (actor, e) => { // todo this is not being called right now, so the overview is not shown on the correct monitor if (e.type() == Clutter.EventType.BUTTON_PRESS || e.type() == Clutter.EventType.TOUCH_BEGIN) { //temporarily use as primary the monitor on which the activities btn was clicked this.panelManager.setFocusedMonitor(this.monitor, true); } } ], [ this._centerBox, 'actor-added', () => this._onBoxActorAdded(this._centerBox) ], [ this._rightBox, 'actor-added', () => this._onBoxActorAdded(this._rightBox) ], [ this.panel.actor, 'scroll-event', this._onPanelMouseScroll.bind(this) ], [ Main.layoutManager, 'startup-complete', () => this._resetGeometry() ] ); this._bindSettingsChanges(); this.panelStyle.enable(this); if (this.checkIfVertical()) { this._signalsHandler.add([ this.panelBox, 'notify::visible', () => { if (this.panelBox.visible) { this._refreshVerticalAlloc(); } } ]); this._setSearchEntryOffset(this.geom.w); if (this.statusArea.dateMenu) { this._formatVerticalClock(); this._signalsHandler.add([ this.statusArea.dateMenu._clock, 'notify::clock', () => this._formatVerticalClock() ]); } } // Since we are usually visible but not usually changing, make sure // most repaint requests don't actually require us to repaint anything. // This saves significant CPU when repainting the screen. this.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS); this._initProgressManager(); }, disable: function () { this.panelStyle.disable(); this._timeoutsHandler.destroy(); this._signalsHandler.destroy(); this._disablePanelCornerSignals(); this.panel.actor.remove_child(this.taskbar.actor); this._setAppmenuVisible(false); if (this.intellihide) { this.intellihide.destroy(); } this.dynamicTransparency.destroy(); this.progressManager.destroy(); this.taskbar.destroy(); this.showAppsIconWrapper.destroy(); this.menuManager._changeMenu = this.menuManager._oldChangeMenu; this._myPanelGhost.get_parent().remove_actor(this._myPanelGhost); this._setSearchEntryOffset(0); panelBoxes.forEach(b => delete this[b].allocate); this._unmappedButtons.forEach(a => this._disconnectVisibleId(a)); if (this._dateMenuIndicatorPadContraints && this.statusArea.dateMenu) { let indicatorPad = this.statusArea.dateMenu.get_first_child().get_first_child(); this._dateMenuIndicatorPadContraints.forEach(c => indicatorPad.add_constraint(c)); } this._setVertical(this.panel.actor, false); if (!this.isStandalone) { this.statusArea.dateMenu._clockDisplay.text = this.statusArea.dateMenu._clock.clock; ['vertical', 'horizontal', 'dashtopanelMainPanel'].forEach(c => this.panel.actor.remove_style_class_name(c)); if (!Main.sessionMode.isLocked) { [['activities', 0], ['aggregateMenu', -1], ['dateMenu', 0]].forEach(b => { let container = this.statusArea[b[0]].container; let originalParent = container._dtpOriginalParent; this.panel.actor.remove_child(container); originalParent ? originalParent.insert_child_at_index(container, b[1]) : null; delete container._dtpOriginalParent; }); } if (!this.panel._leftCorner.actor.mapped) { this.panel.actor.add_child(this.panel._leftCorner.actor); this.panel.actor.add_child(this.panel._rightCorner.actor); } this._setShowDesktopButton(false); delete Utils.getIndicators(this.statusArea.aggregateMenu._volume)._dtpIgnoreScroll; if (DateMenu.IndicatorPad) { Utils.hookVfunc(DateMenu.IndicatorPad.prototype, 'get_preferred_width', DateMenu.IndicatorPad.prototype.vfunc_get_preferred_width); Utils.hookVfunc(DateMenu.IndicatorPad.prototype, 'get_preferred_height', DateMenu.IndicatorPad.prototype.vfunc_get_preferred_height); } if (this._panelConnectId) { this.panel.actor.disconnect(this._panelConnectId); } else { Utils.hookVfunc(this.panel.__proto__, 'allocate', this.panel.__proto__.vfunc_allocate); } this.panel.actor._delegate = this.panel; } else { this._removePanelMenu('dateMenu'); this._removePanelMenu('aggregateMenu'); this._removePanelMenu('activities'); } Main.ctrlAltTabManager.removeGroup(this); }, handleDragOver: function(source, actor, x, y, time) { if (source == Main.xdndHandler) { // open overview so they can choose a window for focusing // and ultimately dropping dragged item onto if(Main.overview.shouldToggleByCornerOrButton()) Main.overview.show(); } return DND.DragMotionResult.CONTINUE; }, getPosition: function() { let position = PanelSettings.getPanelPosition(Me.settings, this.monitor.index); if (position == Pos.TOP) { return St.Side.TOP; } else if (position == Pos.RIGHT) { return St.Side.RIGHT; } else if (position == Pos.BOTTOM) { return St.Side.BOTTOM; } return St.Side.LEFT; }, checkIfVertical: function() { let position = this.getPosition(); return (position == St.Side.LEFT || position == St.Side.RIGHT); }, getOrientation: function() { return (this.checkIfVertical() ? 'vertical' : 'horizontal'); }, updateElementPositions: function() { let panelPositions = this.panelManager.panelsElementPositions[this.monitor.index] || Pos.defaults; this._updateGroupedElements(panelPositions); this._disablePanelCornerSignals(); if (this.getPosition() == St.Side.TOP) { let visibleElements = panelPositions.filter(pp => pp.visible); let connectCorner = (corner, button) => { corner._button = button; corner._buttonStyleChangedSignalId = button.connect('style-changed', () => { corner.set_style_pseudo_class(button.get_style_pseudo_class()); }); } if (visibleElements[0].element == Pos.ACTIVITIES_BTN) { connectCorner(this.panel._leftCorner, this.statusArea.activities); } if (visibleElements[visibleElements.length - 1].element == Pos.SYSTEM_MENU) { connectCorner(this.panel._rightCorner, this.statusArea.aggregateMenu); } } this.panel.actor.hide(); this.panel.actor.show(); }, _updateGroupedElements: function(panelPositions) { let previousPosition = 0; let previousCenteredPosition = 0; let currentGroup = -1; this._elementGroups = []; panelPositions.forEach(pos => { let allocationMap = this.allocationMap[pos.element]; if (allocationMap.actor) { allocationMap.actor.visible = pos.visible; if (!pos.visible) { return; } let currentPosition = pos.position; let isCentered = Pos.checkIfCentered(currentPosition); if (currentPosition == Pos.STACKED_TL && previousPosition == Pos.STACKED_BR) { currentPosition = Pos.STACKED_BR; } if (!previousPosition || (previousPosition == Pos.STACKED_TL && currentPosition != Pos.STACKED_TL) || (previousPosition != Pos.STACKED_BR && currentPosition == Pos.STACKED_BR) || (isCentered && previousPosition != currentPosition && previousPosition != Pos.STACKED_BR)) { this._elementGroups[++currentGroup] = { elements: [], index: this._elementGroups.length, expandableIndex: -1 }; previousCenteredPosition = 0; } if (pos.element == Pos.TASKBAR) { this._elementGroups[currentGroup].expandableIndex = this._elementGroups[currentGroup].elements.length; } if (isCentered && !this._elementGroups[currentGroup].isCentered) { this._elementGroups[currentGroup].isCentered = 1; previousCenteredPosition = currentPosition; } this._elementGroups[currentGroup].position = previousCenteredPosition || currentPosition; this._elementGroups[currentGroup].elements.push(allocationMap); allocationMap.position = currentPosition; previousPosition = currentPosition; } }); }, _disablePanelCornerSignals: function() { if (this.panel._rightCorner && this.panel._rightCorner._buttonStyleChangedSignalId) { this.panel._rightCorner._button.disconnect(this.panel._rightCorner._buttonStyleChangedSignalId); delete this.panel._rightCorner._buttonStyleChangedSignalId; } if (this.panel._leftCorner && this.panel._leftCorner._buttonStyleChangedSignalId) { this.panel._leftCorner._button.disconnect(this.panel._leftCorner._buttonStyleChangedSignalId); delete this.panel._leftCorner._buttonStyleChangedSignalId; } }, _bindSettingsChanges: function() { let isVertical = this.checkIfVertical(); this._signalsHandler.add( [ Me.settings, [ 'changed::panel-sizes', 'changed::group-apps' ], () => this._resetGeometry() ], [ Me.settings, [ 'changed::appicon-margin', 'changed::appicon-padding' ], () => this.taskbar.resetAppIcons() ], [ Me.settings, 'changed::show-appmenu', () => this._setAppmenuVisible(Me.settings.get_boolean('show-appmenu')) ], [ Me.settings, [ 'changed::showdesktop-button-width', 'changed::trans-use-custom-bg', 'changed::desktop-line-use-custom-color', 'changed::desktop-line-custom-color', 'changed::trans-bg-color' ], () => this._setShowDesktopButtonStyle() ], [ Me.desktopSettings, 'changed::clock-format', () => { this._clockFormat = null; if (isVertical) { this._formatVerticalClock(); } } ], [ Me.settings, 'changed::progress-show-bar', () => this._initProgressManager() ], [ Me.settings, 'changed::progress-show-count', () => this._initProgressManager() ] ); if (isVertical) { this._signalsHandler.add([Me.settings, 'changed::group-apps-label-max-width', () => this._resetGeometry()]); } }, _setPanelMenu: function(propName, constr, container) { if (!this.statusArea[propName]) { this.statusArea[propName] = this._getPanelMenu(propName, constr); this.menuManager.addMenu(this.statusArea[propName].menu); container.insert_child_at_index(this.statusArea[propName].container, 0); } }, _removePanelMenu: function(propName) { if (this.statusArea[propName]) { let parent = this.statusArea[propName].container.get_parent(); if (parent) { parent.remove_actor(this.statusArea[propName].container); } //calling this.statusArea[propName].destroy(); is buggy for now, gnome-shell never //destroys those panel menus... //since we can't destroy the menu (hence properly disconnect its signals), let's //store it so the next time a panel needs one of its kind, we can reuse it instead //of creating a new one let panelMenu = this.statusArea[propName]; this.menuManager.removeMenu(panelMenu.menu); Me.persistentStorage[propName].push(panelMenu); this.statusArea[propName] = null; } }, _getPanelMenu: function(propName, constr) { Me.persistentStorage[propName] = Me.persistentStorage[propName] || []; if (!Me.persistentStorage[propName].length) { Me.persistentStorage[propName].push(new constr()); } return Me.persistentStorage[propName].pop(); }, _setPanelGhostSize: function() { this._myPanelGhost.set_size(this.width, this.checkIfVertical() ? 1 : this.height); }, _setSearchEntryOffset: function(offset) { if (this.isPrimary) { //In the overview, when the panel is vertical the search-entry is the only element //that doesn't natively take into account the size of a side dock, as it is always //centered relatively to the monitor. This looks misaligned, adjust it here so it //is centered like the rest of the overview elements. let paddingSide = this.getPosition() == St.Side.LEFT ? 'left' : 'right'; let scaleFactor = Utils.getScaleFactor(); let style = offset ? 'padding-' + paddingSide + ':' + (offset / scaleFactor) + 'px;' : null; let searchEntry = Main.overview._overview._controls._searchEntry; searchEntry.get_parent().set_style(style); } }, _adjustForOverview: function() { let isFocusedMonitor = this.panelManager.checkIfFocusedMonitor(this.monitor); let isOverview = !!Main.overview.visibleTarget; let isOverviewFocusedMonitor = isOverview && isFocusedMonitor; let isShown = !isOverview || isOverviewFocusedMonitor; this.panelBox[isShown ? 'show' : 'hide'](); if (isOverview) { this._myPanelGhost[isOverviewFocusedMonitor ? 'show' : 'hide'](); if (isOverviewFocusedMonitor) { Utils.getPanelGhost().set_size(1, this.geom.position == St.Side.TOP ? 0 : 32); } } }, _resetGeometry: function() { this.geom = this.getGeometry(); this._setPanelGhostSize(); this._setPanelPosition(); this.taskbar.resetAppIcons(true); this.dynamicTransparency.updateExternalStyle(); if (this.intellihide && this.intellihide.enabled) { this.intellihide.reset(); } if (this.checkIfVertical()) { this.showAppsIconWrapper.realShowAppsIcon.toggleButton.set_width(this.geom.w); this._refreshVerticalAlloc(); this._setSearchEntryOffset(this.geom.w); } }, getGeometry: function() { let scaleFactor = Utils.getScaleFactor(); let panelBoxTheme = this.panelBox.get_theme_node(); let lrPadding = panelBoxTheme.get_padding(St.Side.RIGHT) + panelBoxTheme.get_padding(St.Side.LEFT); let topPadding = panelBoxTheme.get_padding(St.Side.TOP); let tbPadding = topPadding + panelBoxTheme.get_padding(St.Side.BOTTOM); let position = this.getPosition(); let length = PanelSettings.getPanelLength(Me.settings, this.monitor.index) / 100; let anchor = PanelSettings.getPanelAnchor(Me.settings, this.monitor.index); let anchorPlaceOnMonitor = 0; let gsTopPanelOffset = 0; let x = 0, y = 0; let w = 0, h = 0; const panelSize = PanelSettings.getPanelSize(Me.settings, this.monitor.index); this.dtpSize = panelSize * scaleFactor; if (Me.settings.get_boolean('stockgs-keep-top-panel') && Main.layoutManager.primaryMonitor == this.monitor) { gsTopPanelOffset = Main.layoutManager.panelBox.height - topPadding; } if (this.checkIfVertical()) { if (!Me.settings.get_boolean('group-apps')) { // add window title width and side padding of _dtpIconContainer when vertical this.dtpSize += Me.settings.get_int('group-apps-label-max-width') + AppIcons.DEFAULT_PADDING_SIZE * 2 / scaleFactor; } this.sizeFunc = 'get_preferred_height', this.fixedCoord = { c1: 'x1', c2: 'x2' }, this.varCoord = { c1: 'y1', c2: 'y2' }; w = this.dtpSize; h = this.monitor.height * length - tbPadding - gsTopPanelOffset; } else { this.sizeFunc = 'get_preferred_width'; this.fixedCoord = { c1: 'y1', c2: 'y2' }; this.varCoord = { c1: 'x1', c2: 'x2' }; w = this.monitor.width * length - lrPadding; h = this.dtpSize; } if (position == St.Side.TOP || position == St.Side.LEFT) { x = this.monitor.x; y = this.monitor.y + gsTopPanelOffset; } else if (position == St.Side.RIGHT) { x = this.monitor.x + this.monitor.width - this.dtpSize - lrPadding; y = this.monitor.y + gsTopPanelOffset; } else { //BOTTOM x = this.monitor.x; y = this.monitor.y + this.monitor.height - this.dtpSize - tbPadding; } if (this.checkIfVertical()) { let viewHeight = this.monitor.height - gsTopPanelOffset; if (anchor === Pos.MIDDLE) { anchorPlaceOnMonitor = (viewHeight - h) / 2; } else if (anchor === Pos.END) { anchorPlaceOnMonitor = viewHeight - h; } else { // Pos.START anchorPlaceOnMonitor = 0; } y = y + anchorPlaceOnMonitor; } else { if (anchor === Pos.MIDDLE) { anchorPlaceOnMonitor = (this.monitor.width - w) / 2; } else if (anchor === Pos.END) { anchorPlaceOnMonitor = this.monitor.width - w; } else { // Pos.START anchorPlaceOnMonitor = 0; } x = x + anchorPlaceOnMonitor; } return { x: x, y: y, w: w, h: h, lrPadding: lrPadding, tbPadding: tbPadding, position: position }; }, _setAllocationMap: function() { this.allocationMap = {}; let setMap = (name, actor, isBox) => this.allocationMap[name] = { actor: actor, isBox: isBox || 0, box: new Clutter.ActorBox() }; setMap(Pos.SHOW_APPS_BTN, this.showAppsIconWrapper.realShowAppsIcon); setMap(Pos.ACTIVITIES_BTN, this.statusArea.activities ? this.statusArea.activities.container : 0); setMap(Pos.LEFT_BOX, this._leftBox, 1); setMap(Pos.TASKBAR, this.taskbar.actor); setMap(Pos.CENTER_BOX, this._centerBox, 1); setMap(Pos.DATE_MENU, this.statusArea.dateMenu.container); setMap(Pos.SYSTEM_MENU, this.statusArea.aggregateMenu.container); setMap(Pos.RIGHT_BOX, this._rightBox, 1); setMap(Pos.DESKTOP_BTN, this._showDesktopButton); }, _mainPanelAllocate: function(actor, box, flags) { Utils.setAllocation(this.panel.actor, box, flags); }, vfunc_allocate: function(box, flags) { Utils.setAllocation(this, box, flags); let fixed = 0; let centeredMonitorGroup; let panelAlloc = new Clutter.ActorBox({ x1: 0, y1: 0, x2: this.geom.w, y2: this.geom.h }); let assignGroupSize = (group, update) => { group.size = 0; group.tlOffset = 0; group.brOffset = 0; group.elements.forEach(element => { if (!update) { element.box[this.fixedCoord.c1] = panelAlloc[this.fixedCoord.c1]; element.box[this.fixedCoord.c2] = panelAlloc[this.fixedCoord.c2]; element.natSize = element.actor[this.sizeFunc](-1)[1]; } if (!group.isCentered || Pos.checkIfCentered(element.position)) { group.size += element.natSize; } else if (element.position == Pos.STACKED_TL) {  group.tlOffset += element.natSize; } else { // Pos.STACKED_BR group.brOffset += element.natSize; } }); if (group.isCentered) { group.size += Math.max(group.tlOffset, group.brOffset) * 2; group.tlOffset = Math.max(group.tlOffset - group.brOffset, 0); } }; let allocateGroup = (group, tlLimit, brLimit) => { let startPosition = tlLimit; let currentPosition = 0; if (group.expandableIndex >= 0) { let availableSize = brLimit - tlLimit; let expandable = group.elements[group.expandableIndex]; let i = 0; let l = this._elementGroups.length; let tlSize = 0; let brSize = 0; if (centeredMonitorGroup && (centeredMonitorGroup != group || expandable.position != Pos.CENTERED_MONITOR)) { if (centeredMonitorGroup.index < group.index || (centeredMonitorGroup == group && expandable.position == Pos.STACKED_TL)) { i = centeredMonitorGroup.index; } else { l = centeredMonitorGroup.index; } } for (; i < l; ++i) { let refGroup = this._elementGroups[i]; if (i < group.index && (!refGroup.fixed || refGroup[this.varCoord.c2] > tlLimit)) { tlSize += refGroup.size; } else if (i > group.index && (!refGroup.fixed || refGroup[this.varCoord.c1] < brLimit)) { brSize += refGroup.size; } } if (group.isCentered) { availableSize -= Math.max(tlSize, brSize) * 2; } else { availableSize -= tlSize + brSize; } if (availableSize < group.size) { expandable.natSize -= (group.size - availableSize) * (group.isCentered && !Pos.checkIfCentered(expandable.position) ? .5 : 1); assignGroupSize(group, true); } } if (group.isCentered) { startPosition = tlLimit + (brLimit - tlLimit - group.size) * .5; } else if (group.position == Pos.STACKED_BR) { startPosition = brLimit - group.size; } currentPosition = group.tlOffset + startPosition; group.elements.forEach(element => { element.box[this.varCoord.c1] = Math.round(currentPosition); element.box[this.varCoord.c2] = Math.round((currentPosition += element.natSize)); if (element.isBox) { return element.actor.allocate(element.box, flags, true); } Utils.allocate(element.actor, element.box, flags, false); }); group[this.varCoord.c1] = startPosition; group[this.varCoord.c2] = currentPosition; group.fixed = 1; ++fixed; }; Utils.allocate(this.panel.actor, panelAlloc, flags); this._elementGroups.forEach(group => { group.fixed = 0; assignGroupSize(group); if (group.position == Pos.CENTERED_MONITOR) { centeredMonitorGroup = group; } }); if (centeredMonitorGroup) { allocateGroup(centeredMonitorGroup, panelAlloc[this.varCoord.c1], panelAlloc[this.varCoord.c2]); } let iterations = 0; //failsafe while (fixed < this._elementGroups.length && ++iterations < 10) { for (let i = 0, l = this._elementGroups.length; i < l; ++i) { let group = this._elementGroups[i]; if (group.fixed) { continue; } let prevGroup = this._elementGroups[i - 1]; let nextGroup = this._elementGroups[i + 1]; let prevLimit = prevGroup && prevGroup.fixed ? prevGroup[this.varCoord.c2] : centeredMonitorGroup && group.index > centeredMonitorGroup.index ? centeredMonitorGroup[this.varCoord.c2] : panelAlloc[this.varCoord.c1]; let nextLimit = nextGroup && nextGroup.fixed ? nextGroup[this.varCoord.c1] : centeredMonitorGroup && group.index < centeredMonitorGroup.index ? centeredMonitorGroup[this.varCoord.c1] : panelAlloc[this.varCoord.c2]; if (group.position == Pos.STACKED_TL) { allocateGroup(group, panelAlloc[this.varCoord.c1], nextLimit); } else if (group.position == Pos.STACKED_BR) { allocateGroup(group, prevLimit, panelAlloc[this.varCoord.c2]); } else if ((!prevGroup || prevGroup.fixed) && (!nextGroup || nextGroup.fixed)) { // CENTERED allocateGroup(group, prevLimit, nextLimit); } } } if (this.geom.position == St.Side.TOP) { let childBoxLeftCorner = new Clutter.ActorBox(); let childBoxRightCorner = new Clutter.ActorBox(); let currentCornerSize = this.cornerSize; let panelAllocFixedSize = box[this.fixedCoord.c2] - box[this.fixedCoord.c1]; [ , this.cornerSize] = this.panel._leftCorner.actor[this.sizeFunc](-1); childBoxLeftCorner[this.varCoord.c1] = 0; childBoxLeftCorner[this.varCoord.c2] = this.cornerSize; childBoxLeftCorner[this.fixedCoord.c1] = panelAllocFixedSize; childBoxLeftCorner[this.fixedCoord.c2] = panelAllocFixedSize + this.cornerSize; childBoxRightCorner[this.varCoord.c1] = box[this.varCoord.c2] - this.cornerSize; childBoxRightCorner[this.varCoord.c2] = box[this.varCoord.c2]; childBoxRightCorner[this.fixedCoord.c1] = panelAllocFixedSize; childBoxRightCorner[this.fixedCoord.c2] = panelAllocFixedSize + this.cornerSize; Utils.allocate(this.panel._leftCorner.actor, childBoxLeftCorner, flags); Utils.allocate(this.panel._rightCorner.actor, childBoxRightCorner, flags); if (this.cornerSize != currentCornerSize) { this._setPanelClip(); } } }, _setPanelPosition: function() { let clipContainer = this.panelBox.get_parent(); this.set_size(this.geom.w, this.geom.h); clipContainer.set_position(this.geom.x, this.geom.y); this._setVertical(this.panel.actor, this.checkIfVertical()); // styles for theming Object.keys(St.Side).forEach(p => { let cssName = 'dashtopanel' + p.charAt(0) + p.slice(1).toLowerCase(); this.panel.actor[(St.Side[p] == this.geom.position ? 'add' : 'remove') + '_style_class_name'](cssName); }); this._setPanelClip(clipContainer); Main.layoutManager._updateHotCorners(); Main.layoutManager._updatePanelBarrier(this); }, _setPanelClip: function(clipContainer) { clipContainer = clipContainer || this.panelBox.get_parent(); this._timeoutsHandler.add([T7, 0, () => Utils.setClip(clipContainer, clipContainer.x, clipContainer.y, this.panelBox.width, this.panelBox.height + this.cornerSize)]); }, _onButtonPress: function(actor, event) { let type = event.type(); let isPress = type == Clutter.EventType.BUTTON_PRESS; let button = isPress ? event.get_button() : -1; let [stageX, stageY] = event.get_coords(); if (button == 3 && global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, stageX, stageY) == this.panel.actor) { //right click on an empty part of the panel, temporarily borrow and display the showapps context menu Main.layoutManager.setDummyCursorGeometry(stageX, stageY, 0, 0); this.showAppsIconWrapper.createMenu(); this.showAppsIconWrapper._menu.sourceActor = Main.layoutManager.dummyCursor; this.showAppsIconWrapper.popupMenu(); return Clutter.EVENT_STOP; } else if (Main.modalCount > 0 || event.get_source() != actor || (!isPress && type != Clutter.EventType.TOUCH_BEGIN) || (isPress && button != 1)) { return Clutter.EVENT_PROPAGATE; } let params = this.checkIfVertical() ? [stageY, 'y', 'height'] : [stageX, 'x', 'width']; let dragWindow = this._getDraggableWindowForPosition.apply(this, params.concat(['maximized_' + this.getOrientation() + 'ly'])); if (!dragWindow) return Clutter.EVENT_PROPAGATE; global.display.begin_grab_op(dragWindow, Meta.GrabOp.MOVING, false, /* pointer grab */ true, /* frame action */ button, event.get_state(), event.get_time(), stageX, stageY); return Clutter.EVENT_STOP; }, _getDraggableWindowForPosition: function(stageCoord, coord, dimension, maximizedProp) { let workspace = Utils.getCurrentWorkspace(); let allWindowsByStacking = global.display.sort_windows_by_stacking( workspace.list_windows() ).reverse(); return Utils.find(allWindowsByStacking, metaWindow => { let rect = metaWindow.get_frame_rect(); return metaWindow.get_monitor() == this.monitor.index && metaWindow.showing_on_its_workspace() && metaWindow.get_window_type() != Meta.WindowType.DESKTOP && metaWindow[maximizedProp] && stageCoord > rect[coord] && stageCoord < rect[coord] + rect[dimension]; }); }, _onBoxActorAdded: function(box) { if (this.checkIfVertical()) { this._setVertical(box, true); } }, _refreshVerticalAlloc: function() { this._setVertical(this._centerBox, true); this._setVertical(this._rightBox, true); this._formatVerticalClock(); }, _setVertical: function(actor, isVertical) { let _set = (actor, isVertical) => { if (!actor || actor instanceof Dash.DashItemContainer || actor instanceof Taskbar.TaskbarItemContainer) { return; } if (actor instanceof St.BoxLayout) { actor.vertical = isVertical; } else if ((actor._delegate || actor) instanceof PanelMenu.ButtonBox && actor != this.statusArea.appMenu) { let child = actor.get_first_child(); if (isVertical && !actor.visible && !actor._dtpVisibleId) { this._unmappedButtons.push(actor); actor._dtpVisibleId = actor.connect('notify::visible', () => { this._disconnectVisibleId(actor); this._refreshVerticalAlloc(); }); actor._dtpDestroyId = actor.connect('destroy', () => this._disconnectVisibleId(actor)); } if (child) { let [, natWidth] = actor.get_preferred_width(-1); child.x_align = Clutter.ActorAlign[isVertical ? 'CENTER' : 'START']; actor.set_width(isVertical ? this.dtpSize : -1); isVertical = isVertical && (natWidth > this.dtpSize); actor[(isVertical ? 'add' : 'remove') + '_style_class_name']('vertical'); } } actor.get_children().forEach(c => _set(c, isVertical)); }; _set(actor, false); _set(actor, isVertical); }, _disconnectVisibleId: function(actor) { actor.disconnect(actor._dtpVisibleId); actor.disconnect(actor._dtpDestroyId); delete actor._dtpVisibleId; delete actor._dtpDestroyId; this._unmappedButtons.splice(this._unmappedButtons.indexOf(actor), 1); }, _setAppmenuVisible: function(isVisible) { let parent; let appMenu = this.statusArea.appMenu; if(appMenu) parent = appMenu.container.get_parent(); if (parent) { parent.remove_child(appMenu.container); } if (isVisible && appMenu) { this._leftBox.insert_child_above(appMenu.container, null); } }, _formatVerticalClock: function() { // https://github.com/GNOME/gnome-desktop/blob/master/libgnome-desktop/gnome-wall-clock.c#L310 if (this.statusArea.dateMenu) { let datetime = this.statusArea.dateMenu._clock.clock; let datetimeParts = datetime.split(' '); let time = datetimeParts[1]; let clockText = this.statusArea.dateMenu._clockDisplay.clutter_text; let setClockText = text => { let stacks = text instanceof Array; let separator = '\n‧‧\n'; clockText.set_text((stacks ? text.join(separator) : text).trim()); clockText.set_use_markup(stacks); clockText.get_allocation_box(); return !clockText.get_layout().is_ellipsized(); }; if (clockText.ellipsize == Pango.EllipsizeMode.NONE) { //on gnome-shell 3.36.4, the clockdisplay isn't ellipsize anymore, so set it back clockText.ellipsize = Pango.EllipsizeMode.END; } if (!time) { datetimeParts = datetime.split(' '); time = datetimeParts.pop(); datetimeParts = [datetimeParts.join(' '), time]; } if (!setClockText(datetime) && !setClockText(datetimeParts) && !setClockText(time)) { let timeParts = time.split('∶'); if (!this._clockFormat) { this._clockFormat = Me.desktopSettings.get_string('clock-format'); } if (this._clockFormat == '12h') { timeParts.push.apply(timeParts, timeParts.pop().split(' ')); } setClockText(timeParts); } } }, _setShowDesktopButton: function (add) { if (add) { if(this._showDesktopButton) return; this._showDesktopButton = new St.Bin({ style_class: 'showdesktop-button', reactive: true, can_focus: true, // x_fill: true, // y_fill: true, track_hover: true }); this._setShowDesktopButtonStyle(); this._showDesktopButton.connect('button-press-event', () => this._onShowDesktopButtonPress()); this._showDesktopButton.connect('enter-event', () => { this._showDesktopButton.add_style_class_name(this._getBackgroundBrightness() ? 'showdesktop-button-light-hovered' : 'showdesktop-button-dark-hovered'); if (Me.settings.get_boolean('show-showdesktop-hover')) { this._timeoutsHandler.add([T4, Me.settings.get_int('show-showdesktop-delay'), () => { this._hiddenDesktopWorkspace = Utils.DisplayWrapper.getWorkspaceManager().get_active_workspace(); this._toggleWorkspaceWindows(true, this._hiddenDesktopWorkspace); }]); } }); this._showDesktopButton.connect('leave-event', () => { this._showDesktopButton.remove_style_class_name(this._getBackgroundBrightness() ? 'showdesktop-button-light-hovered' : 'showdesktop-button-dark-hovered'); if (Me.settings.get_boolean('show-showdesktop-hover')) { if (this._timeoutsHandler.getId(T4)) { this._timeoutsHandler.remove(T4); } else if (this._hiddenDesktopWorkspace) { this._toggleWorkspaceWindows(false, this._hiddenDesktopWorkspace); }  } }); this.panel.actor.add_child(this._showDesktopButton); } else { if(!this._showDesktopButton) return; this.panel.actor.remove_child(this._showDesktopButton); this._showDesktopButton.destroy(); this._showDesktopButton = null; } }, _setShowDesktopButtonStyle: function() { let rgb = this._getBackgroundBrightness() ? "rgba(55, 55, 55, .2)" : "rgba(200, 200, 200, .2)"; let isLineCustom = Me.settings.get_boolean('desktop-line-use-custom-color'); rgb = isLineCustom ? Me.settings.get_string('desktop-line-custom-color') : rgb; if (this._showDesktopButton) { let buttonSize = Me.settings.get_int('showdesktop-button-width') + 'px;'; let isVertical = this.checkIfVertical(); let sytle = "border: 0 solid " + rgb + ";"; sytle += isVertical ? 'border-top-width:1px;height:' + buttonSize : 'border-left-width:1px;width:' + buttonSize; this._showDesktopButton.set_style(sytle); this._showDesktopButton[(isVertical ? 'x' : 'y') + '_expand'] = true; } }, // _getBackgroundBrightness: return true if panel has a bright background color _getBackgroundBrightness: function() { return Utils.checkIfColorIsBright(this.dynamicTransparency.backgroundColorRgb); }, _toggleWorkspaceWindows: function(hide, workspace) { let time = Me.settings.get_int('show-showdesktop-time') * .001; workspace.list_windows().forEach(w => { if (!w.minimized) { let tweenOpts = { opacity: hide ? 0 : 255, time: time, transition: 'easeOutQuad' }; Utils.animateWindowOpacity(w.get_compositor_private(), tweenOpts); } }); }, _onShowDesktopButtonPress: function() { let label = 'trackerFocusApp'; this._signalsHandler.removeWithLabel(label); this._timeoutsHandler.remove(T5); if(this._restoreWindowList && this._restoreWindowList.length) { this._timeoutsHandler.remove(T4); let current_workspace = Utils.DisplayWrapper.getWorkspaceManager().get_active_workspace(); let windows = current_workspace.list_windows(); this._restoreWindowList.forEach(function(w) { if(windows.indexOf(w) > -1) Main.activateWindow(w); }); this._restoreWindowList = null; } else { let current_workspace = Utils.DisplayWrapper.getWorkspaceManager().get_active_workspace(); let windows = current_workspace.list_windows().filter(function (w) { return w.showing_on_its_workspace() && !w.skip_taskbar; }); windows = global.display.sort_windows_by_stacking(windows); windows.forEach(function(w) { w.minimize(); }); this._restoreWindowList = windows; this._timeoutsHandler.add([T5, 20, () => this._signalsHandler.addWithLabel( label, [ tracker, 'notify::focus-app', () => this._restoreWindowList = null ] )]); } Main.overview.hide(); }, _onPanelMouseScroll: function(actor, event) { let scrollAction = Me.settings.get_string('scroll-panel-action'); let direction = Utils.getMouseScrollDirection(event); if (!this._checkIfIgnoredScrollSource(event.get_source()) && !this._timeoutsHandler.getId(T6)) { if (direction && scrollAction === 'SWITCH_WORKSPACE') { let args = [global.display]; //adjust for horizontal workspaces if (Utils.DisplayWrapper.getWorkspaceManager().layout_rows === 1) { direction = direction == 'up' ? 'left' : 'right'; } //gnome-shell < 3.30 needs an additional "screen" param global.screen ? args.push(global.screen) : 0; let showWsPopup = Me.settings.get_boolean('scroll-panel-show-ws-popup'); showWsPopup ? 0 : Main.wm._workspaceSwitcherPopup = { display: () => {} }; Main.wm._showWorkspaceSwitcher.apply(Main.wm, args.concat([0, { get_name: () => 'switch---' + direction }])); showWsPopup ? 0 : Main.wm._workspaceSwitcherPopup = null; } else if (direction && scrollAction === 'CYCLE_WINDOWS') { let windows = this.taskbar.getAppInfos().reduce((ws, appInfo) => ws.concat(appInfo.windows), []); Utils.activateSiblingWindow(windows, direction); } else if (scrollAction === 'CHANGE_VOLUME' && !event.is_pointer_emulated()) { var proto = Volume.Indicator.prototype; var func = proto._handleScrollEvent || proto.vfunc_scroll_event || proto._onScrollEvent; func.call(Main.panel.statusArea.aggregateMenu._volume, 0, event); } else { return; } var scrollDelay = Me.settings.get_int('scroll-panel-delay'); if (scrollDelay) { this._timeoutsHandler.add([T6, scrollDelay, () => {}]); } } }, _checkIfIgnoredScrollSource: function(source) { let ignoredConstr = ['WorkspaceIndicator']; return source.get_parent()._dtpIgnoreScroll || ignoredConstr.indexOf(source.constructor.name) >= 0; }, _initProgressManager: function() { if(!this.progressManager && (Me.settings.get_boolean('progress-show-bar') || Me.settings.get_boolean('progress-show-count'))) this.progressManager = new Progress.ProgressManager(); }, }); var dtpSecondaryPanel = Utils.defineClass({ Name: 'DashToPanel-SecondaryPanel', Extends: St.Widget, _init: function(params) { this.callParent('_init', params); }, vfunc_allocate: function(box, flags) { Utils.setAllocation(this, box, flags); } }); var dtpSecondaryAggregateMenu = Utils.defineClass({ Name: 'DashToPanel-SecondaryAggregateMenu', Extends: PanelMenu.Button, _init: function() { this.callParent('_init', 0.0, C_("System menu in the top bar", "System"), false); Utils.wrapActor(this); this.menu.actor.add_style_class_name('aggregate-menu'); let menuLayout = new Panel.AggregateLayout(); this.menu.box.set_layout_manager(menuLayout); this._indicators = new St.BoxLayout({ style_class: 'panel-status-indicators-box' }); this.actor.add_child(this._indicators); this._power = new imports.ui.status.power.Indicator(); this._volume = new imports.ui.status.volume.Indicator(); this._brightness = new imports.ui.status.brightness.Indicator(); this._system = new imports.ui.status.system.Indicator(); if (Config.PACKAGE_VERSION >= '3.28') { this._thunderbolt = new imports.ui.status.thunderbolt.Indicator(); this._indicators.add_child(Utils.getIndicators(this._thunderbolt)); } if (Config.PACKAGE_VERSION < '3.37') { this._screencast = new imports.ui.status.screencast.Indicator(); this._indicators.add_child(Utils.getIndicators(this._screencast)); } if (Config.PACKAGE_VERSION >= '3.24') { this._nightLight = new imports.ui.status.nightLight.Indicator(); this._indicators.add_child(Utils.getIndicators(this._nightLight)); } if (Config.HAVE_NETWORKMANAGER && Config.PACKAGE_VERSION >= '3.24') { this._network = new imports.ui.status.network.NMApplet(); this._indicators.add_child(Utils.getIndicators(this._network)); } if (Config.HAVE_BLUETOOTH) { this._bluetooth = new imports.ui.status.bluetooth.Indicator(); this._indicators.add_child(Utils.getIndicators(this._bluetooth)); } this._indicators.add_child(Utils.getIndicators(this._volume)); this._indicators.add_child(Utils.getIndicators(this._power)); this.menu.addMenuItem(this._volume.menu); this._volume._volumeMenu._readOutput(); this._volume._volumeMenu._readInput(); this.menu.addMenuItem(this._brightness.menu); this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); if (this._network) { this.menu.addMenuItem(this._network.menu); } if (this._bluetooth) { this.menu.addMenuItem(this._bluetooth.menu); } this.menu.addMenuItem(this._power.menu); this._power._sync(); if (this._nightLight) { this.menu.addMenuItem(this._nightLight.menu); } this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); this.menu.addMenuItem(this._system.menu); menuLayout.addSizeChild(this._power.menu.actor); menuLayout.addSizeChild(this._system.menu.actor); }, });