Skip to content

Commit 44a520f

Browse files
committed
window-list applet: Add notification badges
1 parent a878e1a commit 44a520f

File tree

2 files changed

+112
-10
lines changed

2 files changed

+112
-10
lines changed

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

Lines changed: 107 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ const PopupMenu = imports.ui.popupMenu;
6363
const Settings = imports.ui.settings;
6464
const SignalManager = imports.misc.signalManager;
6565
const Tooltips = imports.ui.tooltips;
66+
const MessageTray = imports.ui.messageTray;
6667
const WindowUtils = imports.misc.windowUtils;
6768

6869
const MAX_TEXT_LENGTH = 1000;
@@ -309,6 +310,27 @@ class AppMenuButton {
309310
this._label = new St.Label();
310311
this.actor.add_actor(this._label);
311312

313+
this.notificationsBadge = new St.BoxLayout({
314+
style_class: 'grouped-window-list-notifications-badge',
315+
important: true,
316+
x_align: St.Align.MIDDLE,
317+
y_align: St.Align.MIDDLE,
318+
show_on_set_parent: false,
319+
});
320+
this.notificationsBadgeLabel = new St.Label({
321+
style_class: 'grouped-window-list-notifications-badge-label',
322+
important: true,
323+
text: ''
324+
});
325+
this.notificationsBadgeLabel.clutter_text.ellipsize = false;
326+
this.notificationsBadge.add(this.notificationsBadgeLabel, {
327+
x_align: St.Align.START,
328+
y_align: St.Align.START,
329+
});
330+
this.actor.add_child(this.notificationsBadge);
331+
this.notificationsBadge.set_text_direction(St.TextDirection.LTR);
332+
this.notificationsBadge.show();
333+
312334
this.updateLabelVisible();
313335

314336
this._visible = true;
@@ -362,6 +384,9 @@ class AppMenuButton {
362384
this.onScrollModeChanged();
363385
this._needsAttention = false;
364386

387+
this.app = this._getApp();
388+
this.appId = this.app ? this.app.get_id() : null;
389+
this.updateNotificationsBadge();
365390
this.setDisplayTitle();
366391
this.onFocus();
367392
this.setIcon();
@@ -507,10 +532,7 @@ class AppMenuButton {
507532

508533
setDisplayTitle() {
509534
let title = this.metaWindow.get_title();
510-
let tracker = Cinnamon.WindowTracker.get_default();
511-
let app = tracker.get_window_app(this.metaWindow);
512-
513-
if (!title) title = app ? app.get_name() : '?';
535+
if (!title) title = this.app ? this.app.get_name() : '?';
514536

515537
/* Sanitize the window title to prevent dodgy window titles such as
516538
* "); DROP TABLE windows; --. Turn all whitespaces into " " because
@@ -527,6 +549,18 @@ class AppMenuButton {
527549
this._label.set_text(title);
528550
}
529551

552+
_getApp() {
553+
const tracker = Cinnamon.WindowTracker.get_default();
554+
let app = tracker.get_window_app(this.metaWindow);
555+
if (!app) {
556+
app = tracker.get_app_from_pid(this.metaWindow.get_pid());
557+
}
558+
if (!app) {
559+
app = tracker.get_app_from_pid(this.metaWindow.get_client_pid());
560+
}
561+
return app;
562+
}
563+
530564
destroy() {
531565
if (this._flashTimer) {
532566
Mainloop.source_remove(this._flashTimer);
@@ -687,7 +721,8 @@ class AppMenuButton {
687721
let allocWidth = box.x2 - box.x1;
688722
let allocHeight = box.y2 - box.y1;
689723

690-
let childBox = new Clutter.ActorBox();
724+
const childBox = new Clutter.ActorBox();
725+
const notifBadgeBox = new Clutter.ActorBox();
691726

692727
let [minWidth, minHeight, naturalWidth, naturalHeight] = this._iconBox.get_preferred_size();
693728

@@ -717,6 +752,22 @@ class AppMenuButton {
717752
}
718753
this._iconBox.allocate(childBox, flags);
719754

755+
// Set notifications badge position
756+
const notifBadgeOffset = 3 * global.ui_scale;
757+
const notifBadgeXCenter = this._iconBox.x + this._iconBox.width - notifBadgeOffset;
758+
const notifBadgeYCenter = this._iconBox.y + notifBadgeOffset;
759+
const [nLabelMinWidth, nLabelMinHeight, nLabelNaturalWidth, nLabelNaturalHeight] = this.notificationsBadgeLabel.get_preferred_size();
760+
const notifBadgesize = Math.max(nLabelNaturalWidth, nLabelNaturalHeight);
761+
notifBadgeBox.x2 = Math.min(notifBadgeXCenter + Math.floor(notifBadgesize / 2), box.x2);
762+
notifBadgeBox.x1 = notifBadgeBox.x2 - notifBadgesize;
763+
notifBadgeBox.y1 = Math.max(notifBadgeYCenter - Math.floor(notifBadgesize / 2), 0);
764+
notifBadgeBox.y2 = notifBadgeBox.y1 + notifBadgesize;
765+
const notifLabelPosX = Math.floor((notifBadgesize - nLabelNaturalWidth) / 2);
766+
const notifLabelPosY = Math.floor((notifBadgesize - nLabelNaturalHeight) / 2);
767+
this.notificationsBadgeLabel.set_anchor_point(-notifLabelPosX, -notifLabelPosY);
768+
this.notificationsBadge.set_size(notifBadgesize, notifBadgesize);
769+
this.notificationsBadge.allocate(notifBadgeBox, flags);
770+
720771
if (this.drawLabel) {
721772
[minWidth, minHeight, naturalWidth, naturalHeight] = this._label.get_preferred_size();
722773

@@ -763,13 +814,10 @@ class AppMenuButton {
763814
}
764815

765816
setIcon() {
766-
let tracker = Cinnamon.WindowTracker.get_default();
767-
let app = tracker.get_window_app(this.metaWindow);
768-
769817
this.icon_size = this._applet.icon_size;
770818

771-
let icon = app ?
772-
app.create_icon_texture_for_window(this.icon_size, this.metaWindow) :
819+
let icon = this.app ?
820+
this.app.create_icon_texture_for_window(this.icon_size, this.metaWindow) :
773821
new St.Icon({ icon_name: 'application-default-icon',
774822
icon_type: St.IconType.FULLCOLOR,
775823
icon_size: this.icon_size });
@@ -815,6 +863,17 @@ class AppMenuButton {
815863
return continueFlashing;
816864
});
817865
}
866+
867+
updateNotificationsBadge() {
868+
const nCount = Main.notificationDaemon.getNotificationCountForApp(this.app);
869+
870+
if (nCount > 0 && this._applet.enableNotifications) {
871+
this.notificationsBadgeLabel.text = nCount.toString();
872+
this.notificationsBadge.show();
873+
} else {
874+
this.notificationsBadge.hide();
875+
}
876+
}
818877
};
819878

820879
class AppMenuButtonRightClickMenu extends Applet.AppletPopupMenu {
@@ -1043,6 +1102,7 @@ class CinnamonWindowListApplet extends Applet.Applet {
10431102
this.settings.bind("window-hover", "windowHover", this._onPreviewChanged);
10441103
this.settings.bind("window-preview-show-label", "showLabel", this._onPreviewChanged);
10451104
this.settings.bind("window-preview-scale", "previewScale", this._onPreviewChanged);
1105+
this.settings.bind("enable-notifications", "enableNotifications", this._updateAllNotificationBadges);
10461106
this.settings.bind("last-window-order", "lastWindowOrder", null);
10471107

10481108
this.signals.connect(global.display, 'window-created', this._onWindowAddedAsync, this);
@@ -1052,6 +1112,7 @@ class CinnamonWindowListApplet extends Applet.Applet {
10521112
this.signals.connect(Main.panelManager, 'monitors-changed', this._updateWatchedMonitors, this);
10531113
this.signals.connect(global.window_manager, 'switch-workspace', this._refreshAllItems, this);
10541114
this.signals.connect(Cinnamon.WindowTracker.get_default(), "window-app-changed", this._onWindowAppChanged, this);
1115+
this.signals.connect(Main.messageTray, 'notify-applet-update', this._onNotificationReceived, this);
10551116

10561117
this.signals.connect(this.actor, 'style-changed', Lang.bind(this, this._updateSpacing));
10571118

@@ -1064,11 +1125,13 @@ class CinnamonWindowListApplet extends Applet.Applet {
10641125
on_applet_added_to_panel(userEnabled) {
10651126
this._updateSpacing();
10661127
this.appletEnabled = true;
1128+
MessageTray.extensionsHandlingNotifications++;
10671129
}
10681130

10691131
on_applet_removed_from_panel() {
10701132
this.signals.disconnectAllSignals();
10711133
this.settings.finalize();
1134+
MessageTray.extensionsHandlingNotifications--;
10721135
}
10731136

10741137
on_applet_instances_changed() {
@@ -1507,6 +1570,40 @@ class CinnamonWindowListApplet extends Applet.Applet {
15071570
this._tooltipErodeTimer = null;
15081571
}
15091572
}
1573+
1574+
_onNotificationReceived(mtray, notification) {
1575+
let appId = notification.source.app?.get_id();
1576+
1577+
if (!appId) {
1578+
return;
1579+
}
1580+
1581+
// Add notification to all appMenuButton's with appId
1582+
let notificationAdded = false;
1583+
this._windows.forEach(window => {
1584+
if (appId === window.appId) {
1585+
notificationAdded = true;
1586+
window.updateNotificationsBadge();
1587+
}
1588+
});
1589+
1590+
if (notificationAdded) {
1591+
notification.appId = appId;
1592+
notification.connect('destroy', () => this._onNotificationDestroyed(notification));
1593+
}
1594+
}
1595+
1596+
_onNotificationDestroyed(notification) {
1597+
this._windows.forEach(window => {
1598+
if (notification.appId === window.appId) {
1599+
window.updateNotificationsBadge();
1600+
}
1601+
});
1602+
}
1603+
1604+
_updateAllNotificationBadges() {
1605+
this._windows.forEach(window => window.updateNotificationsBadge());
1606+
}
15101607
}
15111608

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

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@
8787
},
8888
"dependency": "window-hover=thumbnail"
8989
},
90+
"enable-notifications": {
91+
"type": "switch",
92+
"default": true,
93+
"description": "Show notification badges"
94+
},
9095
"last-window-order": {
9196
"type": "generic",
9297
"default": ""

0 commit comments

Comments
 (0)