Repository navigation
fix(tray): drop icons whose owner window no longer exists - #1841
Merged
Merged
Conversation
When a process exits without calling NIM_DELETE (crash, forced kill, in-place update), Explorer drops its icons internally, so the tray hook never sees an IconRemove and the icon stays in Seelen's list forever. Hovering it then fails with "Window handle is invalid". Prune icons with a dead window handle after every tray event, when the icon list is requested, and when an action targets a dead icon (the same thing Explorer does on hover). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01StkifHpSLWH4J5TvuP56ur
Contributor
|
Emit `Changed` when `get_system_tray_icons` prunes icons, so every tray webview drops them, not only the caller. Remember the process that owned each icon's window when it was registered and treat the icon as dead if the handle now belongs to a different process, so a recycled HWND can neither keep a stale icon nor receive its callback messages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01StkifHpSLWH4J5TvuP56ur
Pruning only on tray events left a dead icon listed until some other icon happened to update, since the tray popup does not send actions on hover. Subscribe to EVENT_OBJECT_DESTROY and prune when the destroyed window owns an icon. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01StkifHpSLWH4J5TvuP56ur
eythaann
approved these changes
Sep 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Tray icons of processes that exit without calling
NIM_DELETEstay in Seelen's tray list forever. This happens when an app crashes, is killed, or updates itself in place. I had three "Claude" entries with only one Claude process alive, because the Claude desktop app had updated twice that day.The cause: Seelen mirrors Explorer's tray through the
Shell_TrayWndhook, but Explorer removes dead icons internally, without sending a message. The hook therefore never sees anIconRemovefor them. Hovering such an icon logs:send_actionalready detects that the window is gone, but it keeps the icon.Fix
SystemTrayManager::prune_dead_icons()drops icons whose owner window is gone. It runs:EVENT_OBJECT_DESTROYthroughHookManager, so the icon disappears as soon as its process dies. The subscription is made in aOnceinsideinstance(), after the manager is fully created, because the callback itself callsinstance(); subscribing fromcreate(), inside theLazyLockinitializer, hung the background thread in my first attempt;get_system_tray_icons, emittingChangedwhen it removed anything so every tray webview stays in sync;send_actionwhen the target is dead: the icon is removed,Changedis emitted and it returnsOk(())instead of the error.An icon counts as alive only if
IsWindowpasses and the window still belongs to the process that registered the icon. The owning pid is recorded when the icon is added or its handle changes, so a recycled HWND neither keeps a stale icon nor receives its callback messages. If the pid could not be read, it falls back toIsWindowalone.Testing
NotifyIcon("PruebaFantasma") in a separate process, thenStop-Process -Force. The icon stays in the popup indefinitely.npm run dev) of this change on top ofv2.8.6. The tray logic is identical tomaster, which only changed the handler wrapper. Results:Tray icon removed, its window no longer exists: 1105f0_1). There were no moreWindow handle is invaliderrors.Stop-Process -Force. Each removal is logged in the same second as the apps module'sRemoving: Window(...)for that window, with no tray event in between, so the destroy event is what triggers it. All real icons stayed, and the background thread stayed responsive.cargo fmt --check,cargo clippy -D warnings, andcargo testpass locally, and the full CI passes on the fork with all three commits.🤖 Generated with Claude Code