Skip to content

Add Tray hover expand mod for hidden icons#4235

Open
wygodad wants to merge 2 commits into
ramensoftware:mainfrom
wygodad:main
Open

Add Tray hover expand mod for hidden icons#4235
wygodad wants to merge 2 commits into
ramensoftware:mainfrom
wygodad:main

Conversation

@wygodad
Copy link
Copy Markdown

@wygodad wygodad commented May 30, 2026

This mod allows the hidden tray icons flyout to open on hover instead of requiring a click. It includes settings for auto-closing, polling interval, collapse delay, and hit area padding.
It works through UI Automation (no shell function hooks), so it is relatively safe and resilient across Windows builds.
Known limitation: auto-collapse relies on detecting the flyout window class (TopLevelWindowForOverflowXamlIsland by default), which is exposed as a configurable setting in case it differs across Windows builds.
Tested on: Windows 11 25H2 (build 26200.8524)

Changelog

If this pull request updates an existing mod, describe the changes below:
N/A — this pull request introduces a new mod.

Mod authorship

If this pull request introduces a new mod, please complete the section below.

This mod was created by:

    • The submitter, without AI assistance
    • The submitter, with AI assistance
    • Claude
    • ChatGPT
    • Gemini
    • Another AI (please specify):
    • Other (please specify):

Please select the options that best apply. Your selection does not affect the acceptance criteria, but it helps reviewers understand the context of the code and provide relevant feedback.

wygodad added 2 commits May 30, 2026 20:47
This mod allows the hidden tray icons flyout to open on hover instead of requiring a click. It includes settings for auto-closing, polling interval, collapse delay, and hit area padding.
@m417z
Copy link
Copy Markdown
Member

m417z commented Jun 1, 2026

Submission review

Note: This review was done by Claude, and then refined manually. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding.

Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them.

2 will become less of an issue once 1 is implemented and the mod becomes isolated.
3 is not a must but would be nice.


Nice, self-contained UIA approach — it's reversible and doesn't hook the shell. A few integration issues to address before merge.

1. This should be a "tool mod", not injected into explorer.exe. The mod has no function hooks at all — it only uses UI Automation plus system-wide Win32 APIs (FindWindowW, GetCursorPos, IsWindowVisible), all of which work from any process. There's no reason to inject into explorer.exe; doing so means a copy runs in every explorer.exe instance, and each worker thread independently polls the cursor and fires Expand/Collapse on the same single flyout — they'll fight each other. Hosting it in the shell also means a bug here can destabilize the shell. This is a textbook fit for the Mods as tools pattern, which also gives you single-instance behavior for free.

Recommended change:

  • @include windhawk.exe instead of explorer.exe
  • Rename Wh_ModInit / Wh_ModSettingsChanged / Wh_ModUninitWhTool_ModInit / WhTool_ModSettingsChanged / WhTool_ModUninit
  • Paste the tool-mod launcher boilerplate verbatim from the wiki

See mods/keep-rainmeter-always-bottom.wh.cpp for a working example (same shape: a worker thread doing cross-process window work, with the launcher boilerplate at the bottom).

2. Thread teardown can orphan the worker and crash on disable. In Wh_ModUninit:

WaitForSingleObject(g_thread, 3000);
CloseHandle(g_thread);

The wait is bounded at 3 s, but you then CloseHandle and return regardless of whether the thread actually exited. pollInterval is only clamped at the low end (>= 10), so a user who sets, say, pollInterval: 5000 makes Sleep(g_settings.pollInterval) outlast the 3 s wait — the wait times out, Windhawk unloads the mod DLL, and the still-sleeping thread then wakes into freed code → crash. A pathological cross-process UIA call (FindAll / get_CurrentBoundingRectangle) could also occasionally exceed 3 s. Fix: make the sleep interruptible and join unconditionally so uninit is both prompt and safe regardless of pollInterval:

// create a manual-reset event in Wh_ModInit; signal it in Wh_ModUninit
WaitForSingleObject(g_stopEvent, g_settings.pollInterval);   // instead of Sleep()
...
// uninit:
g_running = false;
SetEvent(g_stopEvent);
WaitForSingleObject(g_thread, INFINITE);   // safe: the thread can no longer block long
CloseHandle(g_thread);

3. Add a screenshot/GIF to the README. This is a visible-effect mod, so a short GIF of the hover-to-expand behavior helps a lot in the catalog. The sibling mod taskbar-tray-show-on-hover embeds one (https://i.imgur.com/...gif); only i.imgur.com and raw.githubusercontent.com are allowed image hosts.

Optional improvements

Minor polish — none of this affects users in normal operation, so it's your call.

  • Data race on settings change. Wh_ModSettingsChangedLoadSettings() reassigns g_settings (including the std::wstring members) on the settings thread while the worker thread reads them every tick (g_settings.flyoutClass.c_str() in IsFlyoutOpen/CursorOverFlyout, g_settings.trayIconAutomationId, g_settings.keywords). Reassigning a std::wstring while another thread reads it is a data race that can crash. Since it only happens on a settings change it's not a blocker, but consider guarding LoadSettings() and the worker's reads with a small mutex, or snapshotting the strings the worker needs.

  • Prefer std::atomic<bool> over volatile bool for g_running. volatile isn't a synchronization primitive; std::atomic<bool> is the idiom and pairs naturally with the stop-event fix above.

  • Redundant NULL checks on Wh_GetStringSetting. It never returns NULL — it returns L"" on unset/error — so if (fc) / if (aid) can never be false. As written, clearing the setting to empty would overwrite your defaults with "" and break FindWindowW. Consider WindhawkUtils::StringSetting (RAII, no manual Wh_FreeStringSetting) and only override the default when the value is non-empty.

  • IsFlyoutOpen() runs FindWindowW on every tick because needState = (...) || g_settings.autoClose and autoClose defaults to true. You only need the flyout-visibility check on the cursor-enter edge and while auto-collapsing; gating it more tightly avoids ~20 FindWindowW calls/sec when nothing is happening.

  • Code comments / #error message are in Polish. The user-facing strings are already English (good); for a public mod, English source comments are preferred for maintainability.

Functionality notes

Non-critical observations about the feature itself.

  • Overlap check — this is distinct. The closest existing mods are taskbar-tray-show-on-hover (auto-hides the whole tray area and reveals it on hover) and taskbar-notification-icons-show-all (forces all hidden icons to always show). Neither does "open the overflow flyout on hover", so this isn't a duplicate — worth a one-line mention in the README of how it differs, for users deciding between them.

  • keywords is hardcoded in the source. Non-English/non-Polish users currently have to edit and recompile (or rely on the leftmost-icon heuristic). Exposing it as an array setting (- keywords: [...], read with keywords[%d]) would let users add their locale's chevron name without touching the source — and lets you drop the README instruction to edit the list.

  • Cursor polling is the core mechanism and has no clean event-driven alternative for "cursor entered this screen rect" (a low-level mouse hook would be worse). So the continuous 50 ms poll plus the periodic UIA FindAll (every 3 s) is an inherent, modest background cost — noting it as an FYI, not a change to make.

  • Chevron→flyout gap. Moving the cursor from the chevron up into the flyout may briefly cross neither rect; the grace delay (default 200 ms) absorbs this, but a user setting grace: 0 could see premature collapses. Fine as-is given the default.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants