Skip to content

Commit b33185a

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

File tree

8 files changed

+156
-54
lines changed

8 files changed

+156
-54
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: 88 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class AppGroup {
7070
appInfo: params.app.get_app_info(),
7171
metaWindows: params.metaWindows || [],
7272
windowCount: params.metaWindows ? params.metaWindows.length : 0,
73+
notificationCount: 0,
7374
lastFocused: params.metaWindow || null,
7475
isFavoriteApp: !params.metaWindow ? true : params.isFavoriteApp === true,
7576
autoStartIndex: this.state.autoStartApps.findIndex( app => app.id === params.appId),
@@ -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',
@@ -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();
418+
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);
404426

405-
this.badge.allocate(childBox, flags);
427+
// Set notifications badge position
428+
childBox.x2 = box.x2;
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.updateNotificationsBadge();
920957
this.onFocusChange();
921958
}
922959
set({
@@ -1071,23 +1108,42 @@ class AppGroup {
10711108
this.checkFocusStyle();
10721109
}
10731110

1074-
calcWindowNumber() {
1111+
incrementNotificationCount() {
10751112
if (this.groupState.willUnmount) return;
10761113

1077-
const windowCount = this.groupState.metaWindows ? this.groupState.metaWindows.length : 0;
1078-
this.numberLabel.text = windowCount.toString();
1114+
this.groupState.set({ notificationCount: this.groupState.notificationCount + 1 });
1115+
this.updateNotificationsBadge();
1116+
}
10791117

1080-
this.groupState.set({windowCount});
1118+
resetNotificationCount() {
1119+
if (this.groupState.willUnmount) return;
1120+
1121+
this.groupState.set({ notificationCount : 0 });
1122+
this.updateNotificationsBadge();
1123+
}
10811124

1082-
if (this.state.settings.numDisplay) {
1083-
if (windowCount <= 1) {
1084-
this.badge.hide();
1085-
} else {
1086-
this.badge.show();
1125+
calcWindowNumber() {
1126+
if (this.groupState.willUnmount) return;
10871127

1088-
}
1128+
this.groupState.set({windowCount: this.groupState.metaWindows ? this.groupState.metaWindows.length : 0});
1129+
this.updateWindowsBadge();
1130+
}
1131+
1132+
updateWindowsBadge(){
1133+
if (this.groupState.windowCount > 1) {
1134+
this.windowsBadgeLabel.text = this.groupState.windowCount.toString();
1135+
this.windowsBadge.show();
1136+
} else {
1137+
this.windowsBadge.hide();
1138+
}
1139+
}
1140+
1141+
updateNotificationsBadge(){
1142+
if (this.groupState.notificationCount > 0) {
1143+
this.notificationsBadgeLabel.text = this.groupState.notificationCount.toString();
1144+
this.notificationsBadge.show();
10891145
} else {
1090-
this.badge.hide();
1146+
this.notificationsBadge.hide();
10911147
}
10921148
}
10931149

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

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,6 @@ class GroupedWindowListApplet extends Applet.Applet {
307307
{key: 'super-num-hotkeys', value: 'SuperNumHotkeys', cb: this.bindAppKeys},
308308
{key: 'title-display', value: 'titleDisplay', cb: this.updateTitleDisplay},
309309
{key: 'launcher-animation-effect', value: 'launcherAnimationEffect', cb: null},
310-
{key: 'number-display', value: 'numDisplay', cb: this.updateWindowNumberState},
311310
{key: 'enable-app-button-dragging', value: 'enableDragging', cb: this.draggableSettingChanged},
312311
{key: 'thumbnail-scroll-behavior', value: 'thumbnailScrollBehavior', cb: null},
313312
{key: 'show-thumbnails', value: 'showThumbs', cb: this.updateVerticalThumbnailState},
@@ -584,12 +583,6 @@ class GroupedWindowListApplet extends Applet.Applet {
584583
});
585584
}
586585

587-
updateWindowNumberState() {
588-
this.workspaces.forEach(
589-
workspace => workspace.calcAllWindowNumbers()
590-
);
591-
}
592-
593586
updateAttentionState(display, window) {
594587
this.workspaces.forEach(
595588
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]/workspace.js

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ class Workspace {
3232
},
3333
updateFocusState: (focusedAppId) => {
3434
this.appGroups.forEach( appGroup => {
35-
if (focusedAppId === appGroup.groupState.appId) return;
35+
if (focusedAppId === appGroup.groupState.appId) {
36+
appGroup.resetNotificationCount();
37+
return;
38+
}
3639
appGroup.onFocusChange(false);
3740
});
3841
}
@@ -53,6 +56,7 @@ class Workspace {
5356
// Ugly change: refresh the removed app instances from all workspaces
5457
this.signals.connect(this.metaWorkspace, 'window-removed', (...args) => this.windowRemoved(...args));
5558
this.signals.connect(global.window_manager, 'switch-workspace' , (...args) => this.reloadList(...args));
59+
Main.messageTray.connect('notify-applet-update', (mtray, notification) => this.notificationReceived(mtray, notification));
5660
this.on_orientation_changed(null, true);
5761
}
5862

@@ -77,6 +81,44 @@ class Workspace {
7781
return windowCount;
7882
}
7983

84+
notificationReceived(mtray, notification) {
85+
const guessFlatpakAppIdFromDesktopEntryHint = (desktopEntry) => {
86+
let tryAppId = desktopEntry + '.desktop:flatpak';
87+
if (this.appGroups.some(appGroup => appGroup.groupState.appId === tryAppId)) {
88+
return tryAppId;
89+
}
90+
const exceptions = {
91+
"vivaldi-stable": "com.vivaldi.Vivaldi",
92+
"brave-browser": "com.brave.Browser",
93+
"google-chrome": "com.google.Chrome",
94+
"microsoft-edge": "com.microsoft.Edge",
95+
"opera": "com.opera.Opera"
96+
};
97+
if (exceptions[desktopEntry]) {
98+
tryAppId = exceptions[desktopEntry] + '.desktop:flatpak';
99+
if (this.appGroups.some(appGroup => appGroup.groupState.appId === tryAppId)) {
100+
return tryAppId;
101+
}
102+
}
103+
};
104+
105+
let appId = notification.source.app?.get_id();
106+
if (!appId) {
107+
appId = guessFlatpakAppIdFromDesktopEntryHint(notification.desktopEntry);
108+
}
109+
if (!appId) {
110+
global.logError(`GWL: Failed to find appId for notification`);
111+
return;
112+
}
113+
114+
this.appGroups.forEach(appGroup => {
115+
if (appId === appGroup.groupState.appId) {
116+
appGroup.incrementNotificationCount();
117+
return;
118+
}
119+
});
120+
}
121+
80122
closeAllHoverMenus(cb) {
81123
this.appGroups.forEach( appGroup => {
82124
const {hoverMenu, groupState} = appGroup;

js/ui/messageTray.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ URLHighlighter.prototype = {
222222
* Creates a notification with the associated title and body
223223
*
224224
* @params can contain values for 'body', 'icon', 'titleMarkup',
225-
* 'bodyMarkup', and 'silent' parameters.
225+
* 'bodyMarkup', 'silent', and 'desktopEntry' parameters.
226226
*
227227
* By default, the icon shown is created by calling
228228
* source.createNotificationIcon(). However, if @params contains an 'icon'
@@ -251,6 +251,7 @@ var Notification = class Notification {
251251
// 'transient' is a reserved keyword in JS, so we have to use an alternate variable name
252252
this.isTransient = false;
253253
this.silent = false;
254+
this.desktopEntry = "";
254255
this._destroyed = false;
255256
this._useActionIcons = false;
256257
this._titleDirection = St.TextDirection.NONE;
@@ -349,10 +350,12 @@ var Notification = class Notification {
349350
icon: null,
350351
titleMarkup: false,
351352
bodyMarkup: false,
352-
silent: false
353+
silent: false,
354+
desktopEntry: ""
353355
});
354356

355357
this.silent = params.silent;
358+
this.desktopEntry = params.desktopEntry;
356359

357360
if (this._icon && params.icon) {
358361
this._icon.destroy();

js/ui/notificationDaemon.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,8 @@ NotificationDaemon.prototype = {
423423
notification = new MessageTray.Notification(source, summary, body,
424424
{ icon: iconActor,
425425
bodyMarkup: true,
426-
silent: hints['suppress-sound'] });
426+
silent: hints['suppress-sound'],
427+
desktopEntry: hints['desktop-entry'] });
427428
ndata.notification = notification;
428429
notification.connect('destroy', Lang.bind(this,
429430
function(n, reason) {
@@ -460,7 +461,8 @@ NotificationDaemon.prototype = {
460461
} else {
461462
notification.update(summary, body, { icon: iconActor,
462463
bodyMarkup: true,
463-
silent: hints['suppress-sound'] });
464+
silent: hints['suppress-sound'],
465+
desktopEntry: hints['desktop-entry'] });
464466
}
465467

466468
// We only display a large image if an icon is also specified.

0 commit comments

Comments
 (0)