Skip to content

Commit dc8f8ee

Browse files
committed
fix(tray): improve icon handling and refetch properties for stale icons
1 parent 05a4ed3 commit dc8f8ee

1 file changed

Lines changed: 67 additions & 8 deletions

File tree

src/modules/trayIcons/sniHost.ts

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ const LOG_PREFIX = 'AuroraTray';
4444

4545
// @ts-ignore — _promisify is a GJS extension not reflected in .d.ts
4646
Gio._promisify(Gio.DBusProxy.prototype, 'init_async');
47+
// @ts-ignore
48+
Gio._promisify(Gio.DBusProxy.prototype, 'call', 'call_finish');
4749

4850
type 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

Comments
 (0)