@@ -44,6 +44,8 @@ const LOG_PREFIX = 'AuroraTray';
4444
4545// @ts -ignore — _promisify is a GJS extension not reflected in .d.ts
4646Gio . _promisify ( Gio . DBusProxy . prototype , 'init_async' ) ;
47+ // @ts -ignore
48+ Gio . _promisify ( Gio . DBusProxy . prototype , 'call' , 'call_finish' ) ;
4749
4850type HostCallbacks = {
4951 onItemAdded ( item : TrayItem ) : void ;
@@ -118,8 +120,17 @@ export class SniHost {
118120 item . status = newStatus as TrayItemStatus ;
119121 this . _callbacks . onStatusChanged ( id , item . status ) ;
120122 } else if ( signalName === 'NewIcon' || signalName === 'NewAttentionIcon' ) {
121- item . icon = this . _resolveIcon ( proxy , signalName ) ;
122- this . _callbacks . onIconChanged ( id ) ;
123+ // Electron/Discord emits NewIcon without PropertiesChanged, leaving the
124+ // proxy cache stale. Re-fetch icon properties before resolving.
125+ this . _refetchIconProperties ( proxy )
126+ . then ( ( ) => {
127+ if ( ! this . _entries . has ( id ) ) return ;
128+ item . icon = this . _resolveIcon ( proxy , signalName ) ;
129+ this . _callbacks . onIconChanged ( id ) ;
130+ } )
131+ . catch ( ( e ) =>
132+ logger . warn ( `Icon property refresh failed for ${ id } : ${ e } ` , { prefix : LOG_PREFIX } ) ,
133+ ) ;
123134 }
124135 } ) ;
125136
@@ -139,6 +150,32 @@ export class SniHost {
139150 this . _callbacks . onItemAdded ( item ) ;
140151 }
141152
153+ private async _refetchIconProperties ( proxy : Gio . DBusProxy ) : Promise < void > {
154+ const props = [
155+ 'IconName' ,
156+ 'IconThemePath' ,
157+ 'IconPixmap' ,
158+ 'AttentionIconName' ,
159+ 'AttentionIconPixmap' ,
160+ ] ;
161+ await Promise . allSettled (
162+ props . map ( async ( prop ) => {
163+ try {
164+ const result = await ( proxy as any ) . call (
165+ 'org.freedesktop.DBus.Properties.Get' ,
166+ new GLib . Variant ( '(ss)' , [ 'org.kde.StatusNotifierItem' , prop ] ) ,
167+ Gio . DBusCallFlags . NONE ,
168+ - 1 ,
169+ null ,
170+ ) ;
171+ proxy . set_cached_property ( prop , result . get_child_value ( 0 ) . get_variant ( ) ) ;
172+ } catch {
173+ // property may not be supported by this item
174+ }
175+ } ) ,
176+ ) ;
177+ }
178+
142179 private _resolveIcon ( proxy : Gio . DBusProxy , reason = 'initial' ) : TrayItem [ 'icon' ] {
143180 const status = ( proxy . get_cached_property ( 'Status' ) ?. unpack ( ) as string ) ?? 'Active' ;
144181 const useAttention = status === 'NeedsAttention' ;
@@ -159,11 +196,6 @@ export class SniHost {
159196 ) ;
160197 return themedIcon ;
161198 }
162- logger . log (
163- `SNI icon ${ itemId } reason=${ reason } source=icon-name name=${ iconName } path=${ iconThemePath || 'none' } ` ,
164- { prefix : LOG_PREFIX } ,
165- ) ;
166- return iconName ;
167199 }
168200
169201 const pixmaps = proxy . get_cached_property ( useAttention ? 'AttentionIconPixmap' : 'IconPixmap' ) ;
@@ -178,6 +210,14 @@ export class SniHost {
178210 }
179211 }
180212
213+ if ( iconName ) {
214+ logger . log (
215+ `SNI icon ${ itemId } reason=${ reason } source=icon-name name=${ iconName } path=${ iconThemePath || 'none' } ` ,
216+ { prefix : LOG_PREFIX } ,
217+ ) ;
218+ return iconName ;
219+ }
220+
181221 logger . log ( `SNI icon ${ itemId } reason=${ reason } source=fallback` , { prefix : LOG_PREFIX } ) ;
182222 return 'image-missing-symbolic' ;
183223 }
@@ -201,7 +241,8 @@ export class SniHost {
201241 const theme = St . IconTheme . new ( ) ;
202242 theme . append_search_path ( iconThemePath ) ;
203243 const iconInfo = theme . lookup_icon ( iconName , 24 , St . IconLookupFlags . FORCE_SIZE ) ;
204- const filename = iconInfo ?. get_filename ( ) ;
244+ const filename =
245+ iconInfo ?. get_filename ( ) ?? this . _findIconThemePathFile ( iconThemePath , iconName ) ;
205246 if ( ! filename ) return null ;
206247
207248 // SVGs go through GTK's symbolic pipeline via St.Icon; return as-is.
@@ -221,6 +262,24 @@ export class SniHost {
221262 }
222263 }
223264
265+ private _findIconThemePathFile ( iconThemePath : string , iconName : string ) : string | null {
266+ if ( ! iconThemePath ) return null ;
267+ if ( iconName . startsWith ( '/' ) )
268+ return Gio . File . new_for_path ( iconName ) . query_exists ( null ) ? iconName : null ;
269+
270+ const extensions = [ '' , '.svg' , '.png' , '.xpm' ] ;
271+ const subdirs = [ '' , 'icons' , 'hicolor/16x16/apps' , 'hicolor/24x24/apps' , 'hicolor/32x32/apps' ] ;
272+ for ( const subdir of subdirs ) {
273+ const dir = subdir ? GLib . build_filenamev ( [ iconThemePath , subdir ] ) : iconThemePath ;
274+ for ( const ext of extensions ) {
275+ const filename = GLib . build_filenamev ( [ dir , `${ iconName } ${ ext } ` ] ) ;
276+ if ( Gio . File . new_for_path ( filename ) . query_exists ( null ) ) return filename ;
277+ }
278+ }
279+
280+ return null ;
281+ }
282+
224283 private _recolorFilePixbuf (
225284 pixbuf : GdkPixbuf . Pixbuf ,
226285 itemId : string ,
0 commit comments