Add simple message tray notifications handling

gh-1695
This commit is contained in:
Charles Gagnon
2025-02-09 13:15:03 -05:00
parent a08805d24d
commit b5cdd6e240
9 changed files with 399 additions and 1031 deletions

View File

@@ -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
View 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))
}
}

View File

@@ -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)

View File

@@ -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),
)
}
},
)

View File

@@ -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
}
}

View File

@@ -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()
})
}

View File

@@ -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
}
}
}

View File

@@ -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 {

View File

@@ -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)
})
}