Skip to content

Commit c173f8c

Browse files
committed
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.
1 parent a878e1a commit c173f8c

File tree

12 files changed

+233
-67
lines changed

12 files changed

+233
-67
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
@@ -88,14 +88,24 @@
8888

8989
&-button-label { padding-left: 4px;}
9090

91-
&-number-label {
92-
font-size: 0.8em;
91+
&-windows-badge {
92+
border-radius: 9999px;
93+
color: $fg_color;
94+
background-color: $accent_bg_color;
95+
}
96+
97+
&-windows-badge-label {
9398
z-index: 99;
9499
}
95100

96-
&-badge {
101+
&-notifications-badge {
97102
border-radius: 9999px;
98-
background-color: $bg_color;
103+
color: $fg_color;
104+
background-color: $notification_badge_bg_color;
105+
}
106+
107+
&-notifications-badge-label {
108+
z-index: 99;
99109
}
100110
}
101111

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

Lines changed: 73 additions & 33 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;
425+
426+
this.windowsBadge.allocate(childBox, flags);
404427

405-
this.badge.allocate(childBox, flags);
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(Math.min(this.iconBox.width, this.iconBox.height) / 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,22 @@ 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});
1081-
1082-
if (this.state.settings.numDisplay) {
1083-
if (windowCount <= 1) {
1084-
this.badge.hide();
1085-
} else {
1086-
this.badge.show();
1115+
this.groupState.set({windowCount: this.groupState.metaWindows ? this.groupState.metaWindows.length : 0});
1116+
1117+
if (this.groupState.windowCount > 1 && this.state.settings.enableWindowCountBadges) {
1118+
this.windowsBadgeLabel.text = this.groupState.windowCount.toString();
1119+
this.windowsBadge.show();
1120+
} else {
1121+
this.windowsBadge.hide();
1122+
}
1123+
}
10871124

1088-
}
1125+
updateNotificationsBadge() {
1126+
if (this.notifications.length > 0 && this.state.settings.enableNotificationBadges) {
1127+
this.notificationsBadgeLabel.text = this.notifications.length.toString();
1128+
this.notificationsBadge.show();
10891129
} else {
1090-
this.badge.hide();
1130+
this.notificationsBadge.hide();
10911131
}
10921132
}
10931133

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

Lines changed: 95 additions & 2 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,8 @@ 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},
313+
{key: 'enable-window-count-badges', value: 'enableWindowCountBadges', cb: this.onEnableWindowCountBadgeChange},
314+
{key: 'enable-notification-badges', value: 'enableNotificationBadges', cb: this.onEnableNotificationsChange},
311315
{key: 'enable-app-button-dragging', value: 'enableDragging', cb: this.draggableSettingChanged},
312316
{key: 'thumbnail-scroll-behavior', value: 'thumbnailScrollBehavior', cb: null},
313317
{key: 'show-thumbnails', value: 'showThumbs', cb: this.updateVerticalThumbnailState},
@@ -357,6 +361,7 @@ class GroupedWindowListApplet extends Applet.Applet {
357361
}
358362
this.bindAppKeys();
359363
this.state.set({appletReady: true});
364+
MessageTray.extensionsHandlingNotifications++;
360365
}
361366

362367
_updateState(initialUpdate) {
@@ -424,6 +429,10 @@ class GroupedWindowListApplet extends Applet.Applet {
424429
});
425430
this.settings.finalize();
426431
unref(this, RESERVE_KEYS);
432+
MessageTray.extensionsHandlingNotifications--;
433+
if (MessageTray.extensionsHandlingNotifications === 0) {
434+
this._destroyAllNotifications();
435+
}
427436
}
428437

429438
on_panel_icon_size_changed(iconSize) {
@@ -584,7 +593,7 @@ class GroupedWindowListApplet extends Applet.Applet {
584593
});
585594
}
586595

587-
updateWindowNumberState() {
596+
onEnableWindowCountBadgeChange() {
588597
this.workspaces.forEach(
589598
workspace => workspace.calcAllWindowNumbers()
590599
);
@@ -1022,6 +1031,90 @@ class GroupedWindowListApplet extends Applet.Applet {
10221031
this.state.set({thumbnailCloseButtonOffset: global.ui_scale > 1 ? -10 : 0});
10231032
this.refreshAllWorkspaces();
10241033
}
1034+
1035+
copyNotifications(newAppGroup) {
1036+
// Copy notifications from any existing appGroup with the same appId.
1037+
this.workspaces.some(workspace => {
1038+
if (!workspace) return false;
1039+
return workspace.appGroups.some(appGroup => {
1040+
if (!appGroup || !appGroup.groupState || appGroup.groupState.willUnmount) return false;
1041+
if (appGroup.groupState.appId === newAppGroup.groupState.appId) {
1042+
newAppGroup.notifications = appGroup.notifications.slice(); // Shallow copy.
1043+
return true;
1044+
}
1045+
return false;
1046+
});
1047+
});
1048+
}
1049+
1050+
_onNotificationReceived(mtray, notification) {
1051+
let appId = notification.source.app?.get_id();
1052+
1053+
if (!appId) {
1054+
return;
1055+
}
1056+
1057+
// Add notification to all appgroups with appId.
1058+
let notificationAdded = false;
1059+
1060+
this.workspaces.forEach(workspace => {
1061+
if (!workspace) return;
1062+
workspace.appGroups.forEach(appGroup => {
1063+
if (!appGroup || !appGroup.groupState || appGroup.groupState.willUnmount) return;
1064+
if (appId === appGroup.groupState.appId) {
1065+
appGroup.notifications.push(notification);
1066+
notificationAdded = true;
1067+
appGroup.updateNotificationsBadge();
1068+
}
1069+
});
1070+
});
1071+
1072+
if (notificationAdded) {
1073+
notification.appId = appId;
1074+
notification.connect('destroy', () => this._onNotificationDestroyed(notification));
1075+
}
1076+
}
1077+
1078+
_onNotificationDestroyed(notification) {
1079+
if (!this.workspaces) return;
1080+
1081+
this.workspaces.forEach(workspace => {
1082+
if (!workspace) return;
1083+
workspace.appGroups.forEach(appGroup => {
1084+
if (!appGroup || !appGroup.groupState || appGroup.groupState.willUnmount) return;
1085+
if (notification.appId === appGroup.groupState.appId) {
1086+
const index = appGroup.notifications.indexOf(notification);
1087+
if (index > -1) {
1088+
appGroup.notifications.splice(index, 1)
1089+
appGroup.updateNotificationsBadge();
1090+
}
1091+
}
1092+
});
1093+
});
1094+
}
1095+
1096+
_destroyAllNotifications() {
1097+
this.workspaces.forEach(workspace => {
1098+
if (!workspace) return;
1099+
workspace.appGroups.forEach(appGroup => {
1100+
if (!appGroup || !appGroup.groupState || appGroup.groupState.willUnmount) return;
1101+
// Iterate backwards due to in place element deletion.
1102+
for (let i = appGroup.notifications.length - 1; i >= 0; i--) {
1103+
appGroup.notifications[i].destroy();
1104+
}
1105+
});
1106+
});
1107+
}
1108+
1109+
onEnableNotificationsChange() {
1110+
this.workspaces.forEach(workspace => {
1111+
if (!workspace) return;
1112+
workspace.appGroups.forEach(appGroup => {
1113+
if (!appGroup || !appGroup.groupState || appGroup.groupState.willUnmount) return;
1114+
appGroup.updateNotificationsBadge();
1115+
});
1116+
});
1117+
}
10251118
}
10261119

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

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@
5050
"keys": [
5151
"title-display",
5252
"launcher-animation-effect",
53-
"number-display",
53+
"enable-window-count-badges",
54+
"enable-notification-badges",
5455
"enable-app-button-dragging"
5556
]
5657
},
@@ -184,10 +185,17 @@
184185
"Scale": 3
185186
}
186187
},
187-
"number-display": {
188+
"enable-window-count-badges": {
188189
"type": "checkbox",
189190
"default": true,
190-
"description": "Show window count numbers"
191+
"description": "Show window count badges",
192+
"tooltip": "Indicate on the panel the number of open windows an application has"
193+
},
194+
"enable-notification-badges": {
195+
"type": "checkbox",
196+
"default": true,
197+
"description": "Show notification Badges",
198+
"tooltip": "Indicate on the panel when an application has notifications"
191199
},
192200
"enable-app-button-dragging": {
193201
"type": "checkbox",

0 commit comments

Comments
 (0)