mirror of
https://github.com/morgan9e/dash-to-panel
synced 2026-04-14 00:04:17 +09:00
Add simple message tray notifications handling
gh-1695
This commit is contained in:
493
src/appIcons.js
493
src/appIcons.js
@@ -43,7 +43,6 @@ import { EventEmitter } from 'resource:///org/gnome/shell/misc/signals.js'
|
||||
import * as Utils from './utils.js'
|
||||
import * as PanelSettings from './panelSettings.js'
|
||||
import * as Taskbar from './taskbar.js'
|
||||
import * as Progress from './progress.js'
|
||||
import {
|
||||
DTP_EXTENSION,
|
||||
SETTINGS,
|
||||
@@ -121,6 +120,7 @@ export const TaskbarAppIcon = GObject.registerClass(
|
||||
_init(appInfo, panel, iconParams, previewMenu, iconAnimator) {
|
||||
this.dtpPanel = panel
|
||||
this._nWindows = 0
|
||||
this._notifications = {}
|
||||
this.window = appInfo.window
|
||||
this.isLauncher = appInfo.isLauncher
|
||||
this._previewMenu = previewMenu
|
||||
@@ -132,6 +132,7 @@ export const TaskbarAppIcon = GObject.registerClass(
|
||||
|
||||
super._init(appInfo.app, iconParams)
|
||||
|
||||
this._signalsHandler = new Utils.GlobalSignalsHandler()
|
||||
this._timeoutsHandler = new Utils.TimeoutsHandler()
|
||||
|
||||
// Fix touchscreen issues before the listener is added by the parent constructor.
|
||||
@@ -176,6 +177,7 @@ export const TaskbarAppIcon = GObject.registerClass(
|
||||
})
|
||||
|
||||
this.remove_child(this._iconContainer)
|
||||
this.icon._iconBin.set_pivot_point(0.5, 0.5)
|
||||
|
||||
this._dtpIconContainer.add_child(this._iconContainer)
|
||||
|
||||
@@ -191,11 +193,6 @@ export const TaskbarAppIcon = GObject.registerClass(
|
||||
this._updateWindowTitle()
|
||||
this._updateWindowTitleStyle()
|
||||
|
||||
this._scaleFactorChangedId = Utils.getStageTheme().connect(
|
||||
'changed',
|
||||
() => this._updateWindowTitleStyle(),
|
||||
)
|
||||
|
||||
box.add_child(this._dtpIconContainer)
|
||||
box.add_child(this._windowTitle)
|
||||
|
||||
@@ -223,35 +220,33 @@ export const TaskbarAppIcon = GObject.registerClass(
|
||||
this._setAppIconPadding()
|
||||
this._setAppIconStyle()
|
||||
this._showDots()
|
||||
this._numberOverlay()
|
||||
|
||||
this._focusWindowChangedId = global.display.connect(
|
||||
'notify::focus-window',
|
||||
this._onFocusAppChanged.bind(this),
|
||||
)
|
||||
|
||||
this._windowEnteredMonitorId = this._windowLeftMonitorId = 0
|
||||
this._stateChangedId = this.app.connect(
|
||||
'windows-changed',
|
||||
this.onWindowsChanged.bind(this),
|
||||
this._signalsHandler.add(
|
||||
[
|
||||
Utils.getStageTheme(),
|
||||
'changed',
|
||||
this._updateWindowTitleStyle.bind(this),
|
||||
],
|
||||
[
|
||||
global.display,
|
||||
'notify::focus-window',
|
||||
this._onFocusAppChanged.bind(this),
|
||||
],
|
||||
[this.app, 'windows-changed', this.onWindowsChanged.bind(this)],
|
||||
)
|
||||
|
||||
if (!this.window) {
|
||||
if (SETTINGS.get_boolean('isolate-monitors')) {
|
||||
this._windowEnteredMonitorId =
|
||||
Utils.DisplayWrapper.getScreen().connect(
|
||||
'window-entered-monitor',
|
||||
this.onWindowEnteredOrLeft.bind(this),
|
||||
)
|
||||
this._windowLeftMonitorId = Utils.DisplayWrapper.getScreen().connect(
|
||||
'window-left-monitor',
|
||||
this._signalsHandler.add([
|
||||
Utils.DisplayWrapper.getScreen(),
|
||||
['window-entered-monitor', 'window-left-monitor'],
|
||||
this.onWindowEnteredOrLeft.bind(this),
|
||||
)
|
||||
])
|
||||
}
|
||||
|
||||
this._titleWindowChangeId = 0
|
||||
this._minimizedWindowChangeId = 0
|
||||
|
||||
this._fullscreenId = Utils.DisplayWrapper.getScreen().connect(
|
||||
this._signalsHandler.add([
|
||||
Utils.DisplayWrapper.getScreen(),
|
||||
'in-fullscreen-changed',
|
||||
() => {
|
||||
if (
|
||||
@@ -263,188 +258,110 @@ export const TaskbarAppIcon = GObject.registerClass(
|
||||
this._displayProperIndicator()
|
||||
}
|
||||
},
|
||||
)
|
||||
])
|
||||
} else {
|
||||
this._titleWindowChangeId = this.window.connect(
|
||||
'notify::title',
|
||||
this._updateWindowTitle.bind(this),
|
||||
)
|
||||
|
||||
this._minimizedWindowChangeId = this.window.connect(
|
||||
'notify::minimized',
|
||||
this._updateWindowTitleStyle.bind(this),
|
||||
this._signalsHandler.add(
|
||||
[this.window, 'notify::title', this._updateWindowTitle.bind(this)],
|
||||
[
|
||||
this.window,
|
||||
'notify::minimized',
|
||||
this._updateWindowTitleStyle.bind(this),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
this._scrollEventId = this.connect(
|
||||
'scroll-event',
|
||||
this._onMouseScroll.bind(this),
|
||||
)
|
||||
|
||||
this._overviewWindowDragEndId = Main.overview.connect(
|
||||
'window-drag-end',
|
||||
this._onOverviewWindowDragEnd.bind(this),
|
||||
)
|
||||
|
||||
this._switchWorkspaceId = global.window_manager.connect(
|
||||
'switch-workspace',
|
||||
this._onSwitchWorkspace.bind(this),
|
||||
)
|
||||
|
||||
this._hoverChangeId = this.connect('notify::hover', () =>
|
||||
this._onAppIconHoverChanged(),
|
||||
)
|
||||
|
||||
this._hoverChangeId2 = this.connect('notify::hover', () =>
|
||||
this._onAppIconHoverChanged_GtkWorkaround(),
|
||||
)
|
||||
this._pressedChangedId = this.connect('notify::pressed', () =>
|
||||
this._onAppIconPressedChanged_GtkWorkaround(),
|
||||
)
|
||||
|
||||
this._dtpSettingsSignalIds = [
|
||||
SETTINGS.connect(
|
||||
'changed::animate-appicon-hover',
|
||||
this._onAnimateAppiconHoverChanged.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
this._signalsHandler.add(
|
||||
[this, 'scroll-event', this._onMouseScroll.bind(this)],
|
||||
[
|
||||
Main.overview,
|
||||
'window-drag-end',
|
||||
this._onOverviewWindowDragEnd.bind(this),
|
||||
],
|
||||
[
|
||||
global.window_manager,
|
||||
'switch-workspace',
|
||||
this._onSwitchWorkspace.bind(this),
|
||||
],
|
||||
[
|
||||
this,
|
||||
'notify::hover',
|
||||
() => {
|
||||
this._onAppIconHoverChanged()
|
||||
this._onAppIconHoverChanged_GtkWorkaround()
|
||||
},
|
||||
],
|
||||
[
|
||||
this,
|
||||
'notify::pressed',
|
||||
this._onAppIconPressedChanged_GtkWorkaround.bind(this),
|
||||
],
|
||||
[
|
||||
this.dtpPanel.panelManager.notificationsMonitor,
|
||||
`update-${this.app.id}`,
|
||||
this._handleNotifications.bind(this),
|
||||
],
|
||||
[
|
||||
SETTINGS,
|
||||
'changed::animate-appicon-hover',
|
||||
() => {
|
||||
this._onAnimateAppiconHoverChanged()
|
||||
this._onAppIconHoverHighlightChanged()
|
||||
},
|
||||
],
|
||||
[
|
||||
SETTINGS,
|
||||
[
|
||||
'changed::highlight-appicon-hover',
|
||||
'changed::highlight-appicon-hover-background-color',
|
||||
'changed::highlight-appicon-pressed-background-color',
|
||||
'changed::highlight-appicon-hover-border-radius',
|
||||
],
|
||||
this._onAppIconHoverHighlightChanged.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::highlight-appicon-hover',
|
||||
this._onAppIconHoverHighlightChanged.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::highlight-appicon-hover-background-color',
|
||||
this._onAppIconHoverHighlightChanged.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::highlight-appicon-pressed-background-color',
|
||||
this._onAppIconHoverHighlightChanged.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
],
|
||||
[
|
||||
SETTINGS,
|
||||
[
|
||||
'changed::dot-position',
|
||||
'changed::dot-size',
|
||||
'changed::dot-style-focused',
|
||||
'changed::dot-style-unfocused',
|
||||
'changed::dot-color-dominant',
|
||||
'changed::dot-color-override',
|
||||
'changed::dot-color-1',
|
||||
'changed::dot-color-2',
|
||||
'changed::dot-color-3',
|
||||
'changed::dot-color-4',
|
||||
'changed::dot-color-unfocused-different',
|
||||
'changed::dot-color-unfocused-1',
|
||||
'changed::dot-color-unfocused-2',
|
||||
'changed::dot-color-unfocused-3',
|
||||
'changed::dot-color-unfocused-4',
|
||||
'changed::focus-highlight',
|
||||
'changed::focus-highlight-dominant',
|
||||
'changed::focus-highlight-color',
|
||||
'changed::focus-highlight-opacity',
|
||||
'changed::group-apps-underline-unfocused',
|
||||
],
|
||||
this._settingsChangeRefresh.bind(this),
|
||||
],
|
||||
[
|
||||
SETTINGS,
|
||||
[
|
||||
'changed::group-apps-label-font-size',
|
||||
'changed::group-apps-label-font-weight',
|
||||
'changed::group-apps-label-font-color',
|
||||
'changed::group-apps-label-font-color-minimized',
|
||||
'changed::group-apps-label-max-width',
|
||||
'changed::group-apps-use-fixed-width',
|
||||
],
|
||||
this._updateWindowTitleStyle.bind(this),
|
||||
],
|
||||
[
|
||||
SETTINGS,
|
||||
'changed::highlight-appicon-hover-border-radius',
|
||||
this._onAppIconHoverHighlightChanged.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::dot-position',
|
||||
this._settingsChangeRefresh.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::dot-size',
|
||||
this._settingsChangeRefresh.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::dot-style-focused',
|
||||
this._settingsChangeRefresh.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::dot-style-unfocused',
|
||||
this._settingsChangeRefresh.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::dot-color-dominant',
|
||||
this._settingsChangeRefresh.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::dot-color-override',
|
||||
this._settingsChangeRefresh.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::dot-color-1',
|
||||
this._settingsChangeRefresh.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::dot-color-2',
|
||||
this._settingsChangeRefresh.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::dot-color-3',
|
||||
this._settingsChangeRefresh.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::dot-color-4',
|
||||
this._settingsChangeRefresh.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::dot-color-unfocused-different',
|
||||
this._settingsChangeRefresh.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::dot-color-unfocused-1',
|
||||
this._settingsChangeRefresh.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::dot-color-unfocused-2',
|
||||
this._settingsChangeRefresh.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::dot-color-unfocused-3',
|
||||
this._settingsChangeRefresh.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::dot-color-unfocused-4',
|
||||
this._settingsChangeRefresh.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::focus-highlight',
|
||||
this._settingsChangeRefresh.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::focus-highlight-dominant',
|
||||
this._settingsChangeRefresh.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::focus-highlight-color',
|
||||
this._settingsChangeRefresh.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::focus-highlight-opacity',
|
||||
this._settingsChangeRefresh.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::group-apps-label-font-size',
|
||||
this._updateWindowTitleStyle.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::group-apps-label-font-weight',
|
||||
this._updateWindowTitleStyle.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::group-apps-label-font-color',
|
||||
this._updateWindowTitleStyle.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::group-apps-label-font-color-minimized',
|
||||
this._updateWindowTitleStyle.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::group-apps-label-max-width',
|
||||
this._updateWindowTitleStyle.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::group-apps-use-fixed-width',
|
||||
this._updateWindowTitleStyle.bind(this),
|
||||
),
|
||||
SETTINGS.connect(
|
||||
'changed::group-apps-underline-unfocused',
|
||||
this._settingsChangeRefresh.bind(this),
|
||||
),
|
||||
]
|
||||
|
||||
this._dtpSettingsSignalIds = this._dtpSettingsSignalIds.concat([
|
||||
SETTINGS.connect('changed::highlight-appicon-hover-border-radius', () =>
|
||||
this._setIconStyle(this._isFocusedWindow()),
|
||||
),
|
||||
])
|
||||
|
||||
this._progressIndicator = new Progress.ProgressIndicator(
|
||||
this,
|
||||
panel.progressManager,
|
||||
() => this._setIconStyle(this._isFocusedWindow()),
|
||||
],
|
||||
)
|
||||
|
||||
this._numberOverlay()
|
||||
}
|
||||
|
||||
getDragActor() {
|
||||
@@ -520,60 +437,9 @@ export const TaskbarAppIcon = GObject.registerClass(
|
||||
super._onDestroy()
|
||||
|
||||
this._timeoutsHandler.destroy()
|
||||
this._signalsHandler.destroy()
|
||||
|
||||
this._previewMenu.close(true)
|
||||
|
||||
// Disconect global signals
|
||||
if (this._stateChangedId > 0) {
|
||||
this.app.disconnect(this._stateChangedId)
|
||||
this._stateChangedId = 0
|
||||
}
|
||||
|
||||
if (this._overviewWindowDragEndId)
|
||||
Main.overview.disconnect(this._overviewWindowDragEndId)
|
||||
|
||||
if (this._focusWindowChangedId)
|
||||
global.display.disconnect(this._focusWindowChangedId)
|
||||
|
||||
if (this._fullscreenId)
|
||||
Utils.DisplayWrapper.getScreen().disconnect(this._fullscreenId)
|
||||
|
||||
if (this._titleWindowChangeId)
|
||||
this.window.disconnect(this._titleWindowChangeId)
|
||||
|
||||
if (this._minimizedWindowChangeId)
|
||||
this.window.disconnect(this._minimizedWindowChangeId)
|
||||
|
||||
if (this._windowEnteredMonitorId) {
|
||||
Utils.DisplayWrapper.getScreen().disconnect(
|
||||
this._windowEnteredMonitorId,
|
||||
)
|
||||
Utils.DisplayWrapper.getScreen().disconnect(this._windowLeftMonitorId)
|
||||
}
|
||||
|
||||
if (this._switchWorkspaceId)
|
||||
global.window_manager.disconnect(this._switchWorkspaceId)
|
||||
|
||||
if (this._scaleFactorChangedId)
|
||||
Utils.getStageTheme().disconnect(this._scaleFactorChangedId)
|
||||
|
||||
if (this._hoverChangeId) {
|
||||
this.disconnect(this._hoverChangeId)
|
||||
}
|
||||
if (this._hoverChangeId2) {
|
||||
this.disconnect(this._hoverChangeId2)
|
||||
}
|
||||
if (this._pressedChangedId) {
|
||||
this.disconnect(this._pressedChangedId)
|
||||
}
|
||||
|
||||
if (this._scrollEventId) {
|
||||
this.disconnect(this._scrollEventId)
|
||||
}
|
||||
|
||||
for (let i = 0; i < this._dtpSettingsSignalIds.length; ++i) {
|
||||
SETTINGS.disconnect(this._dtpSettingsSignalIds[i])
|
||||
}
|
||||
}
|
||||
|
||||
onWindowsChanged() {
|
||||
@@ -1009,16 +875,18 @@ export const TaskbarAppIcon = GObject.registerClass(
|
||||
if (!this._menu) {
|
||||
this._menu = new TaskbarSecondaryMenu(this, this.dtpPanel.geom.position)
|
||||
this._menu.setApp(this.app)
|
||||
this._menu.connect('open-state-changed', (menu, isPoppedUp) => {
|
||||
if (!isPoppedUp) this._onMenuPoppedDown()
|
||||
else this._previewMenu.close(true)
|
||||
})
|
||||
let id = Main.overview.connect('hiding', () => {
|
||||
this._menu.close()
|
||||
})
|
||||
this.connect('destroy', () => {
|
||||
Main.overview.disconnect(id)
|
||||
})
|
||||
|
||||
this._signalsHandler.add(
|
||||
[
|
||||
this._menu,
|
||||
'open-state-changed',
|
||||
(menu, isPoppedUp) => {
|
||||
if (!isPoppedUp) this._onMenuPoppedDown()
|
||||
else this._previewMenu.close(true)
|
||||
},
|
||||
],
|
||||
[Main.overview, 'hiding', () => this._menu.close()],
|
||||
)
|
||||
|
||||
// We want to keep the item hovered while the menu is up
|
||||
this._menu.blockSourceEvents = true
|
||||
@@ -1731,25 +1599,89 @@ export const TaskbarAppIcon = GObject.registerClass(
|
||||
cr.$dispose()
|
||||
}
|
||||
|
||||
_handleNotifications(notificationsMonitor, state) {
|
||||
if (!this._nWindows && !this.window) {
|
||||
delete this._notifications.total
|
||||
return
|
||||
}
|
||||
|
||||
let urgent =
|
||||
'urgent' in state ? state.urgent : this._notifications.urgent || false
|
||||
let formatCount = (count) => {
|
||||
if (!count) return 0
|
||||
|
||||
return count > 10 ? '10+' : count
|
||||
}
|
||||
|
||||
if ('count-visible' in state)
|
||||
this._notifications.countVisible = state['count-visible']
|
||||
|
||||
if ('count' in state) this._notifications.count = state.count
|
||||
|
||||
if ('trayCount' in state)
|
||||
this._notifications.trayCount = this._checkIfFocusedApp()
|
||||
? 0
|
||||
: state.trayCount
|
||||
|
||||
this._notifications.total =
|
||||
SETTINGS.get_boolean('progress-show-count') &&
|
||||
((this._notifications.countVisible || 0) &&
|
||||
(this._notifications.count || 0)) +
|
||||
(this._notifications.trayCount || 0)
|
||||
|
||||
urgent = urgent && !!this._notifications.total
|
||||
|
||||
if (urgent !== this._notifications.urgent)
|
||||
this.iconAnimator[`${urgent ? 'add' : 'remove'}Animation`](
|
||||
this.icon._iconBin,
|
||||
'dance',
|
||||
)
|
||||
|
||||
this._notifications.urgent = urgent
|
||||
|
||||
// restore hotkeys number if no more notifications
|
||||
this._maybeToggleNumberOverlay(
|
||||
formatCount(this._notifications.total) ||
|
||||
this._numberHotkeysOverlayLabel,
|
||||
)
|
||||
}
|
||||
|
||||
_maybeToggleNumberOverlay(labelNumber) {
|
||||
let visible = this._numberOverlayBin.visible
|
||||
let shouldBeVisible =
|
||||
this._hotkeysOverlayActive || this._notifications.total
|
||||
|
||||
this._numberOverlayLabel[
|
||||
`${this._notifications.total ? 'add' : 'remove'}_style_class_name`
|
||||
]('notification-badge')
|
||||
|
||||
if (
|
||||
shouldBeVisible &&
|
||||
labelNumber != this._numberOverlayLabel.get_text()
|
||||
) {
|
||||
this._numberOverlayLabel.set_text(labelNumber.toString())
|
||||
this._updateNumberOverlay()
|
||||
}
|
||||
|
||||
if (visible && !shouldBeVisible) this._numberOverlayBin.hide()
|
||||
else if (!visible && shouldBeVisible) this._numberOverlayBin.show()
|
||||
}
|
||||
|
||||
_numberOverlay() {
|
||||
// Add label for a Hot-Key visual aid
|
||||
// Add label for a numeric visual aid (hotkeys or notification)
|
||||
this._numberOverlayLabel = new St.Label({ style_class: 'badge' })
|
||||
this._numberOverlayBin = new St.Bin({
|
||||
child: this._numberOverlayLabel,
|
||||
y: 2,
|
||||
})
|
||||
this._numberOverlayLabel.add_style_class_name('number-overlay')
|
||||
this._numberOverlayOrder = -1
|
||||
this._numberHotkeysOverlayLabel = -1
|
||||
this._numberOverlayBin.hide()
|
||||
|
||||
this._dtpIconContainer.add_child(this._numberOverlayBin)
|
||||
}
|
||||
|
||||
updateHotkeyNumberOverlay() {
|
||||
this.updateNumberOverlay(this._numberOverlayBin, true)
|
||||
}
|
||||
|
||||
updateNumberOverlay(bin, fixedSize) {
|
||||
_updateNumberOverlay() {
|
||||
// We apply an overall scale factor that might come from a HiDPI monitor.
|
||||
// Clutter dimensions are in physical pixels, but CSS measures are in logical
|
||||
// pixels, so make sure to consider the scale.
|
||||
@@ -1760,7 +1692,6 @@ export const TaskbarAppIcon = GObject.registerClass(
|
||||
Math.max(12, 0.3 * natWidth) / Utils.getScaleFactor(),
|
||||
)
|
||||
let size = Math.round(font_size * 1.3)
|
||||
let label = bin.child
|
||||
let style =
|
||||
'font-size: ' +
|
||||
font_size +
|
||||
@@ -1772,25 +1703,25 @@ export const TaskbarAppIcon = GObject.registerClass(
|
||||
size +
|
||||
'px;'
|
||||
|
||||
if (fixedSize || label.get_text().length == 1) {
|
||||
if (this._numberOverlayLabel.get_text().length == 1) {
|
||||
style += 'width: ' + size + 'px;'
|
||||
} else {
|
||||
style += 'padding: 0 2px;'
|
||||
}
|
||||
|
||||
bin.x = 2
|
||||
label.set_style(style)
|
||||
this._numberOverlayLabel.set_style(style)
|
||||
}
|
||||
|
||||
setNumberOverlay(number) {
|
||||
this._numberOverlayOrder = number
|
||||
this._numberOverlayLabel.set_text(number.toString())
|
||||
setHotkeysNumberOverlayLabel(number) {
|
||||
this._numberHotkeysOverlayLabel = number
|
||||
}
|
||||
|
||||
toggleNumberOverlay(activate) {
|
||||
if (activate && this._numberOverlayOrder > -1)
|
||||
this._numberOverlayBin.show()
|
||||
else this._numberOverlayBin.hide()
|
||||
toggleHotkeysNumberOverlay(activate) {
|
||||
this._hotkeysOverlayActive =
|
||||
activate && this._numberHotkeysOverlayLabel > -1
|
||||
|
||||
if (!this._notifications.total)
|
||||
this._maybeToggleNumberOverlay(this._numberHotkeysOverlayLabel)
|
||||
}
|
||||
|
||||
handleDragOver(source) {
|
||||
|
||||
159
src/notificationsMonitor.js
Normal file
159
src/notificationsMonitor.js
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
import Gio from 'gi://Gio'
|
||||
import Shell from 'gi://Shell'
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js'
|
||||
import * as MessageTray from 'resource:///org/gnome/shell/ui/messageTray.js'
|
||||
import { EventEmitter } from 'resource:///org/gnome/shell/misc/signals.js'
|
||||
|
||||
import * as Utils from './utils.js'
|
||||
|
||||
const tracker = Shell.WindowTracker.get_default()
|
||||
const knownCorrespondances = {
|
||||
'org.gnome.Evolution': [/^org\.gnome\.[eE]volution([.-].+)?$/g],
|
||||
}
|
||||
|
||||
export const NotificationsMonitor = class extends EventEmitter {
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
this._signalsHandler = new Utils.GlobalSignalsHandler()
|
||||
|
||||
// pretty much useless, but might as well keep it for now
|
||||
this._launcherEntryId = Gio.DBus.session.signal_subscribe(
|
||||
null, // sender
|
||||
'com.canonical.Unity.LauncherEntry', // iface
|
||||
'Update', // member
|
||||
null, // path
|
||||
null, // arg0
|
||||
Gio.DBusSignalFlags.NONE,
|
||||
(
|
||||
connection,
|
||||
senderName,
|
||||
objectPath,
|
||||
interfaceName,
|
||||
signalName,
|
||||
parameters,
|
||||
) => this._handleLauncherUpdate(senderName, parameters),
|
||||
)
|
||||
|
||||
this._signalsHandler.add([
|
||||
tracker,
|
||||
'notify::focus-app',
|
||||
() => {
|
||||
// reset notifications from message tray on app focus
|
||||
if (tracker.focus_app)
|
||||
this.dispatch(tracker.focus_app.id, { trayCount: 0 }, true)
|
||||
},
|
||||
])
|
||||
this._acquireUnityDBus()
|
||||
|
||||
this._checkNotifications()
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this._launcherEntryId)
|
||||
Gio.DBus.session.signal_unsubscribe(this._launcherEntryId)
|
||||
|
||||
this._releaseUnityDBus()
|
||||
this._signalsHandler.destroy()
|
||||
}
|
||||
|
||||
dispatch(appId, state, ignoreMapping) {
|
||||
// depending of the notification source, some app id end
|
||||
// with ".desktop" and some don't ¯\_(ツ)_/¯
|
||||
appId = appId.replace('.desktop', '')
|
||||
|
||||
// some app have different source app id, deamon and such,
|
||||
// but it maps to a desktop app so match those here
|
||||
if (!ignoreMapping && !knownCorrespondances[appId])
|
||||
appId =
|
||||
Object.keys(knownCorrespondances).find((k) =>
|
||||
knownCorrespondances[k].some((regex) => appId.match(regex)),
|
||||
) || appId
|
||||
|
||||
this.emit(`update-${appId}.desktop`, state)
|
||||
}
|
||||
|
||||
_acquireUnityDBus() {
|
||||
if (!this._unityBusId) {
|
||||
this._unityBusId = Gio.DBus.session.own_name(
|
||||
'com.canonical.Unity',
|
||||
Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
_releaseUnityDBus() {
|
||||
if (this._unityBusId) {
|
||||
Gio.DBus.session.unown_name(this._unityBusId)
|
||||
this._unityBusId = 0
|
||||
}
|
||||
}
|
||||
|
||||
_handleLauncherUpdate(senderName, parameters) {
|
||||
if (!senderName || !parameters) return
|
||||
|
||||
let [appUri, properties] = parameters.deep_unpack()
|
||||
let appId = appUri.replace(/(^\w+:|^)\/\//, '')
|
||||
let updates = {}
|
||||
|
||||
// https://wiki.ubuntu.com/Unity/LauncherAPI#Low_level_DBus_API:_com.canonical.Unity.LauncherEntry
|
||||
for (let property in properties)
|
||||
updates[property] = properties[property].unpack()
|
||||
|
||||
this.dispatch(appId, updates)
|
||||
}
|
||||
|
||||
_checkNotifications() {
|
||||
let addSource = (tray, source) => {
|
||||
let appId = source?._appId || source?.app?.id
|
||||
|
||||
if (!appId) return
|
||||
|
||||
this._signalsHandler.addWithLabel(appId, [
|
||||
source,
|
||||
'notify::count',
|
||||
() =>
|
||||
this.dispatch(appId, {
|
||||
trayCount: source.count, // source.unseenCount might be less annoying
|
||||
urgent: !!source.notifications.find(
|
||||
(n) => n.urgency > MessageTray.Urgency.NORMAL,
|
||||
),
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
this._signalsHandler.add(
|
||||
[Main.messageTray, 'source-added', addSource],
|
||||
[
|
||||
Main.messageTray,
|
||||
'source-removed',
|
||||
(tray, source) => {
|
||||
if (source?._appId)
|
||||
this._signalsHandler.removeWithLabel(source._appId)
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
Main.messageTray.getSources().forEach((s) => addSource(null, s))
|
||||
}
|
||||
}
|
||||
@@ -373,7 +373,7 @@ export const Overview = class {
|
||||
this._hotKeysEnabled = true
|
||||
|
||||
if (SETTINGS.get_string('hotkeys-overlay-combo') === 'ALWAYS')
|
||||
this.taskbar.toggleNumberOverlay(true)
|
||||
this.taskbar.toggleHotkeysNumberOverlay(true)
|
||||
}
|
||||
|
||||
_disableHotKeys() {
|
||||
@@ -418,7 +418,7 @@ export const Overview = class {
|
||||
|
||||
this._hotKeysEnabled = false
|
||||
|
||||
this.taskbar.toggleNumberOverlay(false)
|
||||
this.taskbar.toggleHotkeysNumberOverlay(false)
|
||||
}
|
||||
|
||||
_optionalNumberOverlay() {
|
||||
@@ -435,8 +435,8 @@ export const Overview = class {
|
||||
SETTINGS.get_boolean('hot-keys') &&
|
||||
SETTINGS.get_string('hotkeys-overlay-combo') === 'ALWAYS'
|
||||
)
|
||||
this.taskbar.toggleNumberOverlay(true)
|
||||
else this.taskbar.toggleNumberOverlay(false)
|
||||
this.taskbar.toggleHotkeysNumberOverlay(true)
|
||||
else this.taskbar.toggleHotkeysNumberOverlay(false)
|
||||
},
|
||||
],
|
||||
[SETTINGS, 'changed::shortcut-num-keys', () => this._resetHotkeys()],
|
||||
@@ -468,7 +468,7 @@ export const Overview = class {
|
||||
if (hotkey_option === 'NEVER') return
|
||||
|
||||
if (hotkey_option === 'TEMPORARILY' || overlayFromShortcut)
|
||||
this.taskbar.toggleNumberOverlay(true)
|
||||
this.taskbar.toggleHotkeysNumberOverlay(true)
|
||||
|
||||
this._panel.intellihide.revealAndHold(Intellihide.Hold.TEMPORARY)
|
||||
|
||||
@@ -484,7 +484,7 @@ export const Overview = class {
|
||||
timeout,
|
||||
() => {
|
||||
if (hotkey_option != 'ALWAYS') {
|
||||
this.taskbar.toggleNumberOverlay(false)
|
||||
this.taskbar.toggleHotkeysNumberOverlay(false)
|
||||
}
|
||||
|
||||
this._panel.intellihide.release(Intellihide.Hold.TEMPORARY)
|
||||
|
||||
28
src/panel.js
28
src/panel.js
@@ -48,7 +48,6 @@ 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'
|
||||
@@ -359,8 +358,6 @@ export const Panel = GObject.registerClass(
|
||||
// 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() {
|
||||
@@ -377,8 +374,6 @@ export const Panel = GObject.registerClass(
|
||||
|
||||
this.dynamicTransparency.destroy()
|
||||
|
||||
this.progressManager.destroy()
|
||||
|
||||
this.taskbar.destroy()
|
||||
this.showAppsIconWrapper.destroy()
|
||||
|
||||
@@ -610,16 +605,6 @@ export const Panel = GObject.registerClass(
|
||||
}
|
||||
},
|
||||
],
|
||||
[
|
||||
SETTINGS,
|
||||
'changed::progress-show-bar',
|
||||
() => this._initProgressManager(),
|
||||
],
|
||||
[
|
||||
SETTINGS,
|
||||
'changed::progress-show-count',
|
||||
() => this._initProgressManager(),
|
||||
],
|
||||
)
|
||||
|
||||
if (isVertical) {
|
||||
@@ -1494,19 +1479,6 @@ export const Panel = GObject.registerClass(
|
||||
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),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ import * as BoxPointer from 'resource:///org/gnome/shell/ui/boxpointer.js'
|
||||
import * as LookingGlass from 'resource:///org/gnome/shell/ui/lookingGlass.js'
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js'
|
||||
import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js'
|
||||
import { NotificationsMonitor } from './notificationsMonitor.js'
|
||||
import * as Layout from 'resource:///org/gnome/shell/ui/layout.js'
|
||||
import { InjectionManager } from 'resource:///org/gnome/shell/extensions/extension.js'
|
||||
import { SETTINGS } from './extension.js'
|
||||
@@ -133,6 +134,8 @@ export const PanelManager = class {
|
||||
|
||||
if (reset) return
|
||||
|
||||
this.notificationsMonitor = new NotificationsMonitor()
|
||||
|
||||
this._desktopIconsUsableArea =
|
||||
new DesktopIconsIntegration.DesktopIconsUsableAreaClass()
|
||||
|
||||
@@ -337,6 +340,8 @@ export const PanelManager = class {
|
||||
|
||||
this._setKeyBindings(false)
|
||||
|
||||
this.notificationsMonitor.destroy()
|
||||
|
||||
this._signalsHandler.destroy()
|
||||
|
||||
Main.layoutManager._updateHotCorners = this._oldUpdateHotCorners
|
||||
@@ -716,6 +721,9 @@ export const IconAnimator = class {
|
||||
if (this._started && this._count === 0) {
|
||||
this._timeline.stop()
|
||||
}
|
||||
|
||||
if (name == 'dance') target.rotation_angle_z = 0
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,18 +255,18 @@ const Preferences = class {
|
||||
this._setMonitorsInfo()
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
let maybeGoToPage = () => {
|
||||
let targetPageName = settings.get_string('target-prefs-page')
|
||||
|
||||
|
||||
if (targetPageName) {
|
||||
window.set_visible_page_name(targetPageName)
|
||||
settings.set_string('target-prefs-page', '')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
settings.connect('changed::target-prefs-page', maybeGoToPage)
|
||||
|
||||
|
||||
maybeGoToPage()
|
||||
})
|
||||
}
|
||||
|
||||
699
src/progress.js
699
src/progress.js
@@ -1,699 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
import Cairo from 'cairo'
|
||||
import Gio from 'gi://Gio'
|
||||
import Clutter from 'gi://Clutter'
|
||||
import Pango from 'gi://Pango'
|
||||
import St from 'gi://St'
|
||||
import * as Utils from './utils.js'
|
||||
import { SETTINGS } from './extension.js'
|
||||
|
||||
import { EventEmitter } from 'resource:///org/gnome/shell/misc/signals.js'
|
||||
|
||||
export const ProgressManager = class extends EventEmitter {
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
this._entriesByDBusName = {}
|
||||
|
||||
this._launcher_entry_dbus_signal_id = Gio.DBus.session.signal_subscribe(
|
||||
null, // sender
|
||||
'com.canonical.Unity.LauncherEntry', // iface
|
||||
null, // member
|
||||
null, // path
|
||||
null, // arg0
|
||||
Gio.DBusSignalFlags.NONE,
|
||||
this._onEntrySignalReceived.bind(this),
|
||||
)
|
||||
|
||||
this._dbus_name_owner_changed_signal_id = Gio.DBus.session.signal_subscribe(
|
||||
'org.freedesktop.DBus', // sender
|
||||
'org.freedesktop.DBus', // interface
|
||||
'NameOwnerChanged', // member
|
||||
'/org/freedesktop/DBus', // path
|
||||
null, // arg0
|
||||
Gio.DBusSignalFlags.NONE,
|
||||
this._onDBusNameOwnerChanged.bind(this),
|
||||
)
|
||||
|
||||
this._acquireUnityDBus()
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this._launcher_entry_dbus_signal_id) {
|
||||
Gio.DBus.session.signal_unsubscribe(this._launcher_entry_dbus_signal_id)
|
||||
}
|
||||
|
||||
if (this._dbus_name_owner_changed_signal_id) {
|
||||
Gio.DBus.session.signal_unsubscribe(
|
||||
this._dbus_name_owner_changed_signal_id,
|
||||
)
|
||||
}
|
||||
|
||||
this._releaseUnityDBus()
|
||||
}
|
||||
|
||||
size() {
|
||||
return Object.keys(this._entriesByDBusName).length
|
||||
}
|
||||
|
||||
lookupByDBusName(dbusName) {
|
||||
return Object.hasOwn(this._entriesByDBusName, dbusName)
|
||||
? this._entriesByDBusName[dbusName]
|
||||
: null
|
||||
}
|
||||
|
||||
lookupById(appId) {
|
||||
let ret = []
|
||||
for (let dbusName in this._entriesByDBusName) {
|
||||
let entry = this._entriesByDBusName[dbusName]
|
||||
if (entry && entry.appId() == appId) {
|
||||
ret.push(entry)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
addEntry(entry) {
|
||||
let existingEntry = this.lookupByDBusName(entry.dbusName())
|
||||
if (existingEntry) {
|
||||
existingEntry.update(entry)
|
||||
} else {
|
||||
this._entriesByDBusName[entry.dbusName()] = entry
|
||||
this.emit('progress-entry-added', entry)
|
||||
}
|
||||
}
|
||||
|
||||
removeEntry(entry) {
|
||||
delete this._entriesByDBusName[entry.dbusName()]
|
||||
this.emit('progress-entry-removed', entry)
|
||||
}
|
||||
|
||||
_acquireUnityDBus() {
|
||||
if (!this._unity_bus_id) {
|
||||
Gio.DBus.session.own_name(
|
||||
'com.canonical.Unity',
|
||||
Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
_releaseUnityDBus() {
|
||||
if (this._unity_bus_id) {
|
||||
Gio.DBus.session.unown_name(this._unity_bus_id)
|
||||
this._unity_bus_id = 0
|
||||
}
|
||||
}
|
||||
|
||||
_onEntrySignalReceived(
|
||||
connection,
|
||||
sender_name,
|
||||
object_path,
|
||||
interface_name,
|
||||
signal_name,
|
||||
parameters,
|
||||
) {
|
||||
if (!parameters || !signal_name) return
|
||||
|
||||
if (signal_name == 'Update') {
|
||||
if (!sender_name) {
|
||||
return
|
||||
}
|
||||
|
||||
this._handleUpdateRequest(sender_name, parameters)
|
||||
}
|
||||
}
|
||||
|
||||
_onDBusNameOwnerChanged(
|
||||
connection,
|
||||
sender_name,
|
||||
object_path,
|
||||
interface_name,
|
||||
signal_name,
|
||||
parameters,
|
||||
) {
|
||||
if (!parameters || !this.size()) return
|
||||
|
||||
let [, before, after] = parameters.deep_unpack()
|
||||
|
||||
if (!after) {
|
||||
if (Object.hasOwn(this._entriesByDBusName, before)) {
|
||||
this.removeEntry(this._entriesByDBusName[before])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_handleUpdateRequest(senderName, parameters) {
|
||||
if (!senderName || !parameters) {
|
||||
return
|
||||
}
|
||||
|
||||
let [appUri, properties] = parameters.deep_unpack()
|
||||
let appId = appUri.replace(/(^\w+:|^)\/\//, '')
|
||||
let entry = this.lookupByDBusName(senderName)
|
||||
|
||||
if (entry) {
|
||||
entry.setDBusName(senderName)
|
||||
entry.update(properties)
|
||||
} else {
|
||||
let entry = new AppProgress(senderName, appId, properties)
|
||||
this.addEntry(entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class AppProgress extends EventEmitter {
|
||||
constructor(dbusName, appId, properties) {
|
||||
super()
|
||||
|
||||
this._dbusName = dbusName
|
||||
this._appId = appId
|
||||
this._count = 0
|
||||
this._countVisible = false
|
||||
this._progress = 0.0
|
||||
this._progressVisible = false
|
||||
this._urgent = false
|
||||
this.update(properties)
|
||||
}
|
||||
|
||||
appId() {
|
||||
return this._appId
|
||||
}
|
||||
|
||||
dbusName() {
|
||||
return this._dbusName
|
||||
}
|
||||
|
||||
count() {
|
||||
return this._count
|
||||
}
|
||||
|
||||
setCount(count) {
|
||||
if (this._count != count) {
|
||||
this._count = count
|
||||
this.emit('count-changed', this._count)
|
||||
}
|
||||
}
|
||||
|
||||
countVisible() {
|
||||
return this._countVisible
|
||||
}
|
||||
|
||||
setCountVisible(countVisible) {
|
||||
if (this._countVisible != countVisible) {
|
||||
this._countVisible = countVisible
|
||||
this.emit('count-visible-changed', this._countVisible)
|
||||
}
|
||||
}
|
||||
|
||||
progress() {
|
||||
return this._progress
|
||||
}
|
||||
|
||||
setProgress(progress) {
|
||||
if (this._progress != progress) {
|
||||
this._progress = progress
|
||||
this.emit('progress-changed', this._progress)
|
||||
}
|
||||
}
|
||||
|
||||
progressVisible() {
|
||||
return this._progressVisible
|
||||
}
|
||||
|
||||
setProgressVisible(progressVisible) {
|
||||
if (this._progressVisible != progressVisible) {
|
||||
this._progressVisible = progressVisible
|
||||
this.emit('progress-visible-changed', this._progressVisible)
|
||||
}
|
||||
}
|
||||
|
||||
urgent() {
|
||||
return this._urgent
|
||||
}
|
||||
|
||||
setUrgent(urgent) {
|
||||
if (this._urgent != urgent) {
|
||||
this._urgent = urgent
|
||||
this.emit('urgent-changed', this._urgent)
|
||||
}
|
||||
}
|
||||
|
||||
setDBusName(dbusName) {
|
||||
if (this._dbusName != dbusName) {
|
||||
let oldName = this._dbusName
|
||||
this._dbusName = dbusName
|
||||
this.emit('dbus-name-changed', oldName)
|
||||
}
|
||||
}
|
||||
|
||||
update(other) {
|
||||
if (other instanceof AppProgress) {
|
||||
this.setDBusName(other.dbusName())
|
||||
this.setCount(other.count())
|
||||
this.setCountVisible(other.countVisible())
|
||||
this.setProgress(other.progress())
|
||||
this.setProgressVisible(other.progressVisible())
|
||||
this.setUrgent(other.urgent())
|
||||
} else {
|
||||
for (let property in other) {
|
||||
if (Object.hasOwn(other, property)) {
|
||||
if (property == 'count') {
|
||||
this.setCount(other[property].get_int64())
|
||||
} else if (property == 'count-visible') {
|
||||
this.setCountVisible(
|
||||
SETTINGS.get_boolean('progress-show-count') &&
|
||||
other[property].get_boolean(),
|
||||
)
|
||||
} else if (property == 'progress') {
|
||||
this.setProgress(other[property].get_double())
|
||||
} else if (property == 'progress-visible') {
|
||||
this.setProgressVisible(
|
||||
SETTINGS.get_boolean('progress-show-bar') &&
|
||||
other[property].get_boolean(),
|
||||
)
|
||||
} else if (property == 'urgent') {
|
||||
this.setUrgent(other[property].get_boolean())
|
||||
} else {
|
||||
// Not implemented yet
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const ProgressIndicator = class {
|
||||
constructor(source, progressManager) {
|
||||
this._source = source
|
||||
this._progressManager = progressManager
|
||||
this._signalsHandler = new Utils.GlobalSignalsHandler()
|
||||
|
||||
this._sourceDestroyId = this._source.connect('destroy', () => {
|
||||
this._signalsHandler.destroy()
|
||||
})
|
||||
|
||||
this._notificationBadgeLabel = new St.Label({ style_class: 'badge' })
|
||||
this._notificationBadgeBin = new St.Bin({
|
||||
child: this._notificationBadgeLabel,
|
||||
y: 2,
|
||||
x: 2,
|
||||
})
|
||||
this._notificationBadgeLabel.add_style_class_name('notification-badge')
|
||||
this._notificationBadgeCount = 0
|
||||
this._notificationBadgeBin.hide()
|
||||
|
||||
this._source._dtpIconContainer.add_child(this._notificationBadgeBin)
|
||||
this._source._dtpIconContainer.connect(
|
||||
'notify::allocation',
|
||||
this.updateNotificationBadge.bind(this),
|
||||
)
|
||||
|
||||
this._progressManagerEntries = []
|
||||
this._progressManager.lookupById(this._source.app.id).forEach((entry) => {
|
||||
this.insertEntry(entry)
|
||||
})
|
||||
|
||||
this._signalsHandler.add(
|
||||
[
|
||||
this._progressManager,
|
||||
'progress-entry-added',
|
||||
this._onEntryAdded.bind(this),
|
||||
],
|
||||
[
|
||||
this._progressManager,
|
||||
'progress-entry-removed',
|
||||
this._onEntryRemoved.bind(this),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._source.disconnect(this._sourceDestroyId)
|
||||
this._signalsHandler.destroy()
|
||||
}
|
||||
|
||||
_onEntryAdded(appProgress, entry) {
|
||||
if (!entry || !entry.appId()) return
|
||||
if (
|
||||
this._source &&
|
||||
this._source.app &&
|
||||
this._source.app.id == entry.appId()
|
||||
) {
|
||||
this.insertEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
_onEntryRemoved(appProgress, entry) {
|
||||
if (!entry || !entry.appId()) return
|
||||
|
||||
if (
|
||||
this._source &&
|
||||
this._source.app &&
|
||||
this._source.app.id == entry.appId()
|
||||
) {
|
||||
this.removeEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
updateNotificationBadge() {
|
||||
this._source.updateNumberOverlay(this._notificationBadgeBin)
|
||||
this._notificationBadgeLabel.clutter_text.ellipsize =
|
||||
Pango.EllipsizeMode.MIDDLE
|
||||
}
|
||||
|
||||
_notificationBadgeCountToText(count) {
|
||||
if (count <= 9999) {
|
||||
return count.toString()
|
||||
} else if (count < 1e5) {
|
||||
let thousands = count / 1e3
|
||||
return thousands.toFixed(1).toString() + 'k'
|
||||
} else if (count < 1e6) {
|
||||
let thousands = count / 1e3
|
||||
return thousands.toFixed(0).toString() + 'k'
|
||||
} else if (count < 1e8) {
|
||||
let millions = count / 1e6
|
||||
return millions.toFixed(1).toString() + 'M'
|
||||
} else if (count < 1e9) {
|
||||
let millions = count / 1e6
|
||||
return millions.toFixed(0).toString() + 'M'
|
||||
} else {
|
||||
let billions = count / 1e9
|
||||
return billions.toFixed(1).toString() + 'B'
|
||||
}
|
||||
}
|
||||
|
||||
setNotificationBadge(count) {
|
||||
this._notificationBadgeCount = count
|
||||
let text = this._notificationBadgeCountToText(count)
|
||||
this._notificationBadgeLabel.set_text(text)
|
||||
}
|
||||
|
||||
toggleNotificationBadge(activate) {
|
||||
if (activate && this._notificationBadgeCount > 0) {
|
||||
this.updateNotificationBadge()
|
||||
this._notificationBadgeBin.show()
|
||||
} else this._notificationBadgeBin.hide()
|
||||
}
|
||||
|
||||
_showProgressOverlay() {
|
||||
if (this._progressOverlayArea) {
|
||||
this._updateProgressOverlay()
|
||||
return
|
||||
}
|
||||
|
||||
this._progressOverlayArea = new St.DrawingArea({
|
||||
x_expand: true,
|
||||
y_expand: true,
|
||||
})
|
||||
this._progressOverlayArea.add_style_class_name('progress-bar')
|
||||
this._progressOverlayArea.connect('repaint', () => {
|
||||
this._drawProgressOverlay(this._progressOverlayArea)
|
||||
})
|
||||
|
||||
this._source._iconContainer.add_child(this._progressOverlayArea)
|
||||
let node = this._progressOverlayArea.get_theme_node()
|
||||
|
||||
let [hasColor, color] = node.lookup_color('-progress-bar-background', false)
|
||||
if (hasColor) this._progressbar_background = color
|
||||
else
|
||||
this._progressbar_background = new Utils.ColorUtils.Color({
|
||||
red: 204,
|
||||
green: 204,
|
||||
blue: 204,
|
||||
alpha: 255,
|
||||
})
|
||||
;[hasColor, color] = node.lookup_color('-progress-bar-border', false)
|
||||
if (hasColor) this._progressbar_border = color
|
||||
else
|
||||
this._progressbar_border = new Utils.ColorUtils.Color({
|
||||
red: 230,
|
||||
green: 230,
|
||||
blue: 230,
|
||||
alpha: 255,
|
||||
})
|
||||
|
||||
this._updateProgressOverlay()
|
||||
}
|
||||
|
||||
_hideProgressOverlay() {
|
||||
if (this._progressOverlayArea) this._progressOverlayArea.destroy()
|
||||
|
||||
this._progressOverlayArea = null
|
||||
this._progressbar_background = null
|
||||
this._progressbar_border = null
|
||||
}
|
||||
|
||||
_updateProgressOverlay() {
|
||||
if (this._progressOverlayArea) {
|
||||
this._progressOverlayArea.queue_repaint()
|
||||
}
|
||||
}
|
||||
|
||||
_drawProgressOverlay(area) {
|
||||
let scaleFactor = Utils.getScaleFactor()
|
||||
let [surfaceWidth, surfaceHeight] = area.get_surface_size()
|
||||
let cr = area.get_context()
|
||||
|
||||
let iconSize = this._source.icon.iconSize * scaleFactor
|
||||
|
||||
let x = Math.floor((surfaceWidth - iconSize) / 2)
|
||||
let y = Math.floor((surfaceHeight - iconSize) / 2)
|
||||
|
||||
let lineWidth = Math.floor(1.0 * scaleFactor)
|
||||
let padding = Math.floor(iconSize * 0.05)
|
||||
let width = iconSize - 2.0 * padding
|
||||
let height = Math.floor(Math.min(18.0 * scaleFactor, 0.2 * iconSize))
|
||||
x += padding
|
||||
y += iconSize - height - padding
|
||||
|
||||
cr.setLineWidth(lineWidth)
|
||||
|
||||
// Draw the outer stroke
|
||||
let stroke = new Cairo.LinearGradient(0, y, 0, y + height)
|
||||
let fill = null
|
||||
stroke.addColorStopRGBA(0.5, 0.5, 0.5, 0.5, 0.1)
|
||||
stroke.addColorStopRGBA(0.9, 0.8, 0.8, 0.8, 0.4)
|
||||
Utils.drawRoundedLine(
|
||||
cr,
|
||||
x + lineWidth / 2.0,
|
||||
y + lineWidth / 2.0,
|
||||
width,
|
||||
height,
|
||||
true,
|
||||
true,
|
||||
stroke,
|
||||
fill,
|
||||
)
|
||||
|
||||
// Draw the background
|
||||
x += lineWidth
|
||||
y += lineWidth
|
||||
width -= 2.0 * lineWidth
|
||||
height -= 2.0 * lineWidth
|
||||
|
||||
stroke = Cairo.SolidPattern.createRGBA(0.2, 0.2, 0.2, 0.9)
|
||||
fill = new Cairo.LinearGradient(0, y, 0, y + height)
|
||||
fill.addColorStopRGBA(0.4, 0.25, 0.25, 0.25, 1.0)
|
||||
fill.addColorStopRGBA(0.9, 0.35, 0.35, 0.35, 1.0)
|
||||
Utils.drawRoundedLine(
|
||||
cr,
|
||||
x + lineWidth / 2.0,
|
||||
y + lineWidth / 2.0,
|
||||
width,
|
||||
height,
|
||||
true,
|
||||
true,
|
||||
stroke,
|
||||
fill,
|
||||
)
|
||||
|
||||
// Draw the finished bar
|
||||
x += lineWidth
|
||||
y += lineWidth
|
||||
width -= 2.0 * lineWidth
|
||||
height -= 2.0 * lineWidth
|
||||
|
||||
let finishedWidth = Math.ceil(this._progress * width)
|
||||
|
||||
let bg = this._progressbar_background
|
||||
let bd = this._progressbar_border
|
||||
|
||||
stroke = Cairo.SolidPattern.createRGBA(
|
||||
bd.red / 255,
|
||||
bd.green / 255,
|
||||
bd.blue / 255,
|
||||
bd.alpha / 255,
|
||||
)
|
||||
fill = Cairo.SolidPattern.createRGBA(
|
||||
bg.red / 255,
|
||||
bg.green / 255,
|
||||
bg.blue / 255,
|
||||
bg.alpha / 255,
|
||||
)
|
||||
|
||||
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
|
||||
Utils.drawRoundedLine(
|
||||
cr,
|
||||
x + lineWidth / 2.0 + width - finishedWidth,
|
||||
y + lineWidth / 2.0,
|
||||
finishedWidth,
|
||||
height,
|
||||
true,
|
||||
true,
|
||||
stroke,
|
||||
fill,
|
||||
)
|
||||
else
|
||||
Utils.drawRoundedLine(
|
||||
cr,
|
||||
x + lineWidth / 2.0,
|
||||
y + lineWidth / 2.0,
|
||||
finishedWidth,
|
||||
height,
|
||||
true,
|
||||
true,
|
||||
stroke,
|
||||
fill,
|
||||
)
|
||||
|
||||
cr.$dispose()
|
||||
}
|
||||
|
||||
setProgress(progress) {
|
||||
this._progress = Math.min(Math.max(progress, 0.0), 1.0)
|
||||
this._updateProgressOverlay()
|
||||
}
|
||||
|
||||
toggleProgressOverlay(activate) {
|
||||
if (activate) {
|
||||
this._showProgressOverlay()
|
||||
} else {
|
||||
this._hideProgressOverlay()
|
||||
}
|
||||
}
|
||||
|
||||
insertEntry(appProgress) {
|
||||
if (
|
||||
!appProgress ||
|
||||
this._progressManagerEntries.indexOf(appProgress) !== -1
|
||||
)
|
||||
return
|
||||
|
||||
this._progressManagerEntries.push(appProgress)
|
||||
this._selectEntry(appProgress)
|
||||
}
|
||||
|
||||
removeEntry(appProgress) {
|
||||
if (!appProgress || this._progressManagerEntries.indexOf(appProgress) == -1)
|
||||
return
|
||||
|
||||
this._progressManagerEntries.splice(
|
||||
this._progressManagerEntries.indexOf(appProgress),
|
||||
1,
|
||||
)
|
||||
|
||||
if (this._progressManagerEntries.length > 0) {
|
||||
this._selectEntry(
|
||||
this._progressManagerEntries[this._progressManagerEntries.length - 1],
|
||||
)
|
||||
} else {
|
||||
this.setNotificationBadge(0)
|
||||
this.toggleNotificationBadge(false)
|
||||
this.setProgress(0)
|
||||
this.toggleProgressOverlay(false)
|
||||
this.setUrgent(false)
|
||||
}
|
||||
}
|
||||
|
||||
_selectEntry(appProgress) {
|
||||
if (!appProgress) return
|
||||
|
||||
this._signalsHandler.removeWithLabel('progress-entry')
|
||||
|
||||
this._signalsHandler.addWithLabel(
|
||||
'progress-entry',
|
||||
[
|
||||
appProgress,
|
||||
'count-changed',
|
||||
(appProgress, value) => {
|
||||
this.setNotificationBadge(value)
|
||||
},
|
||||
],
|
||||
[
|
||||
appProgress,
|
||||
'count-visible-changed',
|
||||
(appProgress, value) => {
|
||||
this.toggleNotificationBadge(value)
|
||||
},
|
||||
],
|
||||
[
|
||||
appProgress,
|
||||
'progress-changed',
|
||||
(appProgress, value) => {
|
||||
this.setProgress(value)
|
||||
},
|
||||
],
|
||||
[
|
||||
appProgress,
|
||||
'progress-visible-changed',
|
||||
(appProgress, value) => {
|
||||
this.toggleProgressOverlay(value)
|
||||
},
|
||||
],
|
||||
[
|
||||
appProgress,
|
||||
'urgent-changed',
|
||||
(appProgress, value) => {
|
||||
this.setUrgent(value)
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
this.setNotificationBadge(appProgress.count())
|
||||
this.toggleNotificationBadge(appProgress.countVisible())
|
||||
this.setProgress(appProgress.progress())
|
||||
this.toggleProgressOverlay(appProgress.progressVisible())
|
||||
|
||||
this._isUrgent = false
|
||||
}
|
||||
|
||||
setUrgent(urgent) {
|
||||
const icon = this._source.icon._iconBin
|
||||
if (urgent) {
|
||||
if (!this._isUrgent) {
|
||||
icon.set_pivot_point(0.5, 0.5)
|
||||
this._source.iconAnimator.addAnimation(icon, 'dance')
|
||||
this._isUrgent = true
|
||||
}
|
||||
} else {
|
||||
if (this._isUrgent) {
|
||||
this._source.iconAnimator.removeAnimation(icon, 'dance')
|
||||
this._isUrgent = false
|
||||
}
|
||||
icon.rotation_angle_z = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,7 +153,7 @@
|
||||
}
|
||||
|
||||
#dashtopanelScrollview .notification-badge {
|
||||
background-color: rgba(255,0,0,0.8);
|
||||
background-color: rgba(214, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
#dashtopanelScrollview .progress-bar {
|
||||
|
||||
@@ -64,6 +64,7 @@ let donateDummyApp = {
|
||||
action == 'opts' ? _('Donation options') : '',
|
||||
},
|
||||
connect: () => [],
|
||||
disconnect: () => false,
|
||||
connectObject: () => [],
|
||||
get_id: () => 'dtp_donate',
|
||||
get_windows: () => [],
|
||||
@@ -1197,10 +1198,8 @@ export const Taskbar = class extends EventEmitter {
|
||||
// This is required for icon reordering when the scrollview is used.
|
||||
this._updateAppIcons()
|
||||
|
||||
// This will update the size, and the corresponding number for each icon on the primary panel
|
||||
if (this.dtpPanel.isPrimary) {
|
||||
this._updateNumberOverlay()
|
||||
}
|
||||
// This will update the size, and the corresponding number for each icon
|
||||
this._updateHotkeysNumberOverlay()
|
||||
|
||||
this._shownInitially = true
|
||||
}
|
||||
@@ -1276,7 +1275,7 @@ export const Taskbar = class extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
_updateNumberOverlay() {
|
||||
_updateHotkeysNumberOverlay() {
|
||||
let seenApps = {}
|
||||
let counter = 0
|
||||
|
||||
@@ -1287,26 +1286,24 @@ export const Taskbar = class extends EventEmitter {
|
||||
}
|
||||
|
||||
if (counter <= 10) {
|
||||
icon.setNumberOverlay(counter == 10 ? 0 : counter)
|
||||
icon.setHotkeysNumberOverlayLabel(counter == 10 ? 0 : counter)
|
||||
} else {
|
||||
// No overlay after 10
|
||||
icon.setNumberOverlay(-1)
|
||||
icon.setHotkeysNumberOverlayLabel(-1)
|
||||
}
|
||||
|
||||
icon.updateHotkeyNumberOverlay()
|
||||
})
|
||||
|
||||
if (
|
||||
SETTINGS.get_boolean('hot-keys') &&
|
||||
SETTINGS.get_string('hotkeys-overlay-combo') === 'ALWAYS'
|
||||
)
|
||||
this.toggleNumberOverlay(true)
|
||||
this.toggleHotkeysNumberOverlay(true)
|
||||
}
|
||||
|
||||
toggleNumberOverlay(activate) {
|
||||
toggleHotkeysNumberOverlay(activate) {
|
||||
let appIcons = this._getAppIcons()
|
||||
appIcons.forEach(function (icon) {
|
||||
icon.toggleNumberOverlay(activate)
|
||||
icon.toggleHotkeysNumberOverlay(activate)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user