Files
dash-to-panel/panel.js
Charles Gagnon d04104ff8a Format code
2025-01-31 11:49:22 -05:00

1511 lines
47 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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 <http://www.gnu.org/licenses/>.
*
* 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.
*/
import Clutter from 'gi://Clutter'
import GObject from 'gi://GObject'
import * as AppIcons from './appIcons.js'
import * as Utils from './utils.js'
import * as Taskbar from './taskbar.js'
import * as TaskbarItemContainer from './taskbar.js'
import * as Pos from './panelPositions.js'
import * as PanelSettings from './panelSettings.js'
import * as PanelStyle from './panelStyle.js'
import * as Main from 'resource:///org/gnome/shell/ui/main.js'
import * as Dash from 'resource:///org/gnome/shell/ui/dash.js'
import * as CtrlAltTab from 'resource:///org/gnome/shell/ui/ctrlAltTab.js'
import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js'
import St from 'gi://St'
import Meta from 'gi://Meta'
import Pango from 'gi://Pango'
import * as DND from 'resource:///org/gnome/shell/ui/dnd.js'
import Shell from 'gi://Shell'
import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'
import * as DateMenu from 'resource:///org/gnome/shell/ui/dateMenu.js'
import * as Volume from 'resource:///org/gnome/shell/ui/status/volume.js'
import * as Progress from './progress.js'
import * as Intellihide from './intellihide.js'
import * as Transparency from './transparency.js'
import { SETTINGS, DESKTOPSETTINGS, PERSISTENTSTORAGE } from './extension.js'
import {
gettext as _,
InjectionManager,
} from 'resource:///org/gnome/shell/extensions/extension.js'
let tracker = Shell.WindowTracker.get_default()
export const panelBoxes = ['_leftBox', '_centerBox', '_rightBox']
//timeout names
const T2 = 'startIntellihideTimeout'
const T4 = 'showDesktopTimeout'
const T5 = 'trackerFocusAppTimeout'
const T6 = 'scrollPanelDelayTimeout'
const T7 = 'waitPanelBoxAllocation'
export const Panel = GObject.registerClass(
{},
class Panel extends St.Widget {
_init(panelManager, monitor, panelBox, isStandalone) {
super._init({ layout_manager: new Clutter.BinLayout() })
this._timeoutsHandler = new Utils.TimeoutsHandler()
this._signalsHandler = new Utils.GlobalSignalsHandler()
this._injectionManager = new InjectionManager()
this.panelManager = panelManager
this.panelStyle = new PanelStyle.PanelStyle()
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 ||
(SETTINGS.get_boolean('stockgs-keep-top-panel') &&
monitor == panelManager.dtpPrimaryMonitor)
this._sessionStyle = null
this._unmappedButtons = []
this._elementGroups = []
let systemMenuInfo = Utils.getSystemMenuInfo()
if (isStandalone) {
this.panel = new SecondaryPanel({ name: 'panel', reactive: true })
this.statusArea = this.panel.statusArea = {}
//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(
systemMenuInfo.name,
systemMenuInfo.constructor,
this.panel,
)
this._setPanelMenu('dateMenu', DateMenu.DateMenuButton, this.panel)
this._setPanelMenu(
'activities',
Main.panel.statusArea.activities.constructor,
this.panel,
)
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', systemMenuInfo.name, 'dateMenu'].forEach((b) => {
let container = this.statusArea[b].container
let parent = container.get_parent()
let siblings = parent.get_children()
let index = siblings.indexOf(container)
container._dtpOriginalParent = parent
container._dtpOriginalIndex =
index && index == siblings.length - 1 ? -1 : index
parent ? parent.remove_child(container) : null
this.panel.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.add_child(this.showAppsIconWrapper.realShowAppsIcon)
this.panel._delegate = this
this.add_child(this.panel)
if (Main.panel._onButtonPress || Main.panel._tryDragWindow) {
this._signalsHandler.add([
this.panel,
['button-press-event', 'touch-event'],
this._onButtonPress.bind(this),
])
}
if (Main.panel._onKeyPress) {
this._signalsHandler.add([
this.panel,
'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() {
let { name: systemMenuName } = Utils.getSystemMenuInfo()
if (
this.statusArea[systemMenuName] &&
this.statusArea[systemMenuName]._volumeOutput
) {
Utils.getIndicators(
this.statusArea[systemMenuName]._volumeOutput,
)._dtpIgnoreScroll = 1
}
this.geom = this.getGeometry()
this._setPanelPosition()
if (!this.isStandalone) {
this._injectionManager.overrideMethod(
Object.getPrototypeOf(this.panel),
'vfunc_allocate',
() => (box) => this._mainPanelAllocate(box),
)
// remove the extra space before the clock when the message-indicator is displayed
if (DateMenu.IndicatorPad) {
this._injectionManager.overrideMethod(
DateMenu.IndicatorPad.prototype,
'vfunc_get_preferred_width',
() => () => [0, 0],
)
this._injectionManager.overrideMethod(
DateMenu.IndicatorPad.prototype,
'vfunc_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()
}
this.menuManager._oldChangeMenu = this.menuManager._changeMenu
this.menuManager._changeMenu = (menu) => {
if (!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.add_child(this.taskbar.actor)
this._setShowDesktopButton(true)
this._setAllocationMap()
this.panel.add_style_class_name(
'dashtopanelMainPanel ' + this.getOrientation(),
)
this._timeoutsHandler.add([
T2,
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',
() => this.panel.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)
}
},
],
[
this.statusArea.activities,
'captured-event',
(actor, e) => {
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)
}
},
],
[
this._centerBox,
'child-added',
() => this._onBoxActorAdded(this._centerBox),
],
[
this._rightBox,
'child-added',
() => this._onBoxActorAdded(this._rightBox),
],
[this.panel, '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()
}
},
])
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() {
this.panelStyle.disable()
this._timeoutsHandler.destroy()
this._signalsHandler.destroy()
this.panel.remove_child(this.taskbar.actor)
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._unmappedButtons.forEach((a) => this._disconnectVisibleId(a))
if (this.statusArea.dateMenu) {
this.statusArea.dateMenu._clockDisplay.text =
this.statusArea.dateMenu._clock.clock
this.statusArea.dateMenu._clockDisplay.clutter_text.set_width(-1)
if (this._dateMenuIndicatorPadContraints) {
let indicatorPad = this.statusArea.dateMenu
.get_first_child()
.get_first_child()
this._dateMenuIndicatorPadContraints.forEach((c) =>
indicatorPad.add_constraint(c),
)
}
}
this._setVertical(this.panel, false)
this._setVertical(this._centerBox, false)
this._setVertical(this._rightBox, false)
let { name: systemMenuName } = Utils.getSystemMenuInfo()
if (!this.isStandalone) {
;['vertical', 'horizontal', 'dashtopanelMainPanel'].forEach((c) =>
this.panel.remove_style_class_name(c),
)
if (!Main.sessionMode.isLocked) {
;['activities', systemMenuName, 'dateMenu'].forEach((b) => {
let container = this.statusArea[b].container
let originalParent = container._dtpOriginalParent
this.panel.remove_child(container)
originalParent &&
originalParent.insert_child_at_index(
container,
Math.min(
container._dtpOriginalIndex,
originalParent.get_children().length - 1,
),
)
delete container._dtpOriginalParent
delete container._dtpOriginalIndex
})
}
this._setShowDesktopButton(false)
delete Utils.getIndicators(
this.statusArea[systemMenuName]._volumeOutput,
)._dtpIgnoreScroll
this._injectionManager.clear()
this.panel._delegate = this.panel
} else {
this._removePanelMenu('dateMenu')
this._removePanelMenu(systemMenuName)
this._removePanelMenu('activities')
}
Main.ctrlAltTabManager.removeGroup(this)
}
getPosition() {
let position = PanelSettings.getPanelPosition(
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() {
let position = this.getPosition()
return position == St.Side.LEFT || position == St.Side.RIGHT
}
getOrientation() {
return this.checkIfVertical() ? 'vertical' : 'horizontal'
}
updateElementPositions() {
let panelPositions =
this.panelManager.panelsElementPositions[this.monitor.index] ||
Pos.defaults
this._updateGroupedElements(panelPositions)
this.panel.hide()
this.panel.show()
}
_updateGroupedElements(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
}
})
}
_bindSettingsChanges() {
let isVertical = this.checkIfVertical()
this._signalsHandler.add(
[
SETTINGS,
[
'changed::panel-sizes',
'changed::appicon-margin-todesktop',
'changed::appicon-margin-toscreenborder',
'changed::group-apps',
],
() => this._resetGeometry(),
],
[
SETTINGS,
['changed::appicon-margin', 'changed::appicon-padding'],
() => this.taskbar.resetAppIcons(),
],
[
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(),
],
[
DESKTOPSETTINGS,
'changed::clock-format',
() => {
this._clockFormat = null
if (isVertical) {
this._formatVerticalClock()
}
},
],
[
SETTINGS,
'changed::progress-show-bar',
() => this._initProgressManager(),
],
[
SETTINGS,
'changed::progress-show-count',
() => this._initProgressManager(),
],
)
if (isVertical) {
this._signalsHandler.add([
SETTINGS,
'changed::group-apps-label-max-width',
() => this._resetGeometry(),
])
}
}
_setPanelMenu(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(propName) {
if (this.statusArea[propName]) {
let parent = this.statusArea[propName].container.get_parent()
if (parent) {
parent.remove_child(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)
PERSISTENTSTORAGE[propName].push(panelMenu)
this.statusArea[propName] = null
}
}
_getPanelMenu(propName, constr) {
PERSISTENTSTORAGE[propName] = PERSISTENTSTORAGE[propName] || []
if (!PERSISTENTSTORAGE[propName].length) {
PERSISTENTSTORAGE[propName].push(new constr())
}
return PERSISTENTSTORAGE[propName].pop()
}
_adjustForOverview() {
let isFocusedMonitor = this.panelManager.checkIfFocusedMonitor(
this.monitor,
)
let isOverview = !!Main.overview.visibleTarget
let isOverviewFocusedMonitor = isOverview && isFocusedMonitor
let isShown = !isOverview || isOverviewFocusedMonitor
let actorData = Utils.getTrackedActorData(this.panelBox)
// prevent the "chrome" to update the panelbox visibility while in overview
actorData.trackFullscreen = !isOverview
this.panelBox[isShown ? 'show' : 'hide']()
}
_resetGeometry() {
this.geom = this.getGeometry()
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()
}
}
getGeometry() {
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(SETTINGS, this.monitor.index) / 100
let anchor = PanelSettings.getPanelAnchor(SETTINGS, this.monitor.index)
let anchorPlaceOnMonitor = 0
let gsTopPanelOffset = 0
let x = 0,
y = 0
let w = 0,
h = 0
const panelSize =
PanelSettings.getPanelSize(SETTINGS, this.monitor.index) +
SETTINGS.get_int('appicon-margin-todesktop') +
SETTINGS.get_int('appicon-margin-toscreenborder')
this.dtpSize = panelSize * scaleFactor
if (
SETTINGS.get_boolean('stockgs-keep-top-panel') &&
Main.layoutManager.primaryMonitor == this.monitor
) {
gsTopPanelOffset = Main.layoutManager.panelBox.height - topPadding
}
if (this.checkIfVertical()) {
if (!SETTINGS.get_boolean('group-apps')) {
// add window title width and side padding of _dtpIconContainer when vertical
this.dtpSize +=
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,
y,
w,
h,
lrPadding,
tbPadding,
position,
}
}
_setAllocationMap() {
this.allocationMap = {}
let setMap = (name, actor) =>
(this.allocationMap[name] = {
actor: actor,
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)
setMap(Pos.TASKBAR, this.taskbar.actor)
setMap(Pos.CENTER_BOX, this._centerBox)
setMap(Pos.DATE_MENU, this.statusArea.dateMenu.container)
setMap(
Pos.SYSTEM_MENU,
this.statusArea[Utils.getSystemMenuInfo().name].container,
)
setMap(Pos.RIGHT_BOX, this._rightBox)
setMap(Pos.DESKTOP_BTN, this._showDesktopButton)
}
_mainPanelAllocate(box) {
this.panel.set_allocation(box)
}
vfunc_allocate(box) {
this.set_allocation(box)
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)
? 0.5
: 1)
assignGroupSize(group, true)
}
}
if (group.isCentered) {
startPosition = tlLimit + (brLimit - tlLimit - group.size) * 0.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),
)
element.actor.allocate(element.box)
})
group[this.varCoord.c1] = startPosition
group[this.varCoord.c2] = currentPosition
group.fixed = 1
++fixed
}
this.panel.allocate(panelAlloc)
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)
}
}
}
}
_setPanelPosition() {
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, this.checkIfVertical())
// styles for theming
Object.keys(St.Side).forEach((p) => {
let cssName = 'dashtopanel' + p.charAt(0) + p.slice(1).toLowerCase()
this.panel[
(St.Side[p] == this.geom.position ? 'add' : 'remove') +
'_style_class_name'
](cssName)
})
this._setPanelClip(clipContainer)
Main.layoutManager._updateHotCorners()
Main.layoutManager._updatePanelBarrier(this)
}
_setPanelClip(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,
),
])
}
_onButtonPress(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
) {
//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.popupMenu(Main.layoutManager.dummyCursor)
return Clutter.EVENT_STOP
} else {
const targetActor = global.stage.get_event_actor(event)
if (
Main.modalCount > 0 ||
targetActor != 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(
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(box) {
if (this.checkIfVertical()) {
this._setVertical(box, true)
}
}
_refreshVerticalAlloc() {
this._setVertical(this._centerBox, true)
this._setVertical(this._rightBox, true)
this._formatVerticalClock()
}
_setVertical(actor, isVertical) {
let _set = (actor, isVertical) => {
if (
!actor ||
actor instanceof Dash.DashItemContainer ||
actor instanceof TaskbarItemContainer.TaskbarItemContainer
) {
return
}
if (actor instanceof St.BoxLayout) {
actor.vertical = isVertical
} else if (
actor != this.statusArea.appMenu &&
((actor._delegate || actor) instanceof PanelMenu.ButtonBox ||
actor == this.statusArea.quickSettings)
) {
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)
if (isVertical) _set(actor, isVertical)
}
_disconnectVisibleId(actor) {
actor.disconnect(actor._dtpVisibleId)
actor.disconnect(actor._dtpDestroyId)
delete actor._dtpVisibleId
delete actor._dtpDestroyId
this._unmappedButtons.splice(this._unmappedButtons.indexOf(actor), 1)
}
_formatVerticalClock() {
// 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, useTimeSeparator) => {
let stacks = text instanceof Array
let separator = `\n<span size="8192"> ${useTimeSeparator ? '‧‧' : '—'} </span>\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
}
clockText.natural_width = this.dtpSize
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 = DESKTOPSETTINGS.get_string('clock-format')
}
if (this._clockFormat == '12h') {
timeParts.push.apply(timeParts, timeParts.pop().split(' '))
}
setClockText(timeParts, true)
}
}
}
_setShowDesktopButton(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('touch-event', (actor, event) => {
if (event.type() == Clutter.EventType.TOUCH_BEGIN) {
this._onShowDesktopButtonPress()
}
})
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 (SETTINGS.get_boolean('show-showdesktop-hover')) {
this._timeoutsHandler.add([
T4,
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 (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.add_child(this._showDesktopButton)
} else {
if (!this._showDesktopButton) return
this.panel.remove_child(this._showDesktopButton)
this._showDesktopButton.destroy()
this._showDesktopButton = null
}
}
_setShowDesktopButtonStyle() {
let rgb = this._getBackgroundBrightness()
? 'rgba(55, 55, 55, .2)'
: 'rgba(200, 200, 200, .2)'
let isLineCustom = SETTINGS.get_boolean('desktop-line-use-custom-color')
rgb = isLineCustom
? SETTINGS.get_string('desktop-line-custom-color')
: rgb
if (this._showDesktopButton) {
let buttonSize = 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() {
return Utils.checkIfColorIsBright(
this.dynamicTransparency.backgroundColorRgb,
)
}
_toggleWorkspaceWindows(hide, workspace) {
let time = SETTINGS.get_int('show-showdesktop-time') * 0.001
workspace.list_windows().forEach((w) => {
if (!w.minimized && !w.customJS_ding) {
let tweenOpts = {
opacity: hide ? 0 : 255,
time: time,
transition: 'easeOutQuad',
}
Utils.animateWindowOpacity(w.get_compositor_private(), tweenOpts)
}
})
}
_onShowDesktopButtonPress() {
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(actor, event) {
let scrollAction = SETTINGS.get_string('scroll-panel-action')
let direction = Utils.getMouseScrollDirection(event)
const targetActor = global.stage.get_event_actor(event)
if (
!this._checkIfIgnoredScrollSource(targetActor) &&
!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 = 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()
) {
let proto = Volume.OutputIndicator.prototype
let func =
proto._handleScrollEvent ||
proto.vfunc_scroll_event ||
proto._onScrollEvent
let indicator =
Main.panel.statusArea[Utils.getSystemMenuInfo().name]._volumeOutput
if (indicator.quickSettingsItems)
// new quick settings menu in gnome-shell > 42
func(indicator.quickSettingsItems[0], event)
else func.call(indicator, 0, event)
} else {
return
}
const scrollDelay = SETTINGS.get_int('scroll-panel-delay')
if (scrollDelay) {
this._timeoutsHandler.add([T6, scrollDelay, () => {}])
}
}
}
_checkIfIgnoredScrollSource(source) {
let ignoredConstr = ['WorkspaceIndicator']
return (
source.get_parent()._dtpIgnoreScroll ||
ignoredConstr.indexOf(source.constructor.name) >= 0
)
}
_initProgressManager() {
const progressVisible = SETTINGS.get_boolean('progress-show-bar')
const countVisible = SETTINGS.get_boolean('progress-show-count')
const pm = this.progressManager
if (!pm && (progressVisible || countVisible))
this.progressManager = new Progress.ProgressManager()
else if (pm)
Object.keys(pm._entriesByDBusName).forEach((k) =>
pm._entriesByDBusName[k].setCountVisible(countVisible),
)
}
},
)
export const SecondaryPanel = GObject.registerClass(
{},
class SecondaryPanel extends St.Widget {
_init(params) {
super._init(params)
}
vfunc_allocate(box) {
this.set_allocation(box)
}
},
)