Skip to content

Commit 4907aa0

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 4907aa0

File tree

11 files changed

+208
-74
lines changed

11 files changed

+208
-74
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: 75 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ class AppGroup {
9999
this.labelVisiblePref = this.state.settings.titleDisplay !== TitleDisplay.None && this.state.isHorizontal;
100100
this.drawLabel = this.labelVisiblePref;
101101
this.progress = 0;
102+
this.notifications = [];
102103

103104
this.actor = new Cinnamon.GenericContainer({
104105
name: 'appButton',
@@ -119,31 +120,50 @@ class AppGroup {
119120
});
120121
this.actor.add_child(this.progressOverlay);
121122

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

127-
this.badge = new St.BoxLayout({
128-
style_class: 'grouped-window-list-badge',
128+
this.windowsBadge = new St.BoxLayout({
129+
style_class: 'grouped-window-list-windows-badge',
129130
important: true,
131+
x_align: St.Align.MIDDLE,
132+
y_align: St.Align.MIDDLE,
133+
show_on_set_parent: false,
134+
});
135+
this.windowsBadgeLabel = new St.Label({
136+
style_class: 'grouped-window-list-windows-badge-label',
137+
important: true,
138+
text: ''
139+
});
140+
this.windowsBadgeLabel.clutter_text.ellipsize = false;
141+
this.windowsBadge.add(this.windowsBadgeLabel, {
130142
x_align: St.Align.START,
143+
y_align: St.Align.START,
144+
});
145+
this.actor.add_child(this.windowsBadge);
146+
this.windowsBadge.set_text_direction(St.TextDirection.LTR);
147+
148+
this.notificationsBadge = new St.BoxLayout({
149+
style_class: 'grouped-window-list-notifications-badge',
150+
important: true,
151+
x_align: St.Align.MIDDLE,
131152
y_align: St.Align.MIDDLE,
132153
show_on_set_parent: false,
133154
});
134-
this.numberLabel = new St.Label({
135-
style_class: 'grouped-window-list-number-label',
155+
this.notificationsBadgeLabel = new St.Label({
156+
style_class: 'grouped-window-list-notifications-badge-label',
136157
important: true,
137-
text: '',
138-
anchor_x: -3 * global.ui_scale,
158+
text: ''
139159
});
140-
this.numberLabel.clutter_text.ellipsize = false;
141-
this.badge.add(this.numberLabel, {
160+
this.notificationsBadgeLabel.clutter_text.ellipsize = false;
161+
this.notificationsBadge.add(this.notificationsBadgeLabel, {
142162
x_align: St.Align.START,
143163
y_align: St.Align.START,
144164
});
145-
this.actor.add_child(this.badge);
146-
this.badge.set_text_direction(St.TextDirection.LTR);
165+
this.actor.add_child(this.notificationsBadge);
166+
this.notificationsBadge.set_text_direction(St.TextDirection.LTR);
147167

148168
this.label = new St.Label({
149169
style_class: 'grouped-window-list-button-label',
@@ -172,6 +192,7 @@ class AppGroup {
172192
this.calcWindowNumber();
173193
this.on_orientation_changed(true);
174194
this.handleFavorite();
195+
this.state.trigger('copyNotifications', this);
175196
}
176197

177198
initThumbnailMenu() {
@@ -394,15 +415,23 @@ class AppGroup {
394415

395416
this.iconBox.allocate(childBox, flags);
396417

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];
418+
this.updateBadgesTextSize();
419+
420+
// Set windows badge position
421+
childBox.x1 = box.x1;
422+
childBox.x2 = childBox.x1 + this.windowsBadgeLabel.width;
423+
childBox.y1 = box.y2 - this.windowsBadge.get_preferred_height(childBox.get_width())[1];
424+
childBox.y2 = box.y2;
404425

405-
this.badge.allocate(childBox, flags);
426+
this.windowsBadge.allocate(childBox, flags);
427+
428+
// Set notifications badge position
429+
childBox.x2 = this.iconBox.x + this.iconBox.width;
430+
childBox.x1 = childBox.x2 - this.notificationsBadgeLabel.width;
431+
childBox.y1 = box.y1;
432+
childBox.y2 = childBox.y1 + this.notificationsBadge.get_preferred_height(childBox.get_width())[1];
433+
434+
this.notificationsBadge.allocate(childBox, flags);
406435

407436
// Set label position
408437
if (this.drawLabel) {
@@ -450,6 +479,14 @@ class AppGroup {
450479
if (this.progressOverlay.visible) this.allocateProgress(childBox, flags);
451480
}
452481

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

678715
showOrderLabel(number) {
679-
this.numberLabel.text = (number + 1).toString();
680-
this.badge.show();
716+
this.windowsBadgeLabel.text = (number + 1).toString();
717+
this.windowsBadge.show();
681718
}
682719

683720
launchNewInstance(offload=false) {
@@ -917,6 +954,7 @@ class AppGroup {
917954
this.setIcon(metaWindow)
918955

919956
this.calcWindowNumber();
957+
this.updateNotificationsBadge();
920958
this.onFocusChange();
921959
}
922960
set({
@@ -1074,20 +1112,25 @@ class AppGroup {
10741112
calcWindowNumber() {
10751113
if (this.groupState.willUnmount) return;
10761114

1077-
const windowCount = this.groupState.metaWindows ? this.groupState.metaWindows.length : 0;
1078-
this.numberLabel.text = windowCount.toString();
1079-
1080-
this.groupState.set({windowCount});
1115+
this.groupState.set({windowCount: this.groupState.metaWindows ? this.groupState.metaWindows.length : 0});
1116+
this.updateWindowsBadge();
1117+
}
10811118

1082-
if (this.state.settings.numDisplay) {
1083-
if (windowCount <= 1) {
1084-
this.badge.hide();
1085-
} else {
1086-
this.badge.show();
1119+
updateWindowsBadge(){
1120+
if (this.groupState.windowCount > 1) {
1121+
this.windowsBadgeLabel.text = this.groupState.windowCount.toString();
1122+
this.windowsBadge.show();
1123+
} else {
1124+
this.windowsBadge.hide();
1125+
}
1126+
}
10871127

1088-
}
1128+
updateNotificationsBadge() {
1129+
if (this.notifications.length > 0) {
1130+
this.notificationsBadgeLabel.text = this.notifications.length.toString();
1131+
this.notificationsBadge.show();
10891132
} else {
1090-
this.badge.hide();
1133+
this.notificationsBadge.hide();
10911134
}
10921135
}
10931136

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

Lines changed: 80 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;
@@ -255,6 +256,7 @@ class GroupedWindowListApplet extends Applet.Applet {
255256
openAbout: () => this.openAbout(),
256257
configureApplet: () => this.configureApplet(),
257258
removeApplet: (event) => this.confirmRemoveApplet(event),
259+
copyNotifications: (appGroup) => this.copyNotifications(appGroup)
258260
});
259261

260262
this.settings = new AppletSettings(this.state.settings, metadata.uuid, instance_id);
@@ -291,6 +293,7 @@ class GroupedWindowListApplet extends Applet.Applet {
291293
this.signals.connect(global.display, 'window-created', (...args) => this.onWindowCreated(...args));
292294
this.signals.connect(global.settings, 'changed::panel-edit-mode', (...args) => this.on_panel_edit_mode_changed(...args));
293295
this.signals.connect(Main.themeManager, 'theme-set', (...args) => this.refreshCurrentWorkspace(...args));
296+
this.signals.connect(Main.messageTray, 'notify-applet-update', this._onNotificationReceived.bind(this));
294297
}
295298

296299
bindSettings() {
@@ -307,7 +310,6 @@ class GroupedWindowListApplet extends Applet.Applet {
307310
{key: 'super-num-hotkeys', value: 'SuperNumHotkeys', cb: this.bindAppKeys},
308311
{key: 'title-display', value: 'titleDisplay', cb: this.updateTitleDisplay},
309312
{key: 'launcher-animation-effect', value: 'launcherAnimationEffect', cb: null},
310-
{key: 'number-display', value: 'numDisplay', cb: this.updateWindowNumberState},
311313
{key: 'enable-app-button-dragging', value: 'enableDragging', cb: this.draggableSettingChanged},
312314
{key: 'thumbnail-scroll-behavior', value: 'thumbnailScrollBehavior', cb: null},
313315
{key: 'show-thumbnails', value: 'showThumbs', cb: this.updateVerticalThumbnailState},
@@ -357,6 +359,7 @@ class GroupedWindowListApplet extends Applet.Applet {
357359
}
358360
this.bindAppKeys();
359361
this.state.set({appletReady: true});
362+
MessageTray.extensionsHandlingNotifications++;
360363
}
361364

362365
_updateState(initialUpdate) {
@@ -424,6 +427,10 @@ class GroupedWindowListApplet extends Applet.Applet {
424427
});
425428
this.settings.finalize();
426429
unref(this, RESERVE_KEYS);
430+
MessageTray.extensionsHandlingNotifications--;
431+
if (MessageTray.extensionsHandlingNotifications === 0) {
432+
this._destroyAllNotifications();
433+
}
427434
}
428435

429436
on_panel_icon_size_changed(iconSize) {
@@ -584,12 +591,6 @@ class GroupedWindowListApplet extends Applet.Applet {
584591
});
585592
}
586593

587-
updateWindowNumberState() {
588-
this.workspaces.forEach(
589-
workspace => workspace.calcAllWindowNumbers()
590-
);
591-
}
592-
593594
updateAttentionState(display, window) {
594595
this.workspaces.forEach(
595596
workspace => workspace.updateAttentionState(display, window)
@@ -1022,6 +1023,78 @@ class GroupedWindowListApplet extends Applet.Applet {
10221023
this.state.set({thumbnailCloseButtonOffset: global.ui_scale > 1 ? -10 : 0});
10231024
this.refreshAllWorkspaces();
10241025
}
1026+
1027+
copyNotifications(newAppGroup) {
1028+
// Copy notifications from any existing appGroup with the same appId.
1029+
this.workspaces.some(workspace => {
1030+
if (!workspace) return false;
1031+
return workspace.appGroups.some(appGroup => {
1032+
if (!appGroup || !appGroup.groupState || appGroup.groupState.willUnmount) return false;
1033+
if (appGroup.groupState.appId === newAppGroup.groupState.appId) {
1034+
newAppGroup.notifications = appGroup.notifications.slice(); // Shallow copy.
1035+
return true;
1036+
}
1037+
return false;
1038+
});
1039+
});
1040+
}
1041+
1042+
_onNotificationReceived(mtray, notification) {
1043+
let appId = notification.source.app?.get_id();
1044+
1045+
if (!appId) {
1046+
return;
1047+
}
1048+
1049+
// Add notification to all appgroups with appId.
1050+
let notificationAdded = false;
1051+
1052+
this.workspaces.forEach(workspace => {
1053+
if (!workspace) return;
1054+
workspace.appGroups.forEach(appGroup => {
1055+
if (!appGroup || !appGroup.groupState || appGroup.groupState.willUnmount) return;
1056+
if (appId === appGroup.groupState.appId) {
1057+
appGroup.notifications.push(notification);
1058+
notificationAdded = true;
1059+
appGroup.updateNotificationsBadge();
1060+
}
1061+
});
1062+
});
1063+
1064+
if (notificationAdded) {
1065+
notification.appId = appId;
1066+
notification.connect('destroy', () => this._onNotificationDestroyed(notification));
1067+
}
1068+
}
1069+
1070+
_onNotificationDestroyed(notification) {
1071+
this.workspaces.forEach(workspace => {
1072+
if (!workspace) return;
1073+
workspace.appGroups.forEach(appGroup => {
1074+
if (!appGroup || !appGroup.groupState || appGroup.groupState.willUnmount) return;
1075+
if (notification.appId === appGroup.groupState.appId) {
1076+
const index = appGroup.notifications.indexOf(notification);
1077+
if (index > -1) {
1078+
appGroup.notifications.splice(index, 1)
1079+
appGroup.updateNotificationsBadge();
1080+
}
1081+
}
1082+
});
1083+
});
1084+
}
1085+
1086+
_destroyAllNotifications() {
1087+
this.workspaces.forEach(workspace => {
1088+
if (!workspace) return;
1089+
workspace.appGroups.forEach(appGroup => {
1090+
if (!appGroup || !appGroup.groupState || appGroup.groupState.willUnmount) return;
1091+
// Iterate backwards due to in place element deletion.
1092+
for (let i = appGroup.notifications.length - 1; i >= 0; i--) {
1093+
appGroup.notifications[i].destroy();
1094+
}
1095+
});
1096+
});
1097+
}
10251098
}
10261099

10271100
function main(metadata, orientation, panel_height, instance_id) {

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,

0 commit comments

Comments
 (0)