@@ -9,6 +9,8 @@ const Applet = imports.ui.applet;
99const Cinnamon = imports . gi . Cinnamon ;
1010const Main = imports . ui . main ;
1111const DND = imports . ui . dnd ;
12+ const NotificationDestroyedReason = imports . ui . messageTray . NotificationDestroyedReason ;
13+ const MessageTray = imports . ui . messageTray ;
1214const { AppletSettings} = imports . ui . settings ;
1315const { SignalManager} = imports . misc . signalManager ;
1416const { throttle, unref, trySpawnCommandLine} = imports . misc . util ;
@@ -161,6 +163,124 @@ class PinnedFavs {
161163 }
162164}
163165
166+ class Notifications {
167+ // As an app can have multiple appgroups (multiple windows, different workspaces, multiple instances), all
168+ // appGroups with the same appId should always display the same number of notifications.
169+ constructor ( ) {
170+ this . _appGroupList = [ ] ; //stores all appGroups from all workspaces
171+ Main . messageTray . connect ( 'notify-applet-update' , ( mtray , notification ) => this . _notificationReceived ( mtray , notification ) ) ;
172+ }
173+
174+ _notificationReceived ( mtray , notification ) {
175+ const guessFlatpakAppIdFromDesktopEntryHint = ( desktopEntry ) => {
176+ let tryAppId = desktopEntry + '.desktop:flatpak' ;
177+ if ( this . _appGroupList . some ( appGroup => appGroup . groupState ?. appId === tryAppId ) ) {
178+ return tryAppId ;
179+ }
180+ const exceptions = {
181+ "vivaldi-stable" : "com.vivaldi.Vivaldi" ,
182+ "brave-browser" : "com.brave.Browser" ,
183+ "google-chrome" : "com.google.Chrome" ,
184+ "microsoft-edge" : "com.microsoft.Edge" ,
185+ "opera" : "com.opera.Opera"
186+ } ;
187+ if ( exceptions [ desktopEntry ] ) {
188+ tryAppId = exceptions [ desktopEntry ] + '.desktop:flatpak' ;
189+ if ( this . _appGroupList . some ( appGroup => appGroup . groupState ?. appId === tryAppId ) ) {
190+ return tryAppId ;
191+ }
192+ }
193+ } ;
194+
195+ let appId = notification . source . app ?. get_id ( ) ;
196+ if ( ! appId ) {
197+ appId = guessFlatpakAppIdFromDesktopEntryHint ( notification . desktopEntry ) ;
198+ }
199+ if ( ! appId ) {
200+ global . logError ( 'GWL: Failed to find appId for notification with desktopEntry hint: '
201+ + notification . desktopEntry ) ;
202+ return ;
203+ }
204+
205+ // Add notification to all appgroups with appId
206+ let notificationAdded = false ;
207+ this . _appGroupList . forEach ( appGroup => {
208+ if ( ! appGroup . groupState || appGroup . groupState . willUnmount ) return ;
209+ if ( appId === appGroup . groupState . appId ) {
210+ appGroup . notifications . push ( notification ) ;
211+ notificationAdded = true ;
212+ this . updateNotificationsBadge ( appGroup ) ;
213+ }
214+ } ) ;
215+ if ( notificationAdded ) {
216+ notification . appId = appId ;
217+ notification . connect ( 'destroy' , ( ) => this . _removeNotification ( notification ) ) ;
218+ }
219+ }
220+
221+ _removeNotification ( notification ) {
222+ this . _appGroupList . forEach ( appGroup => {
223+ if ( ! appGroup . groupState || appGroup . groupState . willUnmount ) return ;
224+ if ( notification . appId === appGroup . groupState . appId ) {
225+ const index = appGroup . notifications . indexOf ( notification ) ;
226+ if ( index > - 1 ) {
227+ appGroup . notifications . splice ( index , 1 )
228+ this . updateNotificationsBadge ( appGroup ) ;
229+ }
230+ }
231+ } ) ;
232+ }
233+
234+ // Called when an app is focused to remove all notifications from all instances of an app (with given appId)
235+ removeAllNotifications ( appId ) {
236+ this . _appGroupList . forEach ( appGroup => {
237+ if ( ! appGroup . groupState || appGroup . groupState . willUnmount ) return ;
238+ if ( appId === appGroup . groupState . appId ) {
239+ // iterate backwards due to in place array element deletion
240+ for ( let i = appGroup . notifications . length - 1 ; i >= 0 ; i -- ) {
241+ if ( appGroup . notifications [ i ] && ! appGroup . notifications [ i ] . _destroyed ) {
242+ appGroup . notifications [ i ] . destroy ( NotificationDestroyedReason . DISMISSED ) ;
243+ }
244+ }
245+ appGroup . notifications = [ ] ;
246+ this . updateNotificationsBadge ( appGroup ) ;
247+ }
248+ } ) ;
249+ }
250+
251+ updateNotificationsBadge ( appGroup ) {
252+ if ( appGroup . notifications . length > 0 ) {
253+ appGroup . notificationsBadgeLabel . text = appGroup . notifications . length . toString ( ) ;
254+ appGroup . notificationsBadge . show ( ) ;
255+ } else {
256+ appGroup . notificationsBadge . hide ( ) ;
257+ }
258+ }
259+
260+ // Called from AppGroup constructor so that we always have a list of all appgroups from all workspaces.
261+ addAppGroup ( newAppGroup ) {
262+ newAppGroup . notifications = [ ] ;
263+
264+ //Copy notifications from any existing appGroup with the same appId.
265+ this . _appGroupList . some ( appGroup => {
266+ if ( ! appGroup . groupState || appGroup . groupState . willUnmount ) return false ;
267+ if ( appGroup . groupState . appId === newAppGroup . groupState . appId ) {
268+ newAppGroup . notifications = appGroup . notifications . slice ( ) ; //shallow copy
269+ return true ;
270+ }
271+ } )
272+
273+ this . _appGroupList . push ( newAppGroup ) ;
274+
275+ //remove old deleted appgroups
276+ this . _appGroupList = this . _appGroupList . filter ( appGroup =>
277+ appGroup !== null &&
278+ appGroup !== undefined &&
279+ appGroup . groupState &&
280+ ! appGroup . groupState . willUnmount ) ;
281+ }
282+ }
283+
164284class GroupedWindowListApplet extends Applet . Applet {
165285 constructor ( metadata , orientation , panel_height , instance_id ) {
166286 super ( orientation , panel_height , instance_id ) ;
@@ -191,6 +311,7 @@ class GroupedWindowListApplet extends Applet.Applet {
191311 appletReady : false ,
192312 willUnmount : false ,
193313 settings : { } ,
314+ notifications : new Notifications ( ) ,
194315 homeDir : GLib . get_home_dir ( ) ,
195316 lastOverlayPreview : null ,
196317 lastCycled : - 1 ,
@@ -307,7 +428,6 @@ class GroupedWindowListApplet extends Applet.Applet {
307428 { key : 'super-num-hotkeys' , value : 'SuperNumHotkeys' , cb : this . bindAppKeys } ,
308429 { key : 'title-display' , value : 'titleDisplay' , cb : this . updateTitleDisplay } ,
309430 { key : 'launcher-animation-effect' , value : 'launcherAnimationEffect' , cb : null } ,
310- { key : 'number-display' , value : 'numDisplay' , cb : this . updateWindowNumberState } ,
311431 { key : 'enable-app-button-dragging' , value : 'enableDragging' , cb : this . draggableSettingChanged } ,
312432 { key : 'thumbnail-scroll-behavior' , value : 'thumbnailScrollBehavior' , cb : null } ,
313433 { key : 'show-thumbnails' , value : 'showThumbs' , cb : this . updateVerticalThumbnailState } ,
@@ -357,6 +477,7 @@ class GroupedWindowListApplet extends Applet.Applet {
357477 }
358478 this . bindAppKeys ( ) ;
359479 this . state . set ( { appletReady : true } ) ;
480+ MessageTray . extensionsHandlingNotifications ++ ;
360481 }
361482
362483 _updateState ( initialUpdate ) {
@@ -424,6 +545,7 @@ class GroupedWindowListApplet extends Applet.Applet {
424545 } ) ;
425546 this . settings . finalize ( ) ;
426547 unref ( this , RESERVE_KEYS ) ;
548+ MessageTray . extensionsHandlingNotifications -- ;
427549 }
428550
429551 on_panel_icon_size_changed ( iconSize ) {
@@ -584,12 +706,6 @@ class GroupedWindowListApplet extends Applet.Applet {
584706 } ) ;
585707 }
586708
587- updateWindowNumberState ( ) {
588- this . workspaces . forEach (
589- workspace => workspace . calcAllWindowNumbers ( )
590- ) ;
591- }
592-
593709 updateAttentionState ( display , window ) {
594710 this . workspaces . forEach (
595711 workspace => workspace . updateAttentionState ( display , window )
0 commit comments