Add Taskbar Volume Percentage Indicator mod - #5389
Conversation
This mod displays the exact master volume percentage in the Windows 11 system tray volume icon with real-time synchronization. It includes various display styles for the volume indicator.
|
Thanks for the pull request! This repository uses a two-stage review: an AI review that you run yourself, followed by a human review. To get started, comment See the pull request review process for the full details. |
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. The feature is a reasonable fit for the catalog and there's no existing mod that does it, but the implementation writes into an internal WinRT object at a hard-coded offset from arbitrary threads, and forces refreshes by changing the machine's actual audio volume. Those need to be reworked before this can ship. 1. Hard-coded struct offset
shared_hstring_header** ppHeader = reinterpret_cast<shared_hstring_header**>(
reinterpret_cast<char*>(pThis) + 0x90);There is no validation and no build gate. Symbol hooks tolerate Windows updates because the symbol name is stable; a member offset is not. The moment Microsoft adds/removes/reorders a field in that class, the mod overwrites an unrelated member and passes a non-hstring pointer to Please drive this through the type's own code instead of its memory layout. Two patterns already in the repo:
If neither is workable and the offset really is the only route, it must at minimum be gated on the module version and sanity-checked before use (e.g. 2. float nudge = (vol > 0.01f) ? (vol - 0.0005f) : (vol + 0.0005f);
pEndpointVolume->SetMasterVolumeLevelScalar(nudge, nullptr);
pEndpointVolume->SetMasterVolumeLevelScalar(vol, nullptr);This writes to the shared audio endpoint to provoke a redraw. It's a system-wide side effect: two endpoint-volume change notifications are broadcast to every app that listens (other volume UIs, OSDs, streaming/DJ tools, other Windhawk mods), the output level genuinely moves for a moment, and if the second call fails — or the process is killed between the two — the user's volume is left permanently off. It runs on every mod enable/reload ( A mod must not modify state outside itself to refresh its own UI. Use the refresh path you already resolve ( 3. COM initialization inside the if (HookSystemTraySymbols(hModule)) {
Wh_ApplyHookOperations();
TriggerImmediateVolumeSync(); // CoInitializeEx + CoCreateInstance + Activate
}
4. The data model is written from arbitrary threads (use-after-free + XAML thread affinity)
Please marshal both operations onto the taskbar UI thread (post to the tray window's thread / use the element's dispatcher), or keep all writes inside the 5. The if (g_pVolumeDataModel && ((ULONG_PTR)g_pVolumeDataModel & (sizeof(void*) - 1)) == 0)only checks alignment, which a freed pointer still satisfies. Either hook the destructor and clear the global there, or don't cache the pointer at all and only act from inside 6. The constructor hook's prototype doesn't match every symbol it can bind to The ctor entry lists both overloads: LR"(public: __cdecl ...::VolumeSystemTrayIconDataModel(struct winrt::SystemTray::IIconDataModel const &))",
...
LR"(public: __cdecl ...::VolumeSystemTrayIconDataModel(void))",but the hook and original are typed Related: 7. No reentrancy guard around
8.
Copy the version check from taskbar-volume-control (also in adaptive-microphone-icon-visibility), and install the 9. The symbol-hook array declares only one of the two modules it's used against
// SystemTray.dll, Taskbar.View.dll
WindhawkUtils::SYMBOL_HOOK symbolHooks[] = { ... };10. It's declared with 11. Add a screenshot to the README This is a purely visual mod and the README has no image. Please add a screenshot (or GIF) of the tray showing at least the default Optional improvements
Minor polish — none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
- Add jitter prevention (zero and space padding modes). - Add 7 customizable mute display styles with custom text support. - Add dynamic XAML memory offset detection to prevent crashes. - Support Windows 11 24H2 and recent builds (>= 2604). - Ensure thread-safe UI marshaling on the taskbar thread. - Hook destructor for clean resource disposal. - Query system volume passively without artificial volume nudging. - Update license to GPL-3.0.
- Add safety tags to custom strings to prevent memory corruption - Remove artificial text padding options - Modernize settings management with Windhawk helpers - Improve string and offset detection for Windows 11 - Simplify initial audio volume query on startup
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. The idea is good and doesn't overlap with anything in the catalog ( 1. for (size_t offset = 0x40; offset <= 0x120; offset += sizeof(void*)) {
shared_hstring_header* candidate = *reinterpret_cast<shared_hstring_header**>(
reinterpret_cast<char*>(pThis) + offset);
if (isValidHeader(candidate)) { ... }Three separate problems:
The fix is to stop touching the object at all and substitute the #include <winrt/base.h>
void __cdecl VolumeSystemTrayIconDataModel_UpdateVolume_Hook(
void* pThis, float volumeLevel, bool isMuted, void* hstringIcon) {
if (g_settings.displayStyle == DisplayStyle::Vanilla) {
VolumeSystemTrayIconDataModel_UpdateVolume_Original(
pThis, volumeLevel, isMuted, hstringIcon);
return;
}
int percentage = std::clamp(
static_cast<int>(std::lround(volumeLevel * 100.0f)), 0, 100);
winrt::hstring text{FormatVolumeText(percentage, isMuted)};
VolumeSystemTrayIconDataModel_UpdateVolume_Original(
pThis, volumeLevel, isMuted, &text);
}Note this passes your own local, it does not modify the caller's temporary — the original will take its own reference and Please verify first that 2. The original HSTRING is leaked on every volume change. static void ReleaseSharedHString(shared_hstring_header* header) {
if (header->flags != 0 || header->padding1 != MOD_HSTRING_MAGIC) {
return; // <-- OS-created strings are never released
}
Also note that using 3. Deadlock on settings change: static void SettingsChangedOnUIThread(void*) {
std::lock_guard<std::mutex> lock(g_dataModelMutex);
...
VolumeSystemTrayIconDataModel_OnDataModelChanged_Original(g_pVolumeDataModel, &propName);You added the Fix: snapshot what you need, release the lock, then notify: void* dataModel = nullptr;
{
std::lock_guard<std::mutex> lock(g_dataModelMutex);
if (g_pVolumeDataModel && !g_unloading) {
ApplyCustomVolumeText(g_pVolumeDataModel, g_lastVolumeLevel, g_lastIsMuted);
dataModel = g_pVolumeDataModel;
}
}
if (dataModel && VolumeSystemTrayIconDataModel_OnDataModelChanged_Original) {
std::wstring_view propName = L"CurrentData";
VolumeSystemTrayIconDataModel_OnDataModelChanged_Original(dataModel, &propName);
}and set the recursion guard around it as you do in 4. The destructor hook is reinterpret_cast<void*>(VolumeSystemTrayIconDataModel_dtor_Hook),
true, // optional
5. The "Windows Default" style isn't the Windows default.
6. The README has no screenshot. This is a purely visual mod, and the README's feature list describes five display styles that a user can't picture from text. Please add a screenshot or GIF ( Optional improvements
Minor polish — none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
- Hook IconView OnViewModelChanged and UpdateHostedContent - Lock container minimum width using IFrameworkElement::put_MinWidth - Add fixedContainerWidth setting with automatic optimal width per style - Remove legacy MMDeviceEnumerator startup overhead
- Lock icon width during layout (MeasureOverride), not just after - Track the icon element so width stays correct on settings change/unload - Remove padStyle setting — no longer needed
Displays the volume percentage directly in the Windows 11 system tray volume icon.
Includes configurable display formats (percentage, number only, custom prefix, icon + percentage, or default).
Closes #4162
Changelog
Mod authorship
This mod was created by:
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.