From c173f8c1f39717b5202602dbedb62554993dd9df Mon Sep 17 00:00:00 2001 From: fredcw Date: Thu, 12 Dec 2024 06:55:56 +0000 Subject: [PATCH 01/11] GWL: add notification badges GWL: add notification badges to top right of panel icons and add config option to disable notification badges GWL: move window count badge from top left to bottom left of panel icon. Remove 'notifications' extension role as it only allows for one extension. Use extensionsHandlingNotifications variable in messageTray.js for applets to increment/decrement when added/removed from panel instead. Ensure notificationDaemon can identify source of notifications from flatpak apps. Increase max notifications per source from 10 to 20. --- data/theme/cinnamon-sass/_colors.scss | 2 + .../cinnamon-sass/widgets/_windowlist.scss | 18 ++- .../appGroup.js | 106 ++++++++++++------ .../applet.js | 97 +++++++++++++++- .../settings-schema.json | 14 ++- .../workspace.js | 2 +- .../notifications@cinnamon.org/applet.js | 7 ++ .../notifications@cinnamon.org/metadata.json | 1 - js/ui/appletManager.js | 1 - js/ui/extension.js | 8 +- js/ui/messageTray.js | 8 +- js/ui/notificationDaemon.js | 36 +++--- 12 files changed, 233 insertions(+), 67 deletions(-) diff --git a/data/theme/cinnamon-sass/_colors.scss b/data/theme/cinnamon-sass/_colors.scss index c9f18d40d4..94a3bab940 100644 --- a/data/theme/cinnamon-sass/_colors.scss +++ b/data/theme/cinnamon-sass/_colors.scss @@ -18,6 +18,8 @@ $destructive_color: #ff7b63; $warning_color: #f8e45c; $warning_bg_color: #cd9309; +$notification_badge_bg_color: #ef2591; + $accent_color: #78aeed; $accent_bg_color: #3584e4; diff --git a/data/theme/cinnamon-sass/widgets/_windowlist.scss b/data/theme/cinnamon-sass/widgets/_windowlist.scss index a23da85c5c..3ddc2d19b5 100644 --- a/data/theme/cinnamon-sass/widgets/_windowlist.scss +++ b/data/theme/cinnamon-sass/widgets/_windowlist.scss @@ -88,14 +88,24 @@ &-button-label { padding-left: 4px;} - &-number-label { - font-size: 0.8em; + &-windows-badge { + border-radius: 9999px; + color: $fg_color; + background-color: $accent_bg_color; + } + + &-windows-badge-label { z-index: 99; } - &-badge { + &-notifications-badge { border-radius: 9999px; - background-color: $bg_color; + color: $fg_color; + background-color: $notification_badge_bg_color; + } + + &-notifications-badge-label { + z-index: 99; } } diff --git a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js index 6a0f0990c5..d6ba2479ea 100644 --- a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js +++ b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js @@ -99,6 +99,7 @@ class AppGroup { this.labelVisiblePref = this.state.settings.titleDisplay !== TitleDisplay.None && this.state.isHorizontal; this.drawLabel = this.labelVisiblePref; this.progress = 0; + this.notifications = []; this.actor = new Cinnamon.GenericContainer({ name: 'appButton', @@ -119,31 +120,50 @@ class AppGroup { }); this.actor.add_child(this.progressOverlay); - // Create the app button icon, number label, and text label for titleDisplay + // Create the app button icon, window count and notification badges, and text label for titleDisplay this.iconBox = new Cinnamon.Slicer({name: 'appMenuIcon'}); this.actor.add_child(this.iconBox); this.setActorAttributes(null, params.metaWindow); - this.badge = new St.BoxLayout({ - style_class: 'grouped-window-list-badge', + this.windowsBadge = new St.BoxLayout({ + style_class: 'grouped-window-list-windows-badge', important: true, + x_align: St.Align.MIDDLE, + y_align: St.Align.MIDDLE, + show_on_set_parent: false, + }); + this.windowsBadgeLabel = new St.Label({ + style_class: 'grouped-window-list-windows-badge-label', + important: true, + text: '' + }); + this.windowsBadgeLabel.clutter_text.ellipsize = false; + this.windowsBadge.add(this.windowsBadgeLabel, { x_align: St.Align.START, + y_align: St.Align.START, + }); + this.actor.add_child(this.windowsBadge); + this.windowsBadge.set_text_direction(St.TextDirection.LTR); + + this.notificationsBadge = new St.BoxLayout({ + style_class: 'grouped-window-list-notifications-badge', + important: true, + x_align: St.Align.MIDDLE, y_align: St.Align.MIDDLE, show_on_set_parent: false, }); - this.numberLabel = new St.Label({ - style_class: 'grouped-window-list-number-label', + this.notificationsBadgeLabel = new St.Label({ + style_class: 'grouped-window-list-notifications-badge-label', important: true, - text: '', - anchor_x: -3 * global.ui_scale, + text: '' }); - this.numberLabel.clutter_text.ellipsize = false; - this.badge.add(this.numberLabel, { + this.notificationsBadgeLabel.clutter_text.ellipsize = false; + this.notificationsBadge.add(this.notificationsBadgeLabel, { x_align: St.Align.START, y_align: St.Align.START, }); - this.actor.add_child(this.badge); - this.badge.set_text_direction(St.TextDirection.LTR); + this.actor.add_child(this.notificationsBadge); + this.notificationsBadge.set_text_direction(St.TextDirection.LTR); this.label = new St.Label({ style_class: 'grouped-window-list-button-label', @@ -172,6 +192,7 @@ class AppGroup { this.calcWindowNumber(); this.on_orientation_changed(true); this.handleFavorite(); + this.state.trigger('copyNotifications', this); } initThumbnailMenu() { @@ -394,15 +415,23 @@ class AppGroup { this.iconBox.allocate(childBox, flags); - // Set badge position - const windowCountFactor = this.groupState.windowCount > 9 ? 1.5 : 2; - const badgeOffset = 2 * global.ui_scale; - childBox.x1 = childBox.x1 - badgeOffset; - childBox.x2 = childBox.x1 + (this.numberLabel.width * windowCountFactor); - childBox.y1 = Math.max(childBox.y1 - badgeOffset, 0); - childBox.y2 = childBox.y1 + this.badge.get_preferred_height(childBox.get_width())[1]; + this.updateBadgesTextSize(); + + // Set windows badge position + childBox.x1 = box.x1; + childBox.x2 = childBox.x1 + this.windowsBadgeLabel.width; + childBox.y1 = box.y2 - this.windowsBadge.get_preferred_height(childBox.get_width())[1]; + childBox.y2 = box.y2; + + this.windowsBadge.allocate(childBox, flags); - this.badge.allocate(childBox, flags); + // Set notifications badge position + childBox.x2 = this.iconBox.x + this.iconBox.width; + childBox.x1 = childBox.x2 - this.notificationsBadgeLabel.width; + childBox.y1 = box.y1; + childBox.y2 = childBox.y1 + this.notificationsBadge.get_preferred_height(childBox.get_width())[1]; + + this.notificationsBadge.allocate(childBox, flags); // Set label position if (this.drawLabel) { @@ -450,6 +479,14 @@ class AppGroup { if (this.progressOverlay.visible) this.allocateProgress(childBox, flags); } + updateBadgesTextSize() { + const badgeTextSize = Math.round(Math.min(this.iconBox.width, this.iconBox.height) / 2.5 / global.ui_scale); + const badgePadding = Math.round(badgeTextSize / 4); + const sizeStyle = `font-size: ${badgeTextSize}px; padding-left: ${badgePadding}px; padding-right: ${badgePadding}px;`; + this.windowsBadgeLabel.set_style(sizeStyle); + this.notificationsBadgeLabel.set_style(sizeStyle); + } + showLabel(animate = false) { if (this.labelVisiblePref || !this.label @@ -676,8 +713,8 @@ class AppGroup { } showOrderLabel(number) { - this.numberLabel.text = (number + 1).toString(); - this.badge.show(); + this.windowsBadgeLabel.text = (number + 1).toString(); + this.windowsBadge.show(); } launchNewInstance(offload=false) { @@ -917,6 +954,7 @@ class AppGroup { this.setIcon(metaWindow) this.calcWindowNumber(); + this.updateNotificationsBadge(); this.onFocusChange(); } set({ @@ -1074,20 +1112,22 @@ class AppGroup { calcWindowNumber() { if (this.groupState.willUnmount) return; - const windowCount = this.groupState.metaWindows ? this.groupState.metaWindows.length : 0; - this.numberLabel.text = windowCount.toString(); - - this.groupState.set({windowCount}); - - if (this.state.settings.numDisplay) { - if (windowCount <= 1) { - this.badge.hide(); - } else { - this.badge.show(); + this.groupState.set({windowCount: this.groupState.metaWindows ? this.groupState.metaWindows.length : 0}); + + if (this.groupState.windowCount > 1 && this.state.settings.enableWindowCountBadges) { + this.windowsBadgeLabel.text = this.groupState.windowCount.toString(); + this.windowsBadge.show(); + } else { + this.windowsBadge.hide(); + } + } - } + updateNotificationsBadge() { + if (this.notifications.length > 0 && this.state.settings.enableNotificationBadges) { + this.notificationsBadgeLabel.text = this.notifications.length.toString(); + this.notificationsBadge.show(); } else { - this.badge.hide(); + this.notificationsBadge.hide(); } } diff --git a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/applet.js b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/applet.js index 75d1c85208..32b6456b8b 100644 --- a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/applet.js +++ b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/applet.js @@ -9,6 +9,7 @@ const Applet = imports.ui.applet; const Cinnamon = imports.gi.Cinnamon; const Main = imports.ui.main; const DND = imports.ui.dnd; +const MessageTray = imports.ui.messageTray; const {AppletSettings} = imports.ui.settings; const {SignalManager} = imports.misc.signalManager; const {throttle, unref, trySpawnCommandLine} = imports.misc.util; @@ -255,6 +256,7 @@ class GroupedWindowListApplet extends Applet.Applet { openAbout: () => this.openAbout(), configureApplet: () => this.configureApplet(), removeApplet: (event) => this.confirmRemoveApplet(event), + copyNotifications: (appGroup) => this.copyNotifications(appGroup) }); this.settings = new AppletSettings(this.state.settings, metadata.uuid, instance_id); @@ -291,6 +293,7 @@ class GroupedWindowListApplet extends Applet.Applet { this.signals.connect(global.display, 'window-created', (...args) => this.onWindowCreated(...args)); this.signals.connect(global.settings, 'changed::panel-edit-mode', (...args) => this.on_panel_edit_mode_changed(...args)); this.signals.connect(Main.themeManager, 'theme-set', (...args) => this.refreshCurrentWorkspace(...args)); + this.signals.connect(Main.messageTray, 'notify-applet-update', this._onNotificationReceived.bind(this)); } bindSettings() { @@ -307,7 +310,8 @@ class GroupedWindowListApplet extends Applet.Applet { {key: 'super-num-hotkeys', value: 'SuperNumHotkeys', cb: this.bindAppKeys}, {key: 'title-display', value: 'titleDisplay', cb: this.updateTitleDisplay}, {key: 'launcher-animation-effect', value: 'launcherAnimationEffect', cb: null}, - {key: 'number-display', value: 'numDisplay', cb: this.updateWindowNumberState}, + {key: 'enable-window-count-badges', value: 'enableWindowCountBadges', cb: this.onEnableWindowCountBadgeChange}, + {key: 'enable-notification-badges', value: 'enableNotificationBadges', cb: this.onEnableNotificationsChange}, {key: 'enable-app-button-dragging', value: 'enableDragging', cb: this.draggableSettingChanged}, {key: 'thumbnail-scroll-behavior', value: 'thumbnailScrollBehavior', cb: null}, {key: 'show-thumbnails', value: 'showThumbs', cb: this.updateVerticalThumbnailState}, @@ -357,6 +361,7 @@ class GroupedWindowListApplet extends Applet.Applet { } this.bindAppKeys(); this.state.set({appletReady: true}); + MessageTray.extensionsHandlingNotifications++; } _updateState(initialUpdate) { @@ -424,6 +429,10 @@ class GroupedWindowListApplet extends Applet.Applet { }); this.settings.finalize(); unref(this, RESERVE_KEYS); + MessageTray.extensionsHandlingNotifications--; + if (MessageTray.extensionsHandlingNotifications === 0) { + this._destroyAllNotifications(); + } } on_panel_icon_size_changed(iconSize) { @@ -584,7 +593,7 @@ class GroupedWindowListApplet extends Applet.Applet { }); } - updateWindowNumberState() { + onEnableWindowCountBadgeChange() { this.workspaces.forEach( workspace => workspace.calcAllWindowNumbers() ); @@ -1022,6 +1031,90 @@ class GroupedWindowListApplet extends Applet.Applet { this.state.set({thumbnailCloseButtonOffset: global.ui_scale > 1 ? -10 : 0}); this.refreshAllWorkspaces(); } + + copyNotifications(newAppGroup) { + // Copy notifications from any existing appGroup with the same appId. + this.workspaces.some(workspace => { + if (!workspace) return false; + return workspace.appGroups.some(appGroup => { + if (!appGroup || !appGroup.groupState || appGroup.groupState.willUnmount) return false; + if (appGroup.groupState.appId === newAppGroup.groupState.appId) { + newAppGroup.notifications = appGroup.notifications.slice(); // Shallow copy. + return true; + } + return false; + }); + }); + } + + _onNotificationReceived(mtray, notification) { + let appId = notification.source.app?.get_id(); + + if (!appId) { + return; + } + + // Add notification to all appgroups with appId. + let notificationAdded = false; + + this.workspaces.forEach(workspace => { + if (!workspace) return; + workspace.appGroups.forEach(appGroup => { + if (!appGroup || !appGroup.groupState || appGroup.groupState.willUnmount) return; + if (appId === appGroup.groupState.appId) { + appGroup.notifications.push(notification); + notificationAdded = true; + appGroup.updateNotificationsBadge(); + } + }); + }); + + if (notificationAdded) { + notification.appId = appId; + notification.connect('destroy', () => this._onNotificationDestroyed(notification)); + } + } + + _onNotificationDestroyed(notification) { + if (!this.workspaces) return; + + this.workspaces.forEach(workspace => { + if (!workspace) return; + workspace.appGroups.forEach(appGroup => { + if (!appGroup || !appGroup.groupState || appGroup.groupState.willUnmount) return; + if (notification.appId === appGroup.groupState.appId) { + const index = appGroup.notifications.indexOf(notification); + if (index > -1) { + appGroup.notifications.splice(index, 1) + appGroup.updateNotificationsBadge(); + } + } + }); + }); + } + + _destroyAllNotifications() { + this.workspaces.forEach(workspace => { + if (!workspace) return; + workspace.appGroups.forEach(appGroup => { + if (!appGroup || !appGroup.groupState || appGroup.groupState.willUnmount) return; + // Iterate backwards due to in place element deletion. + for (let i = appGroup.notifications.length - 1; i >= 0; i--) { + appGroup.notifications[i].destroy(); + } + }); + }); + } + + onEnableNotificationsChange() { + this.workspaces.forEach(workspace => { + if (!workspace) return; + workspace.appGroups.forEach(appGroup => { + if (!appGroup || !appGroup.groupState || appGroup.groupState.willUnmount) return; + appGroup.updateNotificationsBadge(); + }); + }); + } } function main(metadata, orientation, panel_height, instance_id) { diff --git a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/settings-schema.json b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/settings-schema.json index b86af45073..7e093ccea2 100644 --- a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/settings-schema.json +++ b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/settings-schema.json @@ -50,7 +50,8 @@ "keys": [ "title-display", "launcher-animation-effect", - "number-display", + "enable-window-count-badges", + "enable-notification-badges", "enable-app-button-dragging" ] }, @@ -184,10 +185,17 @@ "Scale": 3 } }, - "number-display": { + "enable-window-count-badges": { "type": "checkbox", "default": true, - "description": "Show window count numbers" + "description": "Show window count badges", + "tooltip": "Indicate on the panel the number of open windows an application has" + }, + "enable-notification-badges": { + "type": "checkbox", + "default": true, + "description": "Show notification Badges", + "tooltip": "Indicate on the panel when an application has notifications" }, "enable-app-button-dragging": { "type": "checkbox", diff --git a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/workspace.js b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/workspace.js index 4329489297..4af591ee8c 100644 --- a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/workspace.js +++ b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/workspace.js @@ -333,7 +333,7 @@ class Workspace { calcAllWindowNumbers() { this.appGroups.forEach( appGroup => { - appGroup.calcWindowNumber(appGroup.groupState.metaWindows); + appGroup.calcWindowNumber(); }); } diff --git a/files/usr/share/cinnamon/applets/notifications@cinnamon.org/applet.js b/files/usr/share/cinnamon/applets/notifications@cinnamon.org/applet.js index 883b252a14..a0f45180d9 100644 --- a/files/usr/share/cinnamon/applets/notifications@cinnamon.org/applet.js +++ b/files/usr/share/cinnamon/applets/notifications@cinnamon.org/applet.js @@ -7,6 +7,7 @@ const PopupMenu = imports.ui.popupMenu; const St = imports.gi.St; const Mainloop = imports.mainloop; const Urgency = imports.ui.messageTray.Urgency; +const MessageTray = imports.ui.messageTray; const NotificationDestroyedReason = imports.ui.messageTray.NotificationDestroyedReason; const Settings = imports.ui.settings; const Gettext = imports.gettext.domain("cinnamon-applets"); @@ -55,6 +56,11 @@ class CinnamonNotificationsApplet extends Applet.TextIconApplet { Main.keybindingManager.removeXletHotKey(this, "notification-open"); Main.keybindingManager.removeXletHotKey(this, "notification-clear"); global.settings.disconnect(this.panelEditModeHandler); + + MessageTray.extensionsHandlingNotifications--; + if (MessageTray.extensionsHandlingNotifications === 0) { + this._clear_all(); + } } _openMenu() { @@ -266,6 +272,7 @@ class CinnamonNotificationsApplet extends Applet.TextIconApplet { on_applet_added_to_panel() { this.on_orientation_changed(this._orientation); + MessageTray.extensionsHandlingNotifications++; } on_orientation_changed (orientation) { diff --git a/files/usr/share/cinnamon/applets/notifications@cinnamon.org/metadata.json b/files/usr/share/cinnamon/applets/notifications@cinnamon.org/metadata.json index 6daa046102..0d9d00af31 100644 --- a/files/usr/share/cinnamon/applets/notifications@cinnamon.org/metadata.json +++ b/files/usr/share/cinnamon/applets/notifications@cinnamon.org/metadata.json @@ -2,6 +2,5 @@ "uuid": "notifications@cinnamon.org", "name": "Notifications", "description": "Click to display and manage system notifications", -"role": "notifications", "icon": "cs-notifications" } diff --git a/js/ui/appletManager.js b/js/ui/appletManager.js index b8bfc679d1..3a2ecdfb7d 100644 --- a/js/ui/appletManager.js +++ b/js/ui/appletManager.js @@ -25,7 +25,6 @@ var appletsLoaded = false; // FIXME: This role stuff is checked in extension.js, why not move checks from here to there? var Roles = { - NOTIFICATIONS: 'notifications', PANEL_LAUNCHER: 'panellauncher', WINDOW_ATTENTION_HANDLER: 'windowattentionhandler', WINDOW_LIST: 'windowlist' diff --git a/js/ui/extension.js b/js/ui/extension.js index 8c9ea22dce..8ef64bb114 100644 --- a/js/ui/extension.js +++ b/js/ui/extension.js @@ -104,7 +104,6 @@ var Type = { }), APPLET: _createExtensionType("Applet", "applets", AppletManager, { roles: { - notifications: null, windowlist: null, windowattentionhandler: null, panellauncher: null, @@ -113,7 +112,6 @@ var Type = { }), DESKLET: _createExtensionType("Desklet", "desklets", DeskletManager, { roles: { - notifications: null, windowlist: null, windowattentionhandler: null } @@ -339,7 +337,7 @@ Extension.prototype = { // If a role is set, make sure it's a valid one let meta_role_list_str = this.meta['role']; if (meta_role_list_str) { - let meta_roles = meta_role_list_str.replace(" ", "").split(","); + let meta_roles = meta_role_list_str.replaceAll(" ", "").split(","); for (let role of meta_roles) { if (!(role in Type[this.upperType].roles)) { throw logError(`Unknown role definition: ${role} in metadata.json`, this.uuid); @@ -415,7 +413,7 @@ Extension.prototype = { lockRole: function(roleProvider) { if (this.meta && this.meta.role) { let meta_role_list_str = this.meta.role; - let meta_roles = meta_role_list_str.replace(" ", "").split(","); + let meta_roles = meta_role_list_str.replaceAll(" ", "").split(","); let avail_roles = []; @@ -448,7 +446,7 @@ Extension.prototype = { unlockRoles: function() { if (this.meta.role) { let meta_role_list_str = this.meta.role; - let meta_roles = meta_role_list_str.replace(" ", "").split(","); + let meta_roles = meta_role_list_str.replaceAll(" ", "").split(","); for (let role of meta_roles) { if (Type[this.upperType].roles[role] === this.uuid) { diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js index 63e46fa2af..14f7a52f6c 100644 --- a/js/ui/messageTray.js +++ b/js/ui/messageTray.js @@ -30,6 +30,8 @@ var LONGER_HIDE_TIMEOUT = 0.6; const NOTIFICATION_IMAGE_SIZE = 125; const NOTIFICATION_IMAGE_OPACITY = 230; // 0 - 255 +var extensionsHandlingNotifications = 0; + var State = Object.freeze({ HIDDEN: 0, SHOWING: 1, @@ -634,7 +636,7 @@ function Source(title) { Source.prototype = { ICON_SIZE: 24, - MAX_NOTIFICATIONS: 10, + MAX_NOTIFICATIONS: 20, _init: function (title) { this.title = title; @@ -1017,7 +1019,7 @@ MessageTray.prototype = { if (this._notification.urgency != Urgency.CRITICAL) { this._updateNotificationTimeout(this.notificationDuration * 1000); - } else if (AppletManager.get_role_provider_exists(AppletManager.Roles.NOTIFICATIONS)) { + } else if (extensionsHandlingNotifications > 0) { this._updateNotificationTimeout(NOTIFICATION_CRITICAL_TIMEOUT_WITH_APPLET * 1000); } }, @@ -1076,7 +1078,7 @@ MessageTray.prototype = { this._notificationBin.hide(); this._notificationBin.child = null; let notification = this._notification; - if (AppletManager.get_role_provider_exists(AppletManager.Roles.NOTIFICATIONS) && !this._notificationRemoved) { + if (extensionsHandlingNotifications > 0 && !this._notificationRemoved) { this.emit('notify-applet-update', notification); } else { if (notification.isTransient) diff --git a/js/ui/notificationDaemon.js b/js/ui/notificationDaemon.js index f14f844ab3..f81a6cb505 100644 --- a/js/ui/notificationDaemon.js +++ b/js/ui/notificationDaemon.js @@ -13,10 +13,6 @@ const MessageTray = imports.ui.messageTray; const Params = imports.misc.params; const Mainloop = imports.mainloop; -// don't automatically clear these apps' notifications on window focus -// lowercase only -const AUTOCLEAR_BLACKLIST = ['chromium', 'firefox', 'google chrome']; - let nextNotificationId = 1; // Should really be defined in Gio.js @@ -207,7 +203,7 @@ NotificationDaemon.prototype = { // // Either a pid or ndata.notification is needed to retrieve or // create a source. - _getSource: function(title, pid, ndata, sender, trayIcon) { + _getSource: function(title, pid, ndata, sender, desktopEntryHint, trayIcon) { if (!pid && !(ndata && ndata.notification)) return null; @@ -231,7 +227,7 @@ NotificationDaemon.prototype = { } } - let source = new Source(title, pid, sender, trayIcon); + let source = new Source(title, pid, sender, desktopEntryHint, trayIcon); source.setTransient(isForTransientNotification); if (!isForTransientNotification) { @@ -365,7 +361,7 @@ NotificationDaemon.prototype = { let sender = invocation.get_sender(); let pid = this._senderToPid[sender]; - let source = this._getSource(appName, pid, ndata, sender, null); + let source = this._getSource(appName, pid, ndata, sender, hints['desktop-entry'], null); if (source) { try { @@ -405,7 +401,7 @@ NotificationDaemon.prototype = { } let [pid] = result; - source = this._getSource(appName, pid, ndata, sender); + source = this._getSource(appName, pid, ndata, sender, hints['desktop-entry'], null); // We only store sender-pid entries for persistent sources. // Removing the entries once the source is destroyed @@ -586,8 +582,6 @@ NotificationDaemon.prototype = { return; let name = tracker.focus_app.get_name(); - if (name && AUTOCLEAR_BLACKLIST.includes(name.toLowerCase())) - return; for (let i = 0; i < this._sources.length; i++) { let source = this._sources[i]; @@ -609,7 +603,7 @@ NotificationDaemon.prototype = { }, _onTrayIconAdded: function(o, icon) { - let source = this._getSource(icon.title || icon.wm_class || _("Unknown"), icon.pid, null, null, icon); + let source = this._getSource(icon.title || icon.wm_class || _("Unknown"), icon.pid, null, null, null, icon); }, _onTrayIconRemoved: function(o, icon) { @@ -626,10 +620,11 @@ function Source(title, pid, sender, trayIcon) { Source.prototype = { __proto__: MessageTray.Source.prototype, - _init: function(title, pid, sender, trayIcon) { + _init: function(title, pid, sender, desktopEntryHint, trayIcon) { MessageTray.Source.prototype._init.call(this, title); this.initialTitle = title; + this.desktopEntryHint = desktopEntryHint; this.pid = pid; if (sender) @@ -676,8 +671,21 @@ Source.prototype = { let app; app = Cinnamon.WindowTracker.get_default().get_app_from_pid(this.pid); - if (app != null) - return app; + + if (!app && this.desktopEntryHint) { + const exceptions = { + "vivaldi-stable": "com.vivaldi.Vivaldi", + "brave-browser": "com.brave.Browser", + "google-chrome": "com.google.Chrome", + "microsoft-edge": "com.microsoft.Edge", + "opera": "com.opera.Opera" + }; + const exception = exceptions[this.desktopEntryHint]; + app = Cinnamon.AppSystem.get_default().lookup_flatpak_app_id(exception ? exception : this.desktopEntryHint); + if (!app) log('Failed to find flatpak app for notification with desktop-entry hint:', this.desktopEntryHint); + } + + if (app) return app; if (this.trayIcon) { app = Cinnamon.AppSystem.get_default().lookup_wmclass(this.trayIcon.wmclass); From ead34a69f2924ec5a83c6909eaf0d1ca11cff723 Mon Sep 17 00:00:00 2001 From: fredcw <58893963+fredcw@users.noreply.github.com> Date: Sun, 23 Nov 2025 00:25:25 +0000 Subject: [PATCH 02/11] Fix wrong capitalisation and add a comment --- .../grouped-window-list@cinnamon.org/settings-schema.json | 2 +- js/ui/messageTray.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/settings-schema.json b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/settings-schema.json index 7e093ccea2..c4f1b49cc2 100644 --- a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/settings-schema.json +++ b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/settings-schema.json @@ -194,7 +194,7 @@ "enable-notification-badges": { "type": "checkbox", "default": true, - "description": "Show notification Badges", + "description": "Show notification badges", "tooltip": "Indicate on the panel when an application has notifications" }, "enable-app-button-dragging": { diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js index 14f7a52f6c..7d224852c4 100644 --- a/js/ui/messageTray.js +++ b/js/ui/messageTray.js @@ -30,6 +30,9 @@ var LONGER_HIDE_TIMEOUT = 0.6; const NOTIFICATION_IMAGE_SIZE = 125; const NOTIFICATION_IMAGE_OPACITY = 230; // 0 - 255 +// Applets wishing to receive the "notify-applet-update" signal should increment and decrement this value when +// added and removed from the panel respectfully as when this value is zero, the signal will not be emitted and +// notifications will be automatically destroyed after being shown. var extensionsHandlingNotifications = 0; var State = Object.freeze({ From 5d02e17a0b4f2de20ce01d6f2c4f38baef6ccfe3 Mon Sep 17 00:00:00 2001 From: fredcw <58893963+fredcw@users.noreply.github.com> Date: Thu, 27 Nov 2025 20:36:32 +0000 Subject: [PATCH 03/11] Let theme decide badge text size --- .../cinnamon-sass/widgets/_windowlist.scss | 12 ++++--- .../appGroup.js | 32 +++++++------------ 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/data/theme/cinnamon-sass/widgets/_windowlist.scss b/data/theme/cinnamon-sass/widgets/_windowlist.scss index 3ddc2d19b5..467f006cbe 100644 --- a/data/theme/cinnamon-sass/widgets/_windowlist.scss +++ b/data/theme/cinnamon-sass/widgets/_windowlist.scss @@ -89,23 +89,25 @@ &-button-label { padding-left: 4px;} &-windows-badge { + z-index: 99; border-radius: 9999px; - color: $fg_color; - background-color: $accent_bg_color; + background-color: $bg_color; + color: $fg_color } &-windows-badge-label { - z-index: 99; + font-size: 0.8em; } &-notifications-badge { + z-index: 99; border-radius: 9999px; - color: $fg_color; background-color: $notification_badge_bg_color; + color: $fg_color } &-notifications-badge-label { - z-index: 99; + font-size: 0.8em; } } diff --git a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js index d6ba2479ea..8df5392ea8 100644 --- a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js +++ b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js @@ -393,6 +393,8 @@ class AppGroup { const allocWidth = box.x2 - box.x1; const allocHeight = box.y2 - box.y1; const childBox = new Clutter.ActorBox(); + const windowBadgeBox = new Clutter.ActorBox(); + const notifBadgeBox = new Clutter.ActorBox(); const direction = this.actor.get_text_direction(); // Set the icon to be left-justified (or right-justified) and centered vertically @@ -415,23 +417,21 @@ class AppGroup { this.iconBox.allocate(childBox, flags); - this.updateBadgesTextSize(); - // Set windows badge position - childBox.x1 = box.x1; - childBox.x2 = childBox.x1 + this.windowsBadgeLabel.width; - childBox.y1 = box.y2 - this.windowsBadge.get_preferred_height(childBox.get_width())[1]; - childBox.y2 = box.y2; + windowBadgeBox.x1 = box.x1; + windowBadgeBox.x2 = box.x1 + this.windowsBadgeLabel.width; + windowBadgeBox.y1 = box.y1; + windowBadgeBox.y2 = box.y1 + this.windowsBadge.get_preferred_height(windowBadgeBox.get_width())[1]; - this.windowsBadge.allocate(childBox, flags); + this.windowsBadge.allocate(windowBadgeBox, flags); // Set notifications badge position - childBox.x2 = this.iconBox.x + this.iconBox.width; - childBox.x1 = childBox.x2 - this.notificationsBadgeLabel.width; - childBox.y1 = box.y1; - childBox.y2 = childBox.y1 + this.notificationsBadge.get_preferred_height(childBox.get_width())[1]; + notifBadgeBox.x2 = this.iconBox.x + this.iconBox.width; + notifBadgeBox.x1 = notifBadgeBox.x2 - this.notificationsBadgeLabel.width; + notifBadgeBox.y1 = box.y1; + notifBadgeBox.y2 = box.y1 + this.notificationsBadge.get_preferred_height(notifBadgeBox.get_width())[1]; - this.notificationsBadge.allocate(childBox, flags); + this.notificationsBadge.allocate(notifBadgeBox, flags); // Set label position if (this.drawLabel) { @@ -479,14 +479,6 @@ class AppGroup { if (this.progressOverlay.visible) this.allocateProgress(childBox, flags); } - updateBadgesTextSize() { - const badgeTextSize = Math.round(Math.min(this.iconBox.width, this.iconBox.height) / 2.5 / global.ui_scale); - const badgePadding = Math.round(badgeTextSize / 4); - const sizeStyle = `font-size: ${badgeTextSize}px; padding-left: ${badgePadding}px; padding-right: ${badgePadding}px;`; - this.windowsBadgeLabel.set_style(sizeStyle); - this.notificationsBadgeLabel.set_style(sizeStyle); - } - showLabel(animate = false) { if (this.labelVisiblePref || !this.label From e9de7a8eeba4482ec32ce1af2815b5497f66c3d6 Mon Sep 17 00:00:00 2001 From: fredcw <58893963+fredcw@users.noreply.github.com> Date: Fri, 28 Nov 2025 18:22:39 +0000 Subject: [PATCH 04/11] Fix badge position and size --- .../appGroup.js | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js index 8df5392ea8..e92d1278e1 100644 --- a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js +++ b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js @@ -418,19 +418,21 @@ class AppGroup { this.iconBox.allocate(childBox, flags); // Set windows badge position - windowBadgeBox.x1 = box.x1; - windowBadgeBox.x2 = box.x1 + this.windowsBadgeLabel.width; - windowBadgeBox.y1 = box.y1; - windowBadgeBox.y2 = box.y1 + this.windowsBadge.get_preferred_height(windowBadgeBox.get_width())[1]; - + const windowBadgeXCenter = this.iconBox.x; + const windowBadgeYCenter = this.iconBox.y; + windowBadgeBox.x1 = Math.max(windowBadgeXCenter - Math.floor(this.windowsBadgeLabel.width / 2), 0); + windowBadgeBox.x2 = windowBadgeBox.x1 + this.windowsBadgeLabel.width; + windowBadgeBox.y1 = Math.max(windowBadgeYCenter - Math.floor(this.windowsBadgeLabel.height / 2), 0); + windowBadgeBox.y2 = windowBadgeBox.y1 + this.windowsBadgeLabel.height; this.windowsBadge.allocate(windowBadgeBox, flags); // Set notifications badge position - notifBadgeBox.x2 = this.iconBox.x + this.iconBox.width; + const notifBadgeXCenter = this.iconBox.x + this.iconBox.width; + const notifBadgeYCenter = this.iconBox.y; + notifBadgeBox.x2 = Math.min(notifBadgeXCenter + Math.floor(this.notificationsBadgeLabel.width / 2), box.x2); notifBadgeBox.x1 = notifBadgeBox.x2 - this.notificationsBadgeLabel.width; - notifBadgeBox.y1 = box.y1; - notifBadgeBox.y2 = box.y1 + this.notificationsBadge.get_preferred_height(notifBadgeBox.get_width())[1]; - + notifBadgeBox.y1 = Math.max(notifBadgeYCenter - Math.floor(this.notificationsBadgeLabel.height / 2), 0); + notifBadgeBox.y2 = notifBadgeBox.y1 + this.notificationsBadgeLabel.height; this.notificationsBadge.allocate(notifBadgeBox, flags); // Set label position @@ -1107,7 +1109,9 @@ class AppGroup { this.groupState.set({windowCount: this.groupState.metaWindows ? this.groupState.metaWindows.length : 0}); if (this.groupState.windowCount > 1 && this.state.settings.enableWindowCountBadges) { - this.windowsBadgeLabel.text = this.groupState.windowCount.toString(); + // paddingSpace is a hack to make make the single digit badge more rounded + const paddingSpace = this.groupState.windowCount < 10 ? " " : ""; + this.windowsBadgeLabel.text = paddingSpace + this.groupState.windowCount.toString() + paddingSpace; this.windowsBadge.show(); } else { this.windowsBadge.hide(); @@ -1116,7 +1120,9 @@ class AppGroup { updateNotificationsBadge() { if (this.notifications.length > 0 && this.state.settings.enableNotificationBadges) { - this.notificationsBadgeLabel.text = this.notifications.length.toString(); + // paddingSpace is a hack to make the single digit badge more rounded + const paddingSpace = this.notifications.length <10 ? " " : ""; + this.notificationsBadgeLabel.text = paddingSpace + this.notifications.length.toString() + paddingSpace; this.notificationsBadge.show(); } else { this.notificationsBadge.hide(); From e6dd69a34caf01f23d99e77f802b40e49f298c53 Mon Sep 17 00:00:00 2001 From: fredcw <58893963+fredcw@users.noreply.github.com> Date: Sat, 29 Nov 2025 07:47:30 +0000 Subject: [PATCH 05/11] set fixed size font in cinnamon theme And add extra method to identify a flatpak app from it's app name. This should catch most of the remaining flatpak apps that can't be identified from the desktop-entry hint --- .../cinnamon-sass/widgets/_windowlist.scss | 4 ++-- js/ui/notificationDaemon.js | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/data/theme/cinnamon-sass/widgets/_windowlist.scss b/data/theme/cinnamon-sass/widgets/_windowlist.scss index 467f006cbe..3e7c6eec7b 100644 --- a/data/theme/cinnamon-sass/widgets/_windowlist.scss +++ b/data/theme/cinnamon-sass/widgets/_windowlist.scss @@ -96,7 +96,7 @@ } &-windows-badge-label { - font-size: 0.8em; + font-size: 10px; } &-notifications-badge { @@ -107,7 +107,7 @@ } &-notifications-badge-label { - font-size: 0.8em; + font-size: 10px; } } diff --git a/js/ui/notificationDaemon.js b/js/ui/notificationDaemon.js index f81a6cb505..98b7c4b44a 100644 --- a/js/ui/notificationDaemon.js +++ b/js/ui/notificationDaemon.js @@ -672,6 +672,7 @@ Source.prototype = { app = Cinnamon.WindowTracker.get_default().get_app_from_pid(this.pid); + // With flatpak apps, the notification's pid is that of the portal so use the desktop-entry hint instead. if (!app && this.desktopEntryHint) { const exceptions = { "vivaldi-stable": "com.vivaldi.Vivaldi", @@ -682,6 +683,9 @@ Source.prototype = { }; const exception = exceptions[this.desktopEntryHint]; app = Cinnamon.AppSystem.get_default().lookup_flatpak_app_id(exception ? exception : this.desktopEntryHint); + if (!app) { + app = this._findUniqueAppByName(this.initialTitle); + } if (!app) log('Failed to find flatpak app for notification with desktop-entry hint:', this.desktopEntryHint); } @@ -696,6 +700,24 @@ Source.prototype = { return null; }, + _findUniqueAppByName(appName) { + const appSystem = Cinnamon.AppSystem.get_default(); + const runningApps = appSystem.get_running(); + const matches = []; + + for (const app of runningApps) { + if (app.get_name() === appName) { + matches.push(app); + } + } + + if (matches.length === 1) { + return matches[0]; + } else { + return null; + } + }, + _setApp: function() { if (this.app) return; From 0878b5b1525555a1b5a8fc6d456dbb769a0a0ef9 Mon Sep 17 00:00:00 2001 From: fredcw <58893963+fredcw@users.noreply.github.com> Date: Sat, 29 Nov 2025 10:28:10 +0000 Subject: [PATCH 06/11] Add badge position offset to better line up with corner of icon --- .../grouped-window-list@cinnamon.org/appGroup.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js index e92d1278e1..fc40ea7fe4 100644 --- a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js +++ b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js @@ -418,8 +418,9 @@ class AppGroup { this.iconBox.allocate(childBox, flags); // Set windows badge position - const windowBadgeXCenter = this.iconBox.x; - const windowBadgeYCenter = this.iconBox.y; + const windowBadgeOffset = 3 * global.ui_scale + const windowBadgeXCenter = this.iconBox.x + windowBadgeOffset; + const windowBadgeYCenter = this.iconBox.y + windowBadgeOffset; windowBadgeBox.x1 = Math.max(windowBadgeXCenter - Math.floor(this.windowsBadgeLabel.width / 2), 0); windowBadgeBox.x2 = windowBadgeBox.x1 + this.windowsBadgeLabel.width; windowBadgeBox.y1 = Math.max(windowBadgeYCenter - Math.floor(this.windowsBadgeLabel.height / 2), 0); @@ -427,8 +428,9 @@ class AppGroup { this.windowsBadge.allocate(windowBadgeBox, flags); // Set notifications badge position - const notifBadgeXCenter = this.iconBox.x + this.iconBox.width; - const notifBadgeYCenter = this.iconBox.y; + const notifBadgeOffset = 3 * global.ui_scale + const notifBadgeXCenter = this.iconBox.x + this.iconBox.width - notifBadgeOffset; + const notifBadgeYCenter = this.iconBox.y + notifBadgeOffset; notifBadgeBox.x2 = Math.min(notifBadgeXCenter + Math.floor(this.notificationsBadgeLabel.width / 2), box.x2); notifBadgeBox.x1 = notifBadgeBox.x2 - this.notificationsBadgeLabel.width; notifBadgeBox.y1 = Math.max(notifBadgeYCenter - Math.floor(this.notificationsBadgeLabel.height / 2), 0); From 1b824a613e98a68bf7270237994caa6d74307596 Mon Sep 17 00:00:00 2001 From: fredcw <58893963+fredcw@users.noreply.github.com> Date: Sat, 29 Nov 2025 14:18:28 +0000 Subject: [PATCH 07/11] Adjust badge sizes and colour --- data/theme/cinnamon-sass/_colors.scss | 2 +- data/theme/cinnamon-sass/widgets/_windowlist.scss | 4 ++-- .../grouped-window-list@cinnamon.org/appGroup.js | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/data/theme/cinnamon-sass/_colors.scss b/data/theme/cinnamon-sass/_colors.scss index 94a3bab940..b0b1063f83 100644 --- a/data/theme/cinnamon-sass/_colors.scss +++ b/data/theme/cinnamon-sass/_colors.scss @@ -18,7 +18,7 @@ $destructive_color: #ff7b63; $warning_color: #f8e45c; $warning_bg_color: #cd9309; -$notification_badge_bg_color: #ef2591; +$notification_badge_bg_color: #e74b37; $accent_color: #78aeed; $accent_bg_color: #3584e4; diff --git a/data/theme/cinnamon-sass/widgets/_windowlist.scss b/data/theme/cinnamon-sass/widgets/_windowlist.scss index 3e7c6eec7b..2749c253af 100644 --- a/data/theme/cinnamon-sass/widgets/_windowlist.scss +++ b/data/theme/cinnamon-sass/widgets/_windowlist.scss @@ -96,7 +96,7 @@ } &-windows-badge-label { - font-size: 10px; + font-size: 9px; } &-notifications-badge { @@ -107,7 +107,7 @@ } &-notifications-badge-label { - font-size: 10px; + font-size: 13px; } } diff --git a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js index fc40ea7fe4..6fc4045ba4 100644 --- a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js +++ b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js @@ -418,7 +418,7 @@ class AppGroup { this.iconBox.allocate(childBox, flags); // Set windows badge position - const windowBadgeOffset = 3 * global.ui_scale + const windowBadgeOffset = 3 * global.ui_scale; const windowBadgeXCenter = this.iconBox.x + windowBadgeOffset; const windowBadgeYCenter = this.iconBox.y + windowBadgeOffset; windowBadgeBox.x1 = Math.max(windowBadgeXCenter - Math.floor(this.windowsBadgeLabel.width / 2), 0); @@ -428,7 +428,7 @@ class AppGroup { this.windowsBadge.allocate(windowBadgeBox, flags); // Set notifications badge position - const notifBadgeOffset = 3 * global.ui_scale + const notifBadgeOffset = 3 * global.ui_scale; const notifBadgeXCenter = this.iconBox.x + this.iconBox.width - notifBadgeOffset; const notifBadgeYCenter = this.iconBox.y + notifBadgeOffset; notifBadgeBox.x2 = Math.min(notifBadgeXCenter + Math.floor(this.notificationsBadgeLabel.width / 2), box.x2); @@ -709,7 +709,7 @@ class AppGroup { } showOrderLabel(number) { - this.windowsBadgeLabel.text = (number + 1).toString(); + this.windowsBadgeLabel.text = " " + (number + 1) + " "; this.windowsBadge.show(); } @@ -1113,7 +1113,7 @@ class AppGroup { if (this.groupState.windowCount > 1 && this.state.settings.enableWindowCountBadges) { // paddingSpace is a hack to make make the single digit badge more rounded const paddingSpace = this.groupState.windowCount < 10 ? " " : ""; - this.windowsBadgeLabel.text = paddingSpace + this.groupState.windowCount.toString() + paddingSpace; + this.windowsBadgeLabel.text = paddingSpace + this.groupState.windowCount + paddingSpace; this.windowsBadge.show(); } else { this.windowsBadge.hide(); @@ -1124,7 +1124,7 @@ class AppGroup { if (this.notifications.length > 0 && this.state.settings.enableNotificationBadges) { // paddingSpace is a hack to make the single digit badge more rounded const paddingSpace = this.notifications.length <10 ? " " : ""; - this.notificationsBadgeLabel.text = paddingSpace + this.notifications.length.toString() + paddingSpace; + this.notificationsBadgeLabel.text = paddingSpace + this.notifications.length + paddingSpace; this.notificationsBadge.show(); } else { this.notificationsBadge.hide(); From 2e01890d14498de47313ac355bdc9793121a4df0 Mon Sep 17 00:00:00 2001 From: fredcw <58893963+fredcw@users.noreply.github.com> Date: Sat, 29 Nov 2025 15:48:25 +0000 Subject: [PATCH 08/11] Ensure badges are always round and center label --- .../appGroup.js | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js index 6fc4045ba4..752bfee5d9 100644 --- a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js +++ b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js @@ -128,7 +128,7 @@ class AppGroup { this.windowsBadge = new St.BoxLayout({ style_class: 'grouped-window-list-windows-badge', important: true, - x_align: St.Align.MIDDLE, + x_align: St.Align.START, y_align: St.Align.MIDDLE, show_on_set_parent: false, }); @@ -148,7 +148,7 @@ class AppGroup { this.notificationsBadge = new St.BoxLayout({ style_class: 'grouped-window-list-notifications-badge', important: true, - x_align: St.Align.MIDDLE, + x_align: St.Align.START, y_align: St.Align.MIDDLE, show_on_set_parent: false, }); @@ -421,20 +421,30 @@ class AppGroup { const windowBadgeOffset = 3 * global.ui_scale; const windowBadgeXCenter = this.iconBox.x + windowBadgeOffset; const windowBadgeYCenter = this.iconBox.y + windowBadgeOffset; - windowBadgeBox.x1 = Math.max(windowBadgeXCenter - Math.floor(this.windowsBadgeLabel.width / 2), 0); - windowBadgeBox.x2 = windowBadgeBox.x1 + this.windowsBadgeLabel.width; - windowBadgeBox.y1 = Math.max(windowBadgeYCenter - Math.floor(this.windowsBadgeLabel.height / 2), 0); - windowBadgeBox.y2 = windowBadgeBox.y1 + this.windowsBadgeLabel.height; + const windowBadgesize = Math.max(this.windowsBadgeLabel.width, this.windowsBadgeLabel.height); + windowBadgeBox.x1 = Math.max(windowBadgeXCenter - Math.floor(windowBadgesize / 2), 0); + windowBadgeBox.x2 = windowBadgeBox.x1 + windowBadgesize; + windowBadgeBox.y1 = Math.max(windowBadgeYCenter - Math.floor(windowBadgesize / 2), 0); + windowBadgeBox.y2 = windowBadgeBox.y1 + windowBadgesize; + const windowLabelPosX = Math.floor((windowBadgesize - this.windowsBadgeLabel.width) / 2); + const windowLabelPosY = Math.floor((windowBadgesize - this.windowsBadgeLabel.height) / 2); + this.windowsBadgeLabel.set_anchor_point(-windowLabelPosX, -windowLabelPosY); + this.windowsBadge.set_size(windowBadgesize, windowBadgesize); this.windowsBadge.allocate(windowBadgeBox, flags); // Set notifications badge position const notifBadgeOffset = 3 * global.ui_scale; const notifBadgeXCenter = this.iconBox.x + this.iconBox.width - notifBadgeOffset; const notifBadgeYCenter = this.iconBox.y + notifBadgeOffset; - notifBadgeBox.x2 = Math.min(notifBadgeXCenter + Math.floor(this.notificationsBadgeLabel.width / 2), box.x2); - notifBadgeBox.x1 = notifBadgeBox.x2 - this.notificationsBadgeLabel.width; - notifBadgeBox.y1 = Math.max(notifBadgeYCenter - Math.floor(this.notificationsBadgeLabel.height / 2), 0); - notifBadgeBox.y2 = notifBadgeBox.y1 + this.notificationsBadgeLabel.height; + const notifBadgesize = Math.max(this.notificationsBadgeLabel.width, this.notificationsBadgeLabel.height); + notifBadgeBox.x2 = Math.min(notifBadgeXCenter + Math.floor(notifBadgesize / 2), box.x2); + notifBadgeBox.x1 = notifBadgeBox.x2 - notifBadgesize; + notifBadgeBox.y1 = Math.max(notifBadgeYCenter - Math.floor(notifBadgesize / 2), 0); + notifBadgeBox.y2 = notifBadgeBox.y1 + notifBadgesize; + const notifLabelPosX = Math.floor((notifBadgesize - this.notificationsBadgeLabel.width) / 2); + const notifLabelPosY = Math.floor((notifBadgesize - this.notificationsBadgeLabel.height) / 2); + this.notificationsBadgeLabel.set_anchor_point(-notifLabelPosX, -notifLabelPosY); + this.notificationsBadge.set_size(notifBadgesize, notifBadgesize); this.notificationsBadge.allocate(notifBadgeBox, flags); // Set label position @@ -709,7 +719,7 @@ class AppGroup { } showOrderLabel(number) { - this.windowsBadgeLabel.text = " " + (number + 1) + " "; + this.windowsBadgeLabel.text = (number + 1).toString(); this.windowsBadge.show(); } @@ -1111,9 +1121,7 @@ class AppGroup { this.groupState.set({windowCount: this.groupState.metaWindows ? this.groupState.metaWindows.length : 0}); if (this.groupState.windowCount > 1 && this.state.settings.enableWindowCountBadges) { - // paddingSpace is a hack to make make the single digit badge more rounded - const paddingSpace = this.groupState.windowCount < 10 ? " " : ""; - this.windowsBadgeLabel.text = paddingSpace + this.groupState.windowCount + paddingSpace; + this.windowsBadgeLabel.text = this.groupState.windowCount.toString(); this.windowsBadge.show(); } else { this.windowsBadge.hide(); @@ -1122,9 +1130,7 @@ class AppGroup { updateNotificationsBadge() { if (this.notifications.length > 0 && this.state.settings.enableNotificationBadges) { - // paddingSpace is a hack to make the single digit badge more rounded - const paddingSpace = this.notifications.length <10 ? " " : ""; - this.notificationsBadgeLabel.text = paddingSpace + this.notifications.length + paddingSpace; + this.notificationsBadgeLabel.text = this.notifications.length.toString(); this.notificationsBadge.show(); } else { this.notificationsBadge.hide(); From b93fc9f141524194b8bcc3f246d3f38a84bc6068 Mon Sep 17 00:00:00 2001 From: fredcw <58893963+fredcw@users.noreply.github.com> Date: Sun, 30 Nov 2025 17:04:57 +0000 Subject: [PATCH 09/11] Fix minor label positioning bug --- .../grouped-window-list@cinnamon.org/appGroup.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js index 752bfee5d9..de4966ec5e 100644 --- a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js +++ b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js @@ -426,8 +426,9 @@ class AppGroup { windowBadgeBox.x2 = windowBadgeBox.x1 + windowBadgesize; windowBadgeBox.y1 = Math.max(windowBadgeYCenter - Math.floor(windowBadgesize / 2), 0); windowBadgeBox.y2 = windowBadgeBox.y1 + windowBadgesize; - const windowLabelPosX = Math.floor((windowBadgesize - this.windowsBadgeLabel.width) / 2); - const windowLabelPosY = Math.floor((windowBadgesize - this.windowsBadgeLabel.height) / 2); + const [wLabelMinWidth, wLabelMinHeight, wLabelNaturalWidth, wLabelNaturalHeight] = this.windowsBadgeLabel.get_preferred_size(); + const windowLabelPosX = Math.floor((windowBadgesize - wLabelNaturalWidth) / 2); + const windowLabelPosY = Math.floor((windowBadgesize - wLabelNaturalHeight) / 2); this.windowsBadgeLabel.set_anchor_point(-windowLabelPosX, -windowLabelPosY); this.windowsBadge.set_size(windowBadgesize, windowBadgesize); this.windowsBadge.allocate(windowBadgeBox, flags); @@ -441,8 +442,9 @@ class AppGroup { notifBadgeBox.x1 = notifBadgeBox.x2 - notifBadgesize; notifBadgeBox.y1 = Math.max(notifBadgeYCenter - Math.floor(notifBadgesize / 2), 0); notifBadgeBox.y2 = notifBadgeBox.y1 + notifBadgesize; - const notifLabelPosX = Math.floor((notifBadgesize - this.notificationsBadgeLabel.width) / 2); - const notifLabelPosY = Math.floor((notifBadgesize - this.notificationsBadgeLabel.height) / 2); + const [nLabelMinWidth, nLabelMinHeight, nLabelNaturalWidth, nLabelNaturalHeight] = this.notificationsBadgeLabel.get_preferred_size(); + const notifLabelPosX = Math.floor((notifBadgesize - nLabelNaturalWidth) / 2); + const notifLabelPosY = Math.floor((notifBadgesize - nLabelNaturalHeight) / 2); this.notificationsBadgeLabel.set_anchor_point(-notifLabelPosX, -notifLabelPosY); this.notificationsBadge.set_size(notifBadgesize, notifBadgesize); this.notificationsBadge.allocate(notifBadgeBox, flags); From d57498e1f9c978ee770c6e3f57359eec76dbe1a7 Mon Sep 17 00:00:00 2001 From: fredcw <58893963+fredcw@users.noreply.github.com> Date: Mon, 1 Dec 2025 04:45:04 +0000 Subject: [PATCH 10/11] Use old css classes for window count badge, no need to change them --- data/theme/cinnamon-sass/widgets/_windowlist.scss | 14 ++++++++------ .../grouped-window-list@cinnamon.org/appGroup.js | 8 ++++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/data/theme/cinnamon-sass/widgets/_windowlist.scss b/data/theme/cinnamon-sass/widgets/_windowlist.scss index 2749c253af..2fe2a715fe 100644 --- a/data/theme/cinnamon-sass/widgets/_windowlist.scss +++ b/data/theme/cinnamon-sass/widgets/_windowlist.scss @@ -88,15 +88,15 @@ &-button-label { padding-left: 4px;} - &-windows-badge { + &-number-label { + font-size: 10px; + color: $fg_color; z-index: 99; - border-radius: 9999px; - background-color: $bg_color; - color: $fg_color } - &-windows-badge-label { - font-size: 9px; + &-badge { + border-radius: 9999px; + background-color: $bg_color; } &-notifications-badge { @@ -108,6 +108,8 @@ &-notifications-badge-label { font-size: 13px; + color: $fg_color; + z-index: 99; } } diff --git a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js index de4966ec5e..a69c8cfe11 100644 --- a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js +++ b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js @@ -126,14 +126,14 @@ class AppGroup { this.setActorAttributes(null, params.metaWindow); this.windowsBadge = new St.BoxLayout({ - style_class: 'grouped-window-list-windows-badge', + style_class: 'grouped-window-list-badge', important: true, - x_align: St.Align.START, + x_align: St.Align.MIDDLE, y_align: St.Align.MIDDLE, show_on_set_parent: false, }); this.windowsBadgeLabel = new St.Label({ - style_class: 'grouped-window-list-windows-badge-label', + style_class: 'grouped-window-list-number-label', important: true, text: '' }); @@ -148,7 +148,7 @@ class AppGroup { this.notificationsBadge = new St.BoxLayout({ style_class: 'grouped-window-list-notifications-badge', important: true, - x_align: St.Align.START, + x_align: St.Align.MIDDLE, y_align: St.Align.MIDDLE, show_on_set_parent: false, }); From d371ffcb971aef6603d39b92c46a4e1398d6e1f0 Mon Sep 17 00:00:00 2001 From: fredcw <58893963+fredcw@users.noreply.github.com> Date: Mon, 1 Dec 2025 17:36:59 +0000 Subject: [PATCH 11/11] Remove duplicate lines --- data/theme/cinnamon-sass/widgets/_windowlist.scss | 7 ++----- .../applets/grouped-window-list@cinnamon.org/appGroup.js | 8 ++++---- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/data/theme/cinnamon-sass/widgets/_windowlist.scss b/data/theme/cinnamon-sass/widgets/_windowlist.scss index 2fe2a715fe..8a0e8494d9 100644 --- a/data/theme/cinnamon-sass/widgets/_windowlist.scss +++ b/data/theme/cinnamon-sass/widgets/_windowlist.scss @@ -90,7 +90,6 @@ &-number-label { font-size: 10px; - color: $fg_color; z-index: 99; } @@ -100,15 +99,13 @@ } &-notifications-badge { - z-index: 99; border-radius: 9999px; background-color: $notification_badge_bg_color; - color: $fg_color + color: $fg_color; + font-size: 13px; } &-notifications-badge-label { - font-size: 13px; - color: $fg_color; z-index: 99; } } diff --git a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js index a69c8cfe11..e4fda9b552 100644 --- a/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js +++ b/files/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/appGroup.js @@ -421,12 +421,12 @@ class AppGroup { const windowBadgeOffset = 3 * global.ui_scale; const windowBadgeXCenter = this.iconBox.x + windowBadgeOffset; const windowBadgeYCenter = this.iconBox.y + windowBadgeOffset; - const windowBadgesize = Math.max(this.windowsBadgeLabel.width, this.windowsBadgeLabel.height); + const [wLabelMinWidth, wLabelMinHeight, wLabelNaturalWidth, wLabelNaturalHeight] = this.windowsBadgeLabel.get_preferred_size(); + const windowBadgesize = Math.max(wLabelNaturalWidth, wLabelNaturalHeight); windowBadgeBox.x1 = Math.max(windowBadgeXCenter - Math.floor(windowBadgesize / 2), 0); windowBadgeBox.x2 = windowBadgeBox.x1 + windowBadgesize; windowBadgeBox.y1 = Math.max(windowBadgeYCenter - Math.floor(windowBadgesize / 2), 0); windowBadgeBox.y2 = windowBadgeBox.y1 + windowBadgesize; - const [wLabelMinWidth, wLabelMinHeight, wLabelNaturalWidth, wLabelNaturalHeight] = this.windowsBadgeLabel.get_preferred_size(); const windowLabelPosX = Math.floor((windowBadgesize - wLabelNaturalWidth) / 2); const windowLabelPosY = Math.floor((windowBadgesize - wLabelNaturalHeight) / 2); this.windowsBadgeLabel.set_anchor_point(-windowLabelPosX, -windowLabelPosY); @@ -437,12 +437,12 @@ class AppGroup { const notifBadgeOffset = 3 * global.ui_scale; const notifBadgeXCenter = this.iconBox.x + this.iconBox.width - notifBadgeOffset; const notifBadgeYCenter = this.iconBox.y + notifBadgeOffset; - const notifBadgesize = Math.max(this.notificationsBadgeLabel.width, this.notificationsBadgeLabel.height); + const [nLabelMinWidth, nLabelMinHeight, nLabelNaturalWidth, nLabelNaturalHeight] = this.notificationsBadgeLabel.get_preferred_size(); + const notifBadgesize = Math.max(nLabelNaturalWidth, nLabelNaturalHeight); notifBadgeBox.x2 = Math.min(notifBadgeXCenter + Math.floor(notifBadgesize / 2), box.x2); notifBadgeBox.x1 = notifBadgeBox.x2 - notifBadgesize; notifBadgeBox.y1 = Math.max(notifBadgeYCenter - Math.floor(notifBadgesize / 2), 0); notifBadgeBox.y2 = notifBadgeBox.y1 + notifBadgesize; - const [nLabelMinWidth, nLabelMinHeight, nLabelNaturalWidth, nLabelNaturalHeight] = this.notificationsBadgeLabel.get_preferred_size(); const notifLabelPosX = Math.floor((notifBadgesize - nLabelNaturalWidth) / 2); const notifLabelPosY = Math.floor((notifBadgesize - nLabelNaturalHeight) / 2); this.notificationsBadgeLabel.set_anchor_point(-notifLabelPosX, -notifLabelPosY);