Skip to content

Commit 7a3923c

Browse files
committed
GWL: add notification badges to panel icons
and remove "Show window count numbers" config option.
1 parent 5174d3c commit 7a3923c

File tree

10 files changed

+216
-58
lines changed

10 files changed

+216
-58
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: 120 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 NotificationDestroyedReason = imports.ui.messageTray.NotificationDestroyedReason;
1213
const {AppletSettings} = imports.ui.settings;
1314
const {SignalManager} = imports.misc.signalManager;
1415
const {throttle, unref, trySpawnCommandLine} = imports.misc.util;
@@ -161,6 +162,124 @@ 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+
const guessFlatpakAppIdFromDesktopEntryHint = (desktopEntry) => {
175+
let tryAppId = desktopEntry + '.desktop:flatpak';
176+
if (this._appGroupList.some(appGroup => appGroup.groupState.appId === tryAppId)) {
177+
return tryAppId;
178+
}
179+
const exceptions = {
180+
"vivaldi-stable": "com.vivaldi.Vivaldi",
181+
"brave-browser": "com.brave.Browser",
182+
"google-chrome": "com.google.Chrome",
183+
"microsoft-edge": "com.microsoft.Edge",
184+
"opera": "com.opera.Opera"
185+
};
186+
if (exceptions[desktopEntry]) {
187+
tryAppId = exceptions[desktopEntry] + '.desktop:flatpak';
188+
if (this._appGroupList.some(appGroup => appGroup.groupState.appId === tryAppId)) {
189+
return tryAppId;
190+
}
191+
}
192+
};
193+
194+
let appId = notification.source.app?.get_id();
195+
if (!appId) {
196+
appId = guessFlatpakAppIdFromDesktopEntryHint(notification.desktopEntry);
197+
}
198+
if (!appId) {
199+
global.logError('GWL: Failed to find appId for notification with desktopEntry hint: '
200+
+ notification.desktopEntry);
201+
return;
202+
}
203+
204+
// Add notification to all appgroups with appId
205+
let notificationAdded = false;
206+
this._appGroupList.forEach(appGroup => {
207+
if (!appGroup.groupState || appGroup.groupState.willUnmount) return;
208+
if (appId === appGroup.groupState.appId) {
209+
appGroup.notifications.push(notification);
210+
notificationAdded = true;
211+
this.updateNotificationsBadge(appGroup);
212+
}
213+
});
214+
if (notificationAdded) {
215+
notification.appId = appId;
216+
notification.connect('destroy', () => this._removeNotification(notification));
217+
}
218+
}
219+
220+
_removeNotification(notification) {
221+
this._appGroupList.forEach(appGroup => {
222+
if (!appGroup.groupState || appGroup.groupState.willUnmount) return;
223+
if (notification.appId === appGroup.groupState.appId) {
224+
const index = appGroup.notifications.indexOf(notification);
225+
if (index > -1) {
226+
appGroup.notifications.splice(index, 1)
227+
this.updateNotificationsBadge(appGroup);
228+
}
229+
}
230+
});
231+
}
232+
233+
// Called when an app is focused to remove all notifications from all instances of an app (with given appId)
234+
removeAllNotifications(appId) {
235+
this._appGroupList.forEach(appGroup => {
236+
if (!appGroup.groupState || appGroup.groupState.willUnmount) return;
237+
if (appId === appGroup.groupState.appId) {
238+
// iterate backwards due to in place array modification
239+
for (let i = appGroup.notifications.length - 1; i >= 0; i--) {
240+
if (appGroup.notifications[i] && !appGroup.notifications[i]._destroyed) {
241+
appGroup.notifications[i].destroy(NotificationDestroyedReason.DISMISSED);
242+
}
243+
}
244+
appGroup.notifications = [];
245+
this.updateNotificationsBadge(appGroup);
246+
}
247+
});
248+
}
249+
250+
updateNotificationsBadge(appGroup) {
251+
if (appGroup.notifications.length > 0) {
252+
appGroup.notificationsBadgeLabel.text = appGroup.notifications.length.toString();
253+
appGroup.notificationsBadge.show();
254+
} else {
255+
appGroup.notificationsBadge.hide();
256+
}
257+
}
258+
259+
// Called from AppGroup constructor so that we always have a list of all appgroups from all workspaces.
260+
addAppGroup(newAppGroup) {
261+
newAppGroup.notifications = [];
262+
263+
//Copy notifications from any existing appGroup with the same appId.
264+
this._appGroupList.some(appGroup => {
265+
if (!appGroup.groupState || appGroup.groupState.willUnmount) return false;
266+
if (appGroup.groupState.appId === newAppGroup.groupState.appId) {
267+
newAppGroup.notifications = appGroup.notifications.slice(); //shallow copy
268+
return true;
269+
}
270+
})
271+
272+
this._appGroupList.push(newAppGroup);
273+
274+
//remove old deleted appgroups
275+
this._appGroupList = this._appGroupList.filter(appGroup =>
276+
appGroup !== null &&
277+
appGroup !== undefined &&
278+
appGroup.groupState &&
279+
!appGroup.groupState.willUnmount);
280+
}
281+
}
282+
164283
class GroupedWindowListApplet extends Applet.Applet {
165284
constructor(metadata, orientation, panel_height, instance_id) {
166285
super(orientation, panel_height, instance_id);
@@ -191,6 +310,7 @@ class GroupedWindowListApplet extends Applet.Applet {
191310
appletReady: false,
192311
willUnmount: false,
193312
settings: {},
313+
notifications: new Notifications(),
194314
homeDir: GLib.get_home_dir(),
195315
lastOverlayPreview: null,
196316
lastCycled: -1,
@@ -307,7 +427,6 @@ class GroupedWindowListApplet extends Applet.Applet {
307427
{key: 'super-num-hotkeys', value: 'SuperNumHotkeys', cb: this.bindAppKeys},
308428
{key: 'title-display', value: 'titleDisplay', cb: this.updateTitleDisplay},
309429
{key: 'launcher-animation-effect', value: 'launcherAnimationEffect', cb: null},
310-
{key: 'number-display', value: 'numDisplay', cb: this.updateWindowNumberState},
311430
{key: 'enable-app-button-dragging', value: 'enableDragging', cb: this.draggableSettingChanged},
312431
{key: 'thumbnail-scroll-behavior', value: 'thumbnailScrollBehavior', cb: null},
313432
{key: 'show-thumbnails', value: 'showThumbs', cb: this.updateVerticalThumbnailState},
@@ -584,12 +703,6 @@ class GroupedWindowListApplet extends Applet.Applet {
584703
});
585704
}
586705

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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "Grouped window list",
3-
"role": "panellauncher, windowattentionhandler",
3+
"role": "panellauncher, windowattentionhandler, notifications",
44
"uuid": "[email protected]",
55
"description": "Main Cinnamon window list with app grouping",
66
"icon": "cs-windows",

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)