Skip to content

Commit 74f74ea

Browse files
committed
GWL: add notification badges
GWL: add notification badges to top right of panel icons and remove "Show window count numbers" config option. 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.
1 parent 5174d3c commit 74f74ea

File tree

11 files changed

+200
-75
lines changed

11 files changed

+200
-75
lines changed

data/theme/cinnamon-sass/_colors.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ $destructive_color: #ff7b63;
1818
$warning_color: #f8e45c;
1919
$warning_bg_color: #cd9309;
2020

21+
$notification_badge_bg_color: #ef2591;
22+
2123
$accent_color: #78aeed;
2224
$accent_bg_color: #3584e4;
2325

data/theme/cinnamon-sass/widgets/_windowlist.scss

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,24 @@
7373

7474
&-button-label { padding-left: 4px;}
7575

76-
&-number-label {
77-
font-size: 0.8em;
76+
&-windows-badge {
77+
border-radius: 9999px;
78+
color: $fg_color;
79+
background-color: $accent_bg_color;
80+
}
81+
82+
&-windows-badge-label {
7883
z-index: 99;
7984
}
8085

81-
&-badge {
86+
&-notifications-badge {
8287
border-radius: 9999px;
83-
background-color: $bg_color;
88+
color: $fg_color;
89+
background-color: $notification_badge_bg_color;
90+
}
91+
92+
&-notifications-badge-label {
93+
z-index: 99;
8494
}
8595
}
8696

files/usr/share/cinnamon/applets/[email protected]/appGroup.js

Lines changed: 66 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -119,31 +119,50 @@ class AppGroup {
119119
});
120120
this.actor.add_child(this.progressOverlay);
121121

122-
// Create the app button icon, number label, and text label for titleDisplay
122+
// Create the app button icon, window count and notification badges, and text label for titleDisplay
123123
this.iconBox = new Cinnamon.Slicer({name: 'appMenuIcon'});
124124
this.actor.add_child(this.iconBox);
125125
this.setActorAttributes(null, params.metaWindow);
126126

127-
this.badge = new St.BoxLayout({
128-
style_class: 'grouped-window-list-badge',
127+
this.windowsBadge = new St.BoxLayout({
128+
style_class: 'grouped-window-list-windows-badge',
129129
important: true,
130+
x_align: St.Align.MIDDLE,
131+
y_align: St.Align.MIDDLE,
132+
show_on_set_parent: false,
133+
});
134+
this.windowsBadgeLabel = new St.Label({
135+
style_class: 'grouped-window-list-windows-badge-label',
136+
important: true,
137+
text: ''
138+
});
139+
this.windowsBadgeLabel.clutter_text.ellipsize = false;
140+
this.windowsBadge.add(this.windowsBadgeLabel, {
130141
x_align: St.Align.START,
142+
y_align: St.Align.START,
143+
});
144+
this.actor.add_child(this.windowsBadge);
145+
this.windowsBadge.set_text_direction(St.TextDirection.LTR);
146+
147+
this.notificationsBadge = new St.BoxLayout({
148+
style_class: 'grouped-window-list-notifications-badge',
149+
important: true,
150+
x_align: St.Align.MIDDLE,
131151
y_align: St.Align.MIDDLE,
132152
show_on_set_parent: false,
133153
});
134-
this.numberLabel = new St.Label({
135-
style_class: 'grouped-window-list-number-label',
154+
this.notificationsBadgeLabel = new St.Label({
155+
style_class: 'grouped-window-list-notifications-badge-label',
136156
important: true,
137-
text: '',
138-
anchor_x: -3 * global.ui_scale,
157+
text: ''
139158
});
140-
this.numberLabel.clutter_text.ellipsize = false;
141-
this.badge.add(this.numberLabel, {
159+
this.notificationsBadgeLabel.clutter_text.ellipsize = false;
160+
this.notificationsBadge.add(this.notificationsBadgeLabel, {
142161
x_align: St.Align.START,
143162
y_align: St.Align.START,
144163
});
145-
this.actor.add_child(this.badge);
146-
this.badge.set_text_direction(St.TextDirection.LTR);
164+
this.actor.add_child(this.notificationsBadge);
165+
this.notificationsBadge.set_text_direction(St.TextDirection.LTR);
147166

148167
this.label = new St.Label({
149168
style_class: 'grouped-window-list-button-label',
@@ -172,6 +191,7 @@ class AppGroup {
172191
this.calcWindowNumber();
173192
this.on_orientation_changed(true);
174193
this.handleFavorite();
194+
this.state.notifications.addAppGroup(this);
175195
}
176196

177197
initThumbnailMenu() {
@@ -394,15 +414,23 @@ class AppGroup {
394414

395415
this.iconBox.allocate(childBox, flags);
396416

397-
// Set badge position
398-
const windowCountFactor = this.groupState.windowCount > 9 ? 1.5 : 2;
399-
const badgeOffset = 2 * global.ui_scale;
400-
childBox.x1 = childBox.x1 - badgeOffset;
401-
childBox.x2 = childBox.x1 + (this.numberLabel.width * windowCountFactor);
402-
childBox.y1 = Math.max(childBox.y1 - badgeOffset, 0);
403-
childBox.y2 = childBox.y1 + this.badge.get_preferred_height(childBox.get_width())[1];
417+
this.updateBadgesTextSize();
404418

405-
this.badge.allocate(childBox, flags);
419+
// Set windows badge position
420+
childBox.x1 = box.x1;
421+
childBox.x2 = childBox.x1 + this.windowsBadgeLabel.width;
422+
childBox.y1 = box.y2 - this.windowsBadge.get_preferred_height(childBox.get_width())[1];
423+
childBox.y2 = box.y2;
424+
425+
this.windowsBadge.allocate(childBox, flags);
426+
427+
// Set notifications badge position
428+
childBox.x2 = this.iconBox.x + this.iconBox.width;
429+
childBox.x1 = childBox.x2 - this.notificationsBadgeLabel.width;
430+
childBox.y1 = box.y1;
431+
childBox.y2 = childBox.y1 + this.notificationsBadge.get_preferred_height(childBox.get_width())[1];
432+
433+
this.notificationsBadge.allocate(childBox, flags);
406434

407435
// Set label position
408436
if (this.drawLabel) {
@@ -450,6 +478,14 @@ class AppGroup {
450478
if (this.progressOverlay.visible) this.allocateProgress(childBox, flags);
451479
}
452480

481+
updateBadgesTextSize() {
482+
const badgeTextSize = Math.round(this.iconBox.width / 2.5 / global.ui_scale);
483+
const badgePadding = Math.round(badgeTextSize / 4);
484+
const sizeStyle = `font-size: ${badgeTextSize}px; padding-left: ${badgePadding}px; padding-right: ${badgePadding}px;`;
485+
this.windowsBadgeLabel.set_style(sizeStyle);
486+
this.notificationsBadgeLabel.set_style(sizeStyle);
487+
}
488+
453489
showLabel(animate = false) {
454490
if (this.labelVisiblePref
455491
|| !this.label
@@ -676,8 +712,8 @@ class AppGroup {
676712
}
677713

678714
showOrderLabel(number) {
679-
this.numberLabel.text = (number + 1).toString();
680-
this.badge.show();
715+
this.windowsBadgeLabel.text = (number + 1).toString();
716+
this.windowsBadge.show();
681717
}
682718

683719
launchNewInstance(offload=false) {
@@ -917,6 +953,7 @@ class AppGroup {
917953
this.setIcon(metaWindow)
918954

919955
this.calcWindowNumber();
956+
this.state.notifications.updateNotificationsBadge(this);
920957
this.onFocusChange();
921958
}
922959
set({
@@ -1074,20 +1111,16 @@ class AppGroup {
10741111
calcWindowNumber() {
10751112
if (this.groupState.willUnmount) return;
10761113

1077-
const windowCount = this.groupState.metaWindows ? this.groupState.metaWindows.length : 0;
1078-
this.numberLabel.text = windowCount.toString();
1079-
1080-
this.groupState.set({windowCount});
1081-
1082-
if (this.state.settings.numDisplay) {
1083-
if (windowCount <= 1) {
1084-
this.badge.hide();
1085-
} else {
1086-
this.badge.show();
1114+
this.groupState.set({windowCount: this.groupState.metaWindows ? this.groupState.metaWindows.length : 0});
1115+
this.updateWindowsBadge();
1116+
}
10871117

1088-
}
1118+
updateWindowsBadge(){
1119+
if (this.groupState.windowCount > 1) {
1120+
this.windowsBadgeLabel.text = this.groupState.windowCount.toString();
1121+
this.windowsBadge.show();
10891122
} else {
1090-
this.badge.hide();
1123+
this.windowsBadge.hide();
10911124
}
10921125
}
10931126

files/usr/share/cinnamon/applets/[email protected]/applet.js

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const Applet = imports.ui.applet;
99
const Cinnamon = imports.gi.Cinnamon;
1010
const Main = imports.ui.main;
1111
const DND = imports.ui.dnd;
12+
const MessageTray = imports.ui.messageTray;
1213
const {AppletSettings} = imports.ui.settings;
1314
const {SignalManager} = imports.misc.signalManager;
1415
const {throttle, unref, trySpawnCommandLine} = imports.misc.util;
@@ -161,6 +162,83 @@ class PinnedFavs {
161162
}
162163
}
163164

165+
class Notifications {
166+
// As an app can have multiple appgroups (multiple windows, different workspaces, multiple instances), all
167+
// appGroups with the same appId should always display the same number of notifications.
168+
constructor() {
169+
this._appGroupList = []; //stores all appGroups from all workspaces
170+
Main.messageTray.connect('notify-applet-update', (mtray, notification) => this._notificationReceived(mtray, notification));
171+
}
172+
173+
_notificationReceived(mtray, notification) {
174+
let appId = notification.source.app?.get_id();
175+
176+
if (!appId) {
177+
return;
178+
}
179+
180+
// Add notification to all appgroups with appId
181+
let notificationAdded = false;
182+
this._appGroupList.forEach(appGroup => {
183+
if (!appGroup.groupState || appGroup.groupState.willUnmount) return;
184+
if (appId === appGroup.groupState.appId) {
185+
appGroup.notifications.push(notification);
186+
notificationAdded = true;
187+
this.updateNotificationsBadge(appGroup);
188+
}
189+
});
190+
if (notificationAdded) {
191+
notification.appId = appId;
192+
notification.connect('destroy', () => this._removeNotification(notification));
193+
}
194+
}
195+
196+
_removeNotification(notification) {
197+
this._appGroupList.forEach(appGroup => {
198+
if (!appGroup.groupState || appGroup.groupState.willUnmount) return;
199+
if (notification.appId === appGroup.groupState.appId) {
200+
const index = appGroup.notifications.indexOf(notification);
201+
if (index > -1) {
202+
appGroup.notifications.splice(index, 1)
203+
this.updateNotificationsBadge(appGroup);
204+
}
205+
}
206+
});
207+
}
208+
209+
updateNotificationsBadge(appGroup) {
210+
if (appGroup.notifications.length > 0) {
211+
appGroup.notificationsBadgeLabel.text = appGroup.notifications.length.toString();
212+
appGroup.notificationsBadge.show();
213+
} else {
214+
appGroup.notificationsBadge.hide();
215+
}
216+
}
217+
218+
// Called from AppGroup constructor so that we always have a list of all appgroups from all workspaces.
219+
addAppGroup(newAppGroup) {
220+
newAppGroup.notifications = [];
221+
222+
//Copy notifications from any existing appGroup with the same appId.
223+
this._appGroupList.some(appGroup => {
224+
if (!appGroup.groupState || appGroup.groupState.willUnmount) return false;
225+
if (appGroup.groupState.appId === newAppGroup.groupState.appId) {
226+
newAppGroup.notifications = appGroup.notifications.slice(); //shallow copy
227+
return true;
228+
}
229+
})
230+
231+
this._appGroupList.push(newAppGroup);
232+
233+
//remove old deleted appgroups
234+
this._appGroupList = this._appGroupList.filter(appGroup =>
235+
appGroup !== null &&
236+
appGroup !== undefined &&
237+
appGroup.groupState &&
238+
!appGroup.groupState.willUnmount);
239+
}
240+
}
241+
164242
class GroupedWindowListApplet extends Applet.Applet {
165243
constructor(metadata, orientation, panel_height, instance_id) {
166244
super(orientation, panel_height, instance_id);
@@ -191,6 +269,7 @@ class GroupedWindowListApplet extends Applet.Applet {
191269
appletReady: false,
192270
willUnmount: false,
193271
settings: {},
272+
notifications: new Notifications(),
194273
homeDir: GLib.get_home_dir(),
195274
lastOverlayPreview: null,
196275
lastCycled: -1,
@@ -307,7 +386,6 @@ class GroupedWindowListApplet extends Applet.Applet {
307386
{key: 'super-num-hotkeys', value: 'SuperNumHotkeys', cb: this.bindAppKeys},
308387
{key: 'title-display', value: 'titleDisplay', cb: this.updateTitleDisplay},
309388
{key: 'launcher-animation-effect', value: 'launcherAnimationEffect', cb: null},
310-
{key: 'number-display', value: 'numDisplay', cb: this.updateWindowNumberState},
311389
{key: 'enable-app-button-dragging', value: 'enableDragging', cb: this.draggableSettingChanged},
312390
{key: 'thumbnail-scroll-behavior', value: 'thumbnailScrollBehavior', cb: null},
313391
{key: 'show-thumbnails', value: 'showThumbs', cb: this.updateVerticalThumbnailState},
@@ -357,6 +435,7 @@ class GroupedWindowListApplet extends Applet.Applet {
357435
}
358436
this.bindAppKeys();
359437
this.state.set({appletReady: true});
438+
MessageTray.extensionsHandlingNotifications++;
360439
}
361440

362441
_updateState(initialUpdate) {
@@ -424,6 +503,7 @@ class GroupedWindowListApplet extends Applet.Applet {
424503
});
425504
this.settings.finalize();
426505
unref(this, RESERVE_KEYS);
506+
MessageTray.extensionsHandlingNotifications--;
427507
}
428508

429509
on_panel_icon_size_changed(iconSize) {
@@ -584,12 +664,6 @@ class GroupedWindowListApplet extends Applet.Applet {
584664
});
585665
}
586666

587-
updateWindowNumberState() {
588-
this.workspaces.forEach(
589-
workspace => workspace.calcAllWindowNumbers()
590-
);
591-
}
592-
593667
updateAttentionState(display, window) {
594668
this.workspaces.forEach(
595669
workspace => workspace.updateAttentionState(display, window)

files/usr/share/cinnamon/applets/[email protected]/settings-schema.json

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
"keys": [
5151
"title-display",
5252
"launcher-animation-effect",
53-
"number-display",
5453
"enable-app-button-dragging"
5554
]
5655
},
@@ -184,11 +183,6 @@
184183
"Scale": 3
185184
}
186185
},
187-
"number-display": {
188-
"type": "checkbox",
189-
"default": true,
190-
"description": "Show window count numbers"
191-
},
192186
"enable-app-button-dragging": {
193187
"type": "checkbox",
194188
"default": true,

files/usr/share/cinnamon/applets/[email protected]/applet.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const PopupMenu = imports.ui.popupMenu;
77
const St = imports.gi.St;
88
const Mainloop = imports.mainloop;
99
const Urgency = imports.ui.messageTray.Urgency;
10+
const MessageTray = imports.ui.messageTray;
1011
const NotificationDestroyedReason = imports.ui.messageTray.NotificationDestroyedReason;
1112
const Settings = imports.ui.settings;
1213
const Gettext = imports.gettext.domain("cinnamon-applets");
@@ -53,6 +54,10 @@ class CinnamonNotificationsApplet extends Applet.TextIconApplet {
5354
on_applet_removed_from_panel () {
5455
Main.keybindingManager.removeHotKey("notification-open-" + this.instance_id);
5556
Main.keybindingManager.removeHotKey("notification-clear-" + this.instance_id);
57+
MessageTray.extensionsHandlingNotifications--;
58+
if (MessageTray.extensionsHandlingNotifications === 0) {
59+
this._clear_all();
60+
}
5661
}
5762

5863
_openMenu() {
@@ -244,6 +249,7 @@ class CinnamonNotificationsApplet extends Applet.TextIconApplet {
244249

245250
on_applet_added_to_panel() {
246251
this.on_orientation_changed(this._orientation);
252+
MessageTray.extensionsHandlingNotifications++;
247253
}
248254

249255
on_orientation_changed (orientation) {

files/usr/share/cinnamon/applets/[email protected]/metadata.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,5 @@
22
33
"name": "Notifications",
44
"description": "Click to display and manage system notifications",
5-
"role": "notifications",
65
"icon": "cs-notifications"
76
}

0 commit comments

Comments
 (0)