From 5904f6503136c2ea924382a4c583b630e303ce19 Mon Sep 17 00:00:00 2001 From: Jesus Fernandez Date: Tue, 5 Aug 2025 14:56:39 +0200 Subject: [PATCH 01/13] Create taskbar-icon-numberer.wh.cpp A mod that adds keyboard shortcut numbers (1-9, 0) to taskbar icons like 7+ Taskbar Numberer --- mods/taskbar-icon-numberer.wh.cpp | 468 ++++++++++++++++++++++++++++++ 1 file changed, 468 insertions(+) create mode 100644 mods/taskbar-icon-numberer.wh.cpp diff --git a/mods/taskbar-icon-numberer.wh.cpp b/mods/taskbar-icon-numberer.wh.cpp new file mode 100644 index 000000000..3cdbb8fcc --- /dev/null +++ b/mods/taskbar-icon-numberer.wh.cpp @@ -0,0 +1,468 @@ +// ==WindhawkMod== +// @id taskbar-icon-numberer +// @name Taskbar Icon Numberer for Windows 11 +// @description Add keyboard shortcut numbers (1-9, 0) to taskbar icons like 7+ Taskbar Numberer +// @version 0.0.1 +// @author js +// @github https://github.com/jsfdez +// @homepage https://tightcorner.substack.com/p/reverse-engineering-windows-11s-taskbar +// @include explorer.exe +// @architecture x86-64 +// @compilerOptions -DWINVER=0x0A00 -lole32 -loleaut32 -lruntimeobject +// ==/WindhawkMod== + +// ==WindhawkModReadme== +/* +# Taskbar Icon Numberer for Windows 11 + +Adds keyboard shortcut numbers (1-9, 0) to taskbar icons, similar to the classic +7+ Taskbar Numberer tool. Numbers appear as small overlays on the taskbar button icons. + +Features: +- Shows numbers 1-9 for the first 9 taskbar items, then 0 for the 10th +- Customizable number position, size, and colors +- Works with Windows 11 taskbar +- Supports both light and dark themes + +The numbers correspond to the Windows + [number] keyboard shortcuts for quick app launching. +*/ +// ==/WindhawkModReadme== + +// ==WindhawkModSettings== +/* +- numberPosition: bottomRight + $name: Number position + $options: + - topLeft: Top left + - topRight: Top right + - bottomLeft: Bottom left + - bottomRight: Bottom right +- numberSize: 10 + $name: Number font size + $description: Size of the overlay numbers (8-16) +- numberColor: "#FFFFFF" + $name: Number color + $description: Text color for the numbers (hex format #RRGGBB or #AARRGGBB) +- backgroundColor: "#80000000" + $name: Stroke/outline color + $description: Outline color around the numbers for better visibility (hex format #RRGGBB or #AARRGGBB) +- showOnlyRunning: true + $name: Show only on running apps + $description: Only show numbers on apps that are currently running +- maxNumbers: 10 + $name: Maximum numbers to show + $description: Maximum number of taskbar items to number (1-10) +*/ +// ==/WindhawkModSettings== + +#include + +#undef GetCurrentTime + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace winrt::Windows::UI; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Xaml::Controls; +using namespace winrt::Windows::UI::Xaml::Media; + +winrt::Windows::UI::Color ParseHexColor(const std::wstring& hex) { + winrt::Windows::UI::Color color{}; + if (hex.length() >= 7 && hex[0] == L'#') { + try { + if (hex.length() == 9) { // #AARRGGBB + color.A = static_cast(std::wcstoul(hex.substr(1, 2).c_str(), nullptr, 16)); + color.R = static_cast(std::wcstoul(hex.substr(3, 2).c_str(), nullptr, 16)); + color.G = static_cast(std::wcstoul(hex.substr(5, 2).c_str(), nullptr, 16)); + color.B = static_cast(std::wcstoul(hex.substr(7, 2).c_str(), nullptr, 16)); + } else { // #RRGGBB + color.A = 255; + color.R = static_cast(std::wcstoul(hex.substr(1, 2).c_str(), nullptr, 16)); + color.G = static_cast(std::wcstoul(hex.substr(3, 2).c_str(), nullptr, 16)); + color.B = static_cast(std::wcstoul(hex.substr(5, 2).c_str(), nullptr, 16)); + } + } catch (...) { + color.A = 255; color.R = 255; color.G = 255; color.B = 255; + } + } else { + color.A = 255; color.R = 255; color.G = 255; color.B = 255; + } + return color; +} + +enum class NumberPosition { + topLeft, + topRight, + bottomLeft, + bottomRight +}; + +struct { + NumberPosition numberPosition; + int numberSize; + std::wstring numberColor; + std::wstring backgroundColor; + bool showOnlyRunning; + int maxNumbers; +} g_settings; + +std::atomic g_taskbarViewDllLoaded; +std::atomic g_unloading; +std::mutex g_overlayMutex; +std::unordered_set g_numberedButtons; + +// Hook function pointers +using TaskListButton_get_IsRunning_t = HRESULT(WINAPI*)(void* pThis, bool* running); +TaskListButton_get_IsRunning_t TaskListButton_get_IsRunning_Original; + +using TaskListButton_UpdateVisualStates_t = void(WINAPI*)(void* pThis); +TaskListButton_UpdateVisualStates_t TaskListButton_UpdateVisualStates_Original; + +// Utility functions +FrameworkElement FindChildByName(FrameworkElement element, PCWSTR name) { + if (!element) return nullptr; + + try { + int childrenCount = Media::VisualTreeHelper::GetChildrenCount(element); + for (int i = 0; i < childrenCount; i++) { + auto child = Media::VisualTreeHelper::GetChild(element, i).try_as(); + if (child && child.Name() == name) { + return child; + } + } + } catch (...) { + // Ignore errors + } + return nullptr; +} + +bool TaskListButton_IsRunning(FrameworkElement taskListButtonElement) { + if (!TaskListButton_get_IsRunning_Original || !taskListButtonElement) { + return false; + } + + try { + bool isRunning = false; + TaskListButton_get_IsRunning_Original( + winrt::get_abi(taskListButtonElement.as()), + &isRunning); + return isRunning; + } catch (...) { + return false; + } +} + +void CreateNumberOverlay(FrameworkElement taskListButtonElement, int number) { + if (!taskListButtonElement || g_unloading) return; + + try { + auto iconPanelElement = FindChildByName(taskListButtonElement, L"IconPanel"); + if (!iconPanelElement) return; + + void* buttonPtr = winrt::get_abi(taskListButtonElement.as()); + + // Remove existing overlay + auto existingOverlay = FindChildByName(iconPanelElement, L"WindhawkNumberOverlay"); + if (existingOverlay) { + auto panel = iconPanelElement.as(); + auto children = panel.Children(); + uint32_t index; + if (children.IndexOf(existingOverlay, index)) { + children.RemoveAt(index); + } + } + + if (g_unloading) return; + + // Create text with stroke effect + TextBlock numberText; + numberText.Text(number == 10 ? L"0" : std::to_wstring(number)); + numberText.FontSize(g_settings.numberSize); + + // White text with parsed color + auto textColor = ParseHexColor(g_settings.numberColor); + auto brush = SolidColorBrush(); + brush.Color(textColor); + numberText.Foreground(brush); + + // Add stroke effect using multiple text elements + Grid textContainer; + textContainer.Name(L"WindhawkNumberOverlay"); + + // Create stroke (background color texts at small offsets) + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + if (dx == 0 && dy == 0) continue; + + TextBlock strokeText; + strokeText.Text(number == 10 ? L"0" : std::to_wstring(number)); + strokeText.FontSize(g_settings.numberSize); + strokeText.Margin(Thickness{static_cast(dx), static_cast(dy), 0, 0}); + + auto strokeColor = ParseHexColor(g_settings.backgroundColor); + auto strokeBrush = SolidColorBrush(); + strokeBrush.Color(strokeColor); + strokeText.Foreground(strokeBrush); + + textContainer.Children().Append(strokeText); + } + } + + // Add main text on top + textContainer.Children().Append(numberText); + + // Position based on settings + switch (g_settings.numberPosition) { + case NumberPosition::topLeft: + textContainer.HorizontalAlignment(HorizontalAlignment::Left); + textContainer.VerticalAlignment(VerticalAlignment::Top); + break; + case NumberPosition::topRight: + textContainer.HorizontalAlignment(HorizontalAlignment::Right); + textContainer.VerticalAlignment(VerticalAlignment::Top); + break; + case NumberPosition::bottomLeft: + textContainer.HorizontalAlignment(HorizontalAlignment::Left); + textContainer.VerticalAlignment(VerticalAlignment::Bottom); + break; + default: // bottomRight + textContainer.HorizontalAlignment(HorizontalAlignment::Right); + textContainer.VerticalAlignment(VerticalAlignment::Bottom); + break; + } + textContainer.Margin(Thickness{0, 0, 2, 2}); + + Canvas::SetZIndex(textContainer, 1000); + + std::lock_guard lock(g_overlayMutex); + iconPanelElement.as().Children().Append(textContainer); + g_numberedButtons.insert(buttonPtr); + + } catch (...) { + // Ignore errors + } +} + +void ClearAllOverlays() { + std::lock_guard lock(g_overlayMutex); + g_numberedButtons.clear(); +} + +void UpdateAllTaskbarNumbers(FrameworkElement taskbarRepeater) { + if (g_unloading) return; + + try { + auto panel = taskbarRepeater.as(); + if (!panel) return; + + auto children = panel.Children(); + + // Collect all TaskListButtons with their X positions + std::vector> buttons; + + for (uint32_t i = 0; i < children.Size(); i++) { + auto child = children.GetAt(i).try_as(); + if (!child || child.Name() != L"TaskListButton") continue; + + double x = child.ActualOffset().x; + buttons.push_back({x, child}); + } + + // Sort by X position (left to right) + std::sort(buttons.begin(), buttons.end(), + [](const auto& a, const auto& b) { return a.first < b.first; }); + + // Number buttons in visual order + int buttonIndex = 0; + for (const auto& [x, button] : buttons) { + if (buttonIndex >= g_settings.maxNumbers) break; + + bool shouldShow = true; + if (g_settings.showOnlyRunning) { + shouldShow = TaskListButton_IsRunning(button); + } + + if (shouldShow) { + buttonIndex++; + CreateNumberOverlay(button, buttonIndex); + } else { + // Remove overlay + auto iconPanel = FindChildByName(button, L"IconPanel"); + if (iconPanel) { + auto existingOverlay = FindChildByName(iconPanel, L"WindhawkNumberOverlay"); + if (existingOverlay) { + auto panelChildren = iconPanel.as().Children(); + uint32_t index; + if (panelChildren.IndexOf(existingOverlay, index)) { + panelChildren.RemoveAt(index); + } + } + } + } + } + + } catch (...) { + // Ignore errors + } +} + +void WINAPI TaskListButton_UpdateVisualStates_Hook(void* pThis) { + if (TaskListButton_UpdateVisualStates_Original) { + TaskListButton_UpdateVisualStates_Original(pThis); + } + + if (g_unloading) return; + + try { + void* taskListButtonIUnknownPtr = (void**)pThis + 3; + winrt::Windows::Foundation::IUnknown taskListButtonIUnknown; + winrt::copy_from_abi(taskListButtonIUnknown, taskListButtonIUnknownPtr); + + auto taskListButtonElement = taskListButtonIUnknown.as(); + if (!taskListButtonElement) return; + + // Find parent repeater + auto parent = Media::VisualTreeHelper::GetParent(taskListButtonElement).as(); + if (!parent || parent.Name() != L"TaskbarFrameRepeater") return; + + // Update all buttons at once to ensure consistent numbering + UpdateAllTaskbarNumbers(parent); + + } catch (...) { + // Ignore errors + } +} + +bool HookTaskbarViewDllSymbols(HMODULE module) { + WindhawkUtils::SYMBOL_HOOK symbolHooks[] = { + { + {LR"(public: virtual int __cdecl winrt::impl::produce::get_IsRunning(bool *))"}, + &TaskListButton_get_IsRunning_Original, + }, + { + {LR"(private: void __cdecl winrt::Taskbar::implementation::TaskListButton::UpdateVisualStates(void))"}, + &TaskListButton_UpdateVisualStates_Original, + TaskListButton_UpdateVisualStates_Hook, + }, + }; + + return HookSymbols(module, symbolHooks, ARRAYSIZE(symbolHooks)); +} + +HMODULE GetTaskbarViewModuleHandle() { + HMODULE module = GetModuleHandle(L"Taskbar.View.dll"); + if (!module) { + module = GetModuleHandle(L"ExplorerExtensions.dll"); + } + return module; +} + +void HandleLoadedModuleIfTaskbarView(HMODULE module, LPCWSTR lpLibFileName) { + if (!g_taskbarViewDllLoaded && GetTaskbarViewModuleHandle() == module && + !g_taskbarViewDllLoaded.exchange(true)) { + + if (HookTaskbarViewDllSymbols(module)) { + Wh_ApplyHookOperations(); + } + } +} + +using LoadLibraryExW_t = decltype(&LoadLibraryExW); +LoadLibraryExW_t LoadLibraryExW_Original; +HMODULE WINAPI LoadLibraryExW_Hook(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) { + HMODULE module = LoadLibraryExW_Original(lpLibFileName, hFile, dwFlags); + if (module) { + HandleLoadedModuleIfTaskbarView(module, lpLibFileName); + } + return module; +} + +void LoadSettings() { + PCWSTR position = Wh_GetStringSetting(L"numberPosition"); + g_settings.numberPosition = NumberPosition::bottomRight; + if (wcscmp(position, L"topLeft") == 0) { + g_settings.numberPosition = NumberPosition::topLeft; + } else if (wcscmp(position, L"topRight") == 0) { + g_settings.numberPosition = NumberPosition::topRight; + } else if (wcscmp(position, L"bottomLeft") == 0) { + g_settings.numberPosition = NumberPosition::bottomLeft; + } + Wh_FreeStringSetting(position); + + g_settings.numberSize = Wh_GetIntSetting(L"numberSize"); + if (g_settings.numberSize < 8) g_settings.numberSize = 8; + if (g_settings.numberSize > 16) g_settings.numberSize = 16; + + PCWSTR numberColor = Wh_GetStringSetting(L"numberColor"); + g_settings.numberColor = numberColor; + Wh_FreeStringSetting(numberColor); + + PCWSTR backgroundColor = Wh_GetStringSetting(L"backgroundColor"); + g_settings.backgroundColor = backgroundColor; + Wh_FreeStringSetting(backgroundColor); + + g_settings.showOnlyRunning = Wh_GetIntSetting(L"showOnlyRunning"); + + g_settings.maxNumbers = Wh_GetIntSetting(L"maxNumbers"); + if (g_settings.maxNumbers < 1) g_settings.maxNumbers = 1; + if (g_settings.maxNumbers > 10) g_settings.maxNumbers = 10; +} + +BOOL Wh_ModInit() { + LoadSettings(); + + if (HMODULE taskbarViewModule = GetTaskbarViewModuleHandle()) { + g_taskbarViewDllLoaded = true; + if (!HookTaskbarViewDllSymbols(taskbarViewModule)) { + return FALSE; + } + } else { + HMODULE kernelBaseModule = GetModuleHandle(L"kernelbase.dll"); + auto pKernelBaseLoadLibraryExW = (decltype(&LoadLibraryExW))GetProcAddress(kernelBaseModule, "LoadLibraryExW"); + WindhawkUtils::SetFunctionHook(pKernelBaseLoadLibraryExW, LoadLibraryExW_Hook, &LoadLibraryExW_Original); + } + + return TRUE; +} + +void Wh_ModAfterInit() { + if (!g_taskbarViewDllLoaded) { + if (HMODULE taskbarViewModule = GetTaskbarViewModuleHandle()) { + if (!g_taskbarViewDllLoaded.exchange(true)) { + if (HookTaskbarViewDllSymbols(taskbarViewModule)) { + Wh_ApplyHookOperations(); + } + } + } + } +} + +void Wh_ModBeforeUninit() { + g_unloading = true; + + std::lock_guard lock(g_overlayMutex); + g_numberedButtons.clear(); +} + +void Wh_ModUninit() { + // Cleanup handled in ModBeforeUninit +} + +void Wh_ModSettingsChanged() { + LoadSettings(); + ClearAllOverlays(); +} From d8adc0d6302e90dedc73ff3d9fc33a49f232a75d Mon Sep 17 00:00:00 2001 From: Jesus Fernandez Date: Tue, 5 Aug 2025 15:16:26 +0200 Subject: [PATCH 02/13] Update taskbar-icon-numberer.wh.cpp --- mods/taskbar-icon-numberer.wh.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mods/taskbar-icon-numberer.wh.cpp b/mods/taskbar-icon-numberer.wh.cpp index 3cdbb8fcc..5d9a72894 100644 --- a/mods/taskbar-icon-numberer.wh.cpp +++ b/mods/taskbar-icon-numberer.wh.cpp @@ -348,7 +348,7 @@ void WINAPI TaskListButton_UpdateVisualStates_Hook(void* pThis) { } bool HookTaskbarViewDllSymbols(HMODULE module) { - WindhawkUtils::SYMBOL_HOOK symbolHooks[] = { + WindhawkUtils::SYMBOL_HOOK taskbarViewDllHooks[] = { { {LR"(public: virtual int __cdecl winrt::impl::produce::get_IsRunning(bool *))"}, &TaskListButton_get_IsRunning_Original, @@ -360,7 +360,7 @@ bool HookTaskbarViewDllSymbols(HMODULE module) { }, }; - return HookSymbols(module, symbolHooks, ARRAYSIZE(symbolHooks)); + return HookSymbols(module, taskbarViewDllHooks, ARRAYSIZE(taskbarViewDllHooks)); } HMODULE GetTaskbarViewModuleHandle() { From a6a67026bd03071e488d2bf0105a377ad4208949 Mon Sep 17 00:00:00 2001 From: Jesus Fernandez Date: Tue, 5 Aug 2025 15:22:05 +0200 Subject: [PATCH 03/13] Update taskbar-icon-numberer.wh.cpp --- mods/taskbar-icon-numberer.wh.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mods/taskbar-icon-numberer.wh.cpp b/mods/taskbar-icon-numberer.wh.cpp index 5d9a72894..515ce6771 100644 --- a/mods/taskbar-icon-numberer.wh.cpp +++ b/mods/taskbar-icon-numberer.wh.cpp @@ -348,7 +348,7 @@ void WINAPI TaskListButton_UpdateVisualStates_Hook(void* pThis) { } bool HookTaskbarViewDllSymbols(HMODULE module) { - WindhawkUtils::SYMBOL_HOOK taskbarViewDllHooks[] = { + WindhawkUtils::SYMBOL_HOOK taskbarViewHooks[] = { { {LR"(public: virtual int __cdecl winrt::impl::produce::get_IsRunning(bool *))"}, &TaskListButton_get_IsRunning_Original, @@ -360,7 +360,7 @@ bool HookTaskbarViewDllSymbols(HMODULE module) { }, }; - return HookSymbols(module, taskbarViewDllHooks, ARRAYSIZE(taskbarViewDllHooks)); + return HookSymbols(module, taskbarViewHooks, ARRAYSIZE(taskbarViewHooks)); } HMODULE GetTaskbarViewModuleHandle() { From f96c51eb242f4c39a77dfce10ce3ee0a825a29ab Mon Sep 17 00:00:00 2001 From: Jesus Fernandez Date: Tue, 5 Aug 2025 15:25:35 +0200 Subject: [PATCH 04/13] Update taskbar-icon-numberer.wh.cpp --- mods/taskbar-icon-numberer.wh.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mods/taskbar-icon-numberer.wh.cpp b/mods/taskbar-icon-numberer.wh.cpp index 515ce6771..9f40dba5d 100644 --- a/mods/taskbar-icon-numberer.wh.cpp +++ b/mods/taskbar-icon-numberer.wh.cpp @@ -348,7 +348,8 @@ void WINAPI TaskListButton_UpdateVisualStates_Hook(void* pThis) { } bool HookTaskbarViewDllSymbols(HMODULE module) { - WindhawkUtils::SYMBOL_HOOK taskbarViewHooks[] = { + // Taskbar.View.dll, ExplorerExtensions.dll + WindhawkUtils::SYMBOL_HOOK taskbarViewDllHooks[] = { { {LR"(public: virtual int __cdecl winrt::impl::produce::get_IsRunning(bool *))"}, &TaskListButton_get_IsRunning_Original, @@ -360,7 +361,7 @@ bool HookTaskbarViewDllSymbols(HMODULE module) { }, }; - return HookSymbols(module, taskbarViewHooks, ARRAYSIZE(taskbarViewHooks)); + return HookSymbols(module, taskbarViewDllHooks, ARRAYSIZE(taskbarViewDllHooks)); } HMODULE GetTaskbarViewModuleHandle() { From 441df44e84b0ac57110ce0e23db5f399c6678b2c Mon Sep 17 00:00:00 2001 From: Jesus Fernandez Date: Tue, 5 Aug 2025 16:18:46 +0200 Subject: [PATCH 05/13] Update taskbar-icon-numberer.wh.cpp --- mods/taskbar-icon-numberer.wh.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mods/taskbar-icon-numberer.wh.cpp b/mods/taskbar-icon-numberer.wh.cpp index 9f40dba5d..27d5df584 100644 --- a/mods/taskbar-icon-numberer.wh.cpp +++ b/mods/taskbar-icon-numberer.wh.cpp @@ -348,8 +348,7 @@ void WINAPI TaskListButton_UpdateVisualStates_Hook(void* pThis) { } bool HookTaskbarViewDllSymbols(HMODULE module) { - // Taskbar.View.dll, ExplorerExtensions.dll - WindhawkUtils::SYMBOL_HOOK taskbarViewDllHooks[] = { + WindhawkUtils::SYMBOL_HOOK user32Hooks[] = { { {LR"(public: virtual int __cdecl winrt::impl::produce::get_IsRunning(bool *))"}, &TaskListButton_get_IsRunning_Original, @@ -361,7 +360,7 @@ bool HookTaskbarViewDllSymbols(HMODULE module) { }, }; - return HookSymbols(module, taskbarViewDllHooks, ARRAYSIZE(taskbarViewDllHooks)); + return HookSymbols(module, user32Hooks, ARRAYSIZE(user32Hooks)); } HMODULE GetTaskbarViewModuleHandle() { From 3e5c5b8b5dc629f4cf4577c828f7cb754acfce35 Mon Sep 17 00:00:00 2001 From: Jesus Fernandez Date: Tue, 5 Aug 2025 16:22:48 +0200 Subject: [PATCH 06/13] Update taskbar-icon-numberer.wh.cpp --- mods/taskbar-icon-numberer.wh.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mods/taskbar-icon-numberer.wh.cpp b/mods/taskbar-icon-numberer.wh.cpp index 27d5df584..86ca51478 100644 --- a/mods/taskbar-icon-numberer.wh.cpp +++ b/mods/taskbar-icon-numberer.wh.cpp @@ -348,7 +348,7 @@ void WINAPI TaskListButton_UpdateVisualStates_Hook(void* pThis) { } bool HookTaskbarViewDllSymbols(HMODULE module) { - WindhawkUtils::SYMBOL_HOOK user32Hooks[] = { + WindhawkUtils::SYMBOL_HOOK user32DllHooks[] = { { {LR"(public: virtual int __cdecl winrt::impl::produce::get_IsRunning(bool *))"}, &TaskListButton_get_IsRunning_Original, @@ -360,7 +360,7 @@ bool HookTaskbarViewDllSymbols(HMODULE module) { }, }; - return HookSymbols(module, user32Hooks, ARRAYSIZE(user32Hooks)); + return HookSymbols(module, user32DllHooks, ARRAYSIZE(user32DllHooks)); } HMODULE GetTaskbarViewModuleHandle() { From fc7dca4ff49947b0deb5a6bccaebe8c9e1e70f72 Mon Sep 17 00:00:00 2001 From: Jesus Fernandez Date: Wed, 6 Aug 2025 12:41:14 +0200 Subject: [PATCH 07/13] Update taskbar-icon-numberer.wh.cpp --- mods/taskbar-icon-numberer.wh.cpp | 138 ++++++++++++++++++++---------- 1 file changed, 94 insertions(+), 44 deletions(-) diff --git a/mods/taskbar-icon-numberer.wh.cpp b/mods/taskbar-icon-numberer.wh.cpp index 86ca51478..7305bb55d 100644 --- a/mods/taskbar-icon-numberer.wh.cpp +++ b/mods/taskbar-icon-numberer.wh.cpp @@ -24,6 +24,8 @@ Adds keyboard shortcut numbers (1-9, 0) to taskbar icons, similar to the classic - Works with Windows 11 taskbar - Supports both light and dark themes +![](https://raw.githubusercontent.com/jsfdez/images/main/taskbar-icon-numberer.wh.cpp.png) + The numbers correspond to the Windows + [number] keyboard shortcuts for quick app launching. */ // ==/WindhawkModReadme== @@ -42,16 +44,10 @@ The numbers correspond to the Windows + [number] keyboard shortcuts for quick ap $description: Size of the overlay numbers (8-16) - numberColor: "#FFFFFF" $name: Number color - $description: Text color for the numbers (hex format #RRGGBB or #AARRGGBB) + $description: Text color for the numbers (hex format "#RRGGBB" or "#AARRGGBB") - backgroundColor: "#80000000" $name: Stroke/outline color - $description: Outline color around the numbers for better visibility (hex format #RRGGBB or #AARRGGBB) -- showOnlyRunning: true - $name: Show only on running apps - $description: Only show numbers on apps that are currently running -- maxNumbers: 10 - $name: Maximum numbers to show - $description: Maximum number of taskbar items to number (1-10) + $description: Outline color around the numbers for better visibility (hex format "#RRGGBB" or "#AARRGGBB") */ // ==/WindhawkModSettings== @@ -80,6 +76,18 @@ using namespace winrt::Windows::UI::Xaml; using namespace winrt::Windows::UI::Xaml::Controls; using namespace winrt::Windows::UI::Xaml::Media; +void LogException(const char* functionName) { + try { + std::rethrow_exception(std::current_exception()); + } catch (winrt::hresult_error const& ex) { + Wh_Log(L"[%S] WinRT exception: 0x%08X - %s", functionName, ex.code(), ex.message().c_str()); + } catch (std::exception const& ex) { + Wh_Log(L"[%S] Standard exception: %S", functionName, ex.what()); + } catch (...) { + Wh_Log(L"[%S] Unknown exception", functionName); + } +} + winrt::Windows::UI::Color ParseHexColor(const std::wstring& hex) { winrt::Windows::UI::Color color{}; if (hex.length() >= 7 && hex[0] == L'#') { @@ -97,6 +105,7 @@ winrt::Windows::UI::Color ParseHexColor(const std::wstring& hex) { } } catch (...) { color.A = 255; color.R = 255; color.G = 255; color.B = 255; + LogException(__FUNCTION__); } } else { color.A = 255; color.R = 255; color.G = 255; color.B = 255; @@ -116,8 +125,6 @@ struct { int numberSize; std::wstring numberColor; std::wstring backgroundColor; - bool showOnlyRunning; - int maxNumbers; } g_settings; std::atomic g_taskbarViewDllLoaded; @@ -146,6 +153,7 @@ FrameworkElement FindChildByName(FrameworkElement element, PCWSTR name) { } } catch (...) { // Ignore errors + LogException(__FUNCTION__); } return nullptr; } @@ -162,6 +170,7 @@ bool TaskListButton_IsRunning(FrameworkElement taskListButtonElement) { &isRunning); return isRunning; } catch (...) { + LogException(__FUNCTION__); return false; } } @@ -254,14 +263,60 @@ void CreateNumberOverlay(FrameworkElement taskListButtonElement, int number) { } catch (...) { // Ignore errors + LogException(__FUNCTION__); + } +} + +void RemoveOverlay(winrt::Windows::UI::Xaml::FrameworkElement& button) { + auto iconPanel = FindChildByName(button, L"IconPanel"); + if (iconPanel) { + auto existingOverlay = + FindChildByName(iconPanel, L"WindhawkNumberOverlay"); + if (existingOverlay) { + auto panelChildren = iconPanel.as().Children(); + uint32_t index; + if (panelChildren.IndexOf(existingOverlay, index)) { + panelChildren.RemoveAt(index); + } + } } } void ClearAllOverlays() { std::lock_guard lock(g_overlayMutex); + auto predicate = [](auto& button) { + RemoveOverlay((winrt::Windows::UI::Xaml::FrameworkElement&)button); + }; + std::for_each(g_numberedButtons.begin(), g_numberedButtons.end(), predicate); g_numberedButtons.clear(); } +// {0BD894F2-EDFC-5DDF-A166-2DB14BBFDF35} +constexpr winrt::guid IItemsRepeater{ + 0x0BD894F2, + 0xEDFC, + 0x5DDF, + {0xA1, 0x66, 0x2D, 0xB1, 0x4B, 0xBF, 0xDF, 0x35}}; + +FrameworkElement ItemsRepeater_TryGetElement( + FrameworkElement taskbarFrameRepeaterElement, + int index) { + winrt::Windows::Foundation::IUnknown pThis = nullptr; + taskbarFrameRepeaterElement.as(IItemsRepeater, winrt::put_abi(pThis)); + + using TryGetElement_t = + HRESULT(WINAPI*)(void* pThis, int index, void** uiElement); + + void** vtable = *(void***)winrt::get_abi(pThis); + auto TryGetElement = (TryGetElement_t)vtable[20]; + + void* uiElement = nullptr; + TryGetElement(winrt::get_abi(pThis), index, &uiElement); + + return UIElement{uiElement, winrt::take_ownership_from_abi} + .try_as(); +} + void UpdateAllTaskbarNumbers(FrameworkElement taskbarRepeater) { if (g_unloading) return; @@ -274,49 +329,49 @@ void UpdateAllTaskbarNumbers(FrameworkElement taskbarRepeater) { // Collect all TaskListButtons with their X positions std::vector> buttons; + Wh_Log(L"Total children found: %d", static_cast(children.Size())); + for (uint32_t i = 0; i < children.Size(); i++) { - auto child = children.GetAt(i).try_as(); + auto child = ItemsRepeater_TryGetElement(panel, i); + // auto child = children.GetAt(i).try_as(); if (!child || child.Name() != L"TaskListButton") continue; + + // More comprehensive filtering + if (child.Visibility() != Visibility::Visible) continue; + + if (child.ActualWidth() <= 0 || child.ActualHeight() <= 0) continue; double x = child.ActualOffset().x; + if (x < 0) continue; + + // Check opacity + if (child.Opacity() <= 0.1) continue; + + Wh_Log(L"Button %d: Valid (x=%f, w=%f, h=%f)", i, x, child.ActualWidth(), child.ActualHeight()); buttons.push_back({x, child}); } + Wh_Log(L"Valid buttons after filtering: %d", static_cast(buttons.size())); + // Sort by X position (left to right) std::sort(buttons.begin(), buttons.end(), [](const auto& a, const auto& b) { return a.first < b.first; }); // Number buttons in visual order - int buttonIndex = 0; - for (const auto& [x, button] : buttons) { - if (buttonIndex >= g_settings.maxNumbers) break; - - bool shouldShow = true; - if (g_settings.showOnlyRunning) { - shouldShow = TaskListButton_IsRunning(button); - } + for (int i = 0; i < buttons.size(); i++) { + auto button = buttons[i].second; - if (shouldShow) { - buttonIndex++; - CreateNumberOverlay(button, buttonIndex); + if (i >= 10) { + // Remove overlay for apps beyond max numbers + RemoveOverlay(button); } else { - // Remove overlay - auto iconPanel = FindChildByName(button, L"IconPanel"); - if (iconPanel) { - auto existingOverlay = FindChildByName(iconPanel, L"WindhawkNumberOverlay"); - if (existingOverlay) { - auto panelChildren = iconPanel.as().Children(); - uint32_t index; - if (panelChildren.IndexOf(existingOverlay, index)) { - panelChildren.RemoveAt(index); - } - } - } + Wh_Log(L"Creating overlay for button %d with number %d", i, i + 1); + CreateNumberOverlay(button, i + 1); } } - } catch (...) { - // Ignore errors + } catch (...) { + LogException(__FUNCTION__); } } @@ -344,11 +399,12 @@ void WINAPI TaskListButton_UpdateVisualStates_Hook(void* pThis) { } catch (...) { // Ignore errors + LogException(__FUNCTION__); } } bool HookTaskbarViewDllSymbols(HMODULE module) { - WindhawkUtils::SYMBOL_HOOK user32DllHooks[] = { + WindhawkUtils::SYMBOL_HOOK symbolHooks[] = { { {LR"(public: virtual int __cdecl winrt::impl::produce::get_IsRunning(bool *))"}, &TaskListButton_get_IsRunning_Original, @@ -360,7 +416,7 @@ bool HookTaskbarViewDllSymbols(HMODULE module) { }, }; - return HookSymbols(module, user32DllHooks, ARRAYSIZE(user32DllHooks)); + return HookSymbols(module, symbolHooks, ARRAYSIZE(symbolHooks)); } HMODULE GetTaskbarViewModuleHandle() { @@ -414,12 +470,6 @@ void LoadSettings() { PCWSTR backgroundColor = Wh_GetStringSetting(L"backgroundColor"); g_settings.backgroundColor = backgroundColor; Wh_FreeStringSetting(backgroundColor); - - g_settings.showOnlyRunning = Wh_GetIntSetting(L"showOnlyRunning"); - - g_settings.maxNumbers = Wh_GetIntSetting(L"maxNumbers"); - if (g_settings.maxNumbers < 1) g_settings.maxNumbers = 1; - if (g_settings.maxNumbers > 10) g_settings.maxNumbers = 10; } BOOL Wh_ModInit() { From 68e35996f36dc3f6407cd7f59fe79ffe9e383d75 Mon Sep 17 00:00:00 2001 From: Jesus Fernandez Date: Wed, 6 Aug 2025 13:39:14 +0200 Subject: [PATCH 08/13] Update taskbar-icon-numberer.wh.cpp --- mods/taskbar-icon-numberer.wh.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/mods/taskbar-icon-numberer.wh.cpp b/mods/taskbar-icon-numberer.wh.cpp index 7305bb55d..ea0c89bf0 100644 --- a/mods/taskbar-icon-numberer.wh.cpp +++ b/mods/taskbar-icon-numberer.wh.cpp @@ -404,6 +404,7 @@ void WINAPI TaskListButton_UpdateVisualStates_Hook(void* pThis) { } bool HookTaskbarViewDllSymbols(HMODULE module) { + // Taskbar.View.dll, ExplorerExtensions.dll WindhawkUtils::SYMBOL_HOOK symbolHooks[] = { { {LR"(public: virtual int __cdecl winrt::impl::produce::get_IsRunning(bool *))"}, From d86c138a622c101c3febc90f2ccba3a8aad234bd Mon Sep 17 00:00:00 2001 From: Jesus Fernandez Date: Sun, 17 Aug 2025 17:25:59 +0200 Subject: [PATCH 09/13] Update taskbar-icon-numberer.wh.cpp --- mods/taskbar-icon-numberer.wh.cpp | 735 +++++++++++++++++++++--------- 1 file changed, 528 insertions(+), 207 deletions(-) diff --git a/mods/taskbar-icon-numberer.wh.cpp b/mods/taskbar-icon-numberer.wh.cpp index ea0c89bf0..97c70dae8 100644 --- a/mods/taskbar-icon-numberer.wh.cpp +++ b/mods/taskbar-icon-numberer.wh.cpp @@ -2,31 +2,40 @@ // @id taskbar-icon-numberer // @name Taskbar Icon Numberer for Windows 11 // @description Add keyboard shortcut numbers (1-9, 0) to taskbar icons like 7+ Taskbar Numberer -// @version 0.0.1 +// @version 0.0.6 // @author js // @github https://github.com/jsfdez // @homepage https://tightcorner.substack.com/p/reverse-engineering-windows-11s-taskbar // @include explorer.exe // @architecture x86-64 -// @compilerOptions -DWINVER=0x0A00 -lole32 -loleaut32 -lruntimeobject +// @compilerOptions -DWINVER=0x0A00 -lole32 -loleaut32 -lruntimeobject -Wextra -lcomctl32 // ==/WindhawkMod== // ==WindhawkModReadme== /* # Taskbar Icon Numberer for Windows 11 -Adds keyboard shortcut numbers (1-9, 0) to taskbar icons, similar to the classic -7+ Taskbar Numberer tool. Numbers appear as small overlays on the taskbar button icons. - -Features: -- Shows numbers 1-9 for the first 9 taskbar items, then 0 for the 10th -- Customizable number position, size, and colors -- Works with Windows 11 taskbar -- Supports both light and dark themes +Displays keyboard shortcut numbers (1-9, 0) as overlays on Windows 11 taskbar icons, replicating the functionality of the classic 7+ Taskbar Numberer tool. ![](https://raw.githubusercontent.com/jsfdez/images/main/taskbar-icon-numberer.wh.cpp.png) -The numbers correspond to the Windows + [number] keyboard shortcuts for quick app launching. +## Features +- Numbers 1-9 for first nine apps, 0 for tenth app +- Fully customizable appearance (position, size, colors) +- Stroke outline for better visibility on any background +- Supports light and dark themes +- Hidden overlays for apps beyond position 10 +- Efficient overlay management with minimal performance impact + +## Usage +Numbers correspond to Windows + [number] keyboard shortcuts for quick app launching. Simply press Win+1 to open the first app, Win+2 for second, etc. + +## Known Issues +- Numbers don't appear until first taskbar interaction (hover over any button) +- Occasional wrong numbers during initial display (self-corrects after reordering) + +## Customization +Configure number position (corners), font size (8-16px), text color, and stroke color through mod settings. */ // ==/WindhawkModReadme== @@ -44,10 +53,10 @@ The numbers correspond to the Windows + [number] keyboard shortcuts for quick ap $description: Size of the overlay numbers (8-16) - numberColor: "#FFFFFF" $name: Number color - $description: Text color for the numbers (hex format "#RRGGBB" or "#AARRGGBB") + $description: Text color for the numbers (hex format "#RRGGBB" or "#AARRGBB") - backgroundColor: "#80000000" $name: Stroke/outline color - $description: Outline color around the numbers for better visibility (hex format "#RRGGBB" or "#AARRGGBB") + $description: Outline color around the numbers for better visibility (hex format "#RRGGBB" or "#AARRGBB") */ // ==/WindhawkModSettings== @@ -63,19 +72,67 @@ The numbers correspond to the Windows + [number] keyboard shortcuts for quick ap #include #include #include +#include #include #include #include #include +#include #include #include +#include +#include using namespace winrt::Windows::UI; using namespace winrt::Windows::UI::Xaml; using namespace winrt::Windows::UI::Xaml::Controls; using namespace winrt::Windows::UI::Xaml::Media; +using LoadLibraryExW_t = decltype(&LoadLibraryExW); +LoadLibraryExW_t LoadLibraryExW_Original; +using RunFromWindowThreadProc_t = void(WINAPI*)(void* parameter); + +// Hook function pointers +using TaskListButton_UpdateVisualStates_t = void(WINAPI*)(void* pThis); +TaskListButton_UpdateVisualStates_t TaskListButton_UpdateVisualStates_Original; + +using TaskListButton_UpdateButtonPadding_t = void(WINAPI*)(void* pThis); +TaskListButton_UpdateButtonPadding_t TaskListButton_UpdateButtonPadding_Original; + +using TaskListButton_UpdateBadgeSize_t = void(WINAPI*)(void* pThis); +TaskListButton_UpdateBadgeSize_t TaskListButton_UpdateBadgeSize_Original; + +enum class NumberPosition { + TopLeft, + TopRight, + BottomLeft, + BottomRight +}; + +struct { + NumberPosition numberPosition; + int numberSize; + std::wstring numberColor; + std::wstring backgroundColor; +} g_settings; + +// Track only primary taskbar buttons with their overlay state +struct ButtonInformation { + void* pointer = nullptr; + FrameworkElement overlay = nullptr; + int currentNumber = -1; + bool isVisible = false; + bool operator<(const ButtonInformation& other) const { return pointer < other.pointer; } +}; + +std::atomic g_taskbarViewDllLoaded; +std::atomic g_unloading; +std::atomic g_initialTime; +std::mutex g_overlayMutex; +std::set g_trackedButtons; + +// Utility functions void LogException(const char* functionName) { try { std::rethrow_exception(std::current_exception()); @@ -113,33 +170,18 @@ winrt::Windows::UI::Color ParseHexColor(const std::wstring& hex) { return color; } -enum class NumberPosition { - topLeft, - topRight, - bottomLeft, - bottomRight -}; +FrameworkElement EnumerateChildren(FrameworkElement element, std::function callback) { + int childrenCount = Media::VisualTreeHelper::GetChildrenCount(element); -struct { - NumberPosition numberPosition; - int numberSize; - std::wstring numberColor; - std::wstring backgroundColor; -} g_settings; - -std::atomic g_taskbarViewDllLoaded; -std::atomic g_unloading; -std::mutex g_overlayMutex; -std::unordered_set g_numberedButtons; - -// Hook function pointers -using TaskListButton_get_IsRunning_t = HRESULT(WINAPI*)(void* pThis, bool* running); -TaskListButton_get_IsRunning_t TaskListButton_get_IsRunning_Original; + for (int i = 0; i < childrenCount; i++) { + auto child = Media::VisualTreeHelper::GetChild(element, i).try_as(); + if (!child) continue; + if (callback(child)) return child; + } -using TaskListButton_UpdateVisualStates_t = void(WINAPI*)(void* pThis); -TaskListButton_UpdateVisualStates_t TaskListButton_UpdateVisualStates_Original; + return nullptr; +} -// Utility functions FrameworkElement FindChildByName(FrameworkElement element, PCWSTR name) { if (!element) return nullptr; @@ -152,67 +194,122 @@ FrameworkElement FindChildByName(FrameworkElement element, PCWSTR name) { } } } catch (...) { - // Ignore errors LogException(__FUNCTION__); } return nullptr; } -bool TaskListButton_IsRunning(FrameworkElement taskListButtonElement) { - if (!TaskListButton_get_IsRunning_Original || !taskListButtonElement) { - return false; +FrameworkElement FindChildByClassName(FrameworkElement element, PCWSTR className) { + return EnumerateChildren(element, [className](FrameworkElement child) { + return winrt::get_class_name(child) == className; + }); +} + +bool IsSecondaryTaskbar(XamlRoot xamlRoot) { + FrameworkElement controlCenterButton = nullptr; + + FrameworkElement child = xamlRoot.Content().try_as(); + if (child && + (child = FindChildByClassName(child, L"SystemTray.SystemTrayFrame")) && + (child = FindChildByName(child, L"SystemTrayFrameGrid")) && + (child = FindChildByName(child, L"ControlCenterButton"))) { + controlCenterButton = child; } - - try { - bool isRunning = false; - TaskListButton_get_IsRunning_Original( - winrt::get_abi(taskListButtonElement.as()), - &isRunning); - return isRunning; - } catch (...) { - LogException(__FUNCTION__); - return false; + if (!controlCenterButton) return false; + return controlCenterButton.ActualWidth() < 5; +} + +HWND FindCurrentProcessTaskbarWnd() { + HWND hTaskbarWnd = nullptr; + + EnumWindows( + [](HWND hWnd, LPARAM lParam) -> BOOL { + DWORD dwProcessId; + DWORD dwCurrentProcessId = GetCurrentProcessId(); + WCHAR className[32]; + + if (GetWindowThreadProcessId(hWnd, &dwProcessId) == FALSE) + { + Wh_Log(L"GetWindowThreadProcessId failed"); + return TRUE; + } + + if (dwProcessId != dwCurrentProcessId) return TRUE; + if (GetClassName(hWnd, className, ARRAYSIZE(className)) == FALSE) return TRUE; + if (_wcsicmp(className, L"Shell_TrayWnd") == 0) { + Wh_Log(L"Found %s in %u", className, dwProcessId); + *reinterpret_cast(lParam) = hWnd; + return FALSE; + } + return TRUE; + }, + reinterpret_cast(&hTaskbarWnd)); + + return hTaskbarWnd; +} + +bool RunFromWindowThread(HWND hWnd, RunFromWindowThreadProc_t proc, void* procParam) { + static const UINT runFromWindowThreadRegisteredMsg = RegisterWindowMessage(L"Windhawk_RunFromWindowThread_" WH_MOD_ID); + + struct RUN_FROM_WINDOW_THREAD_PARAM { + RunFromWindowThreadProc_t proc; + void* procParam; + }; + + DWORD dwThreadId = GetWindowThreadProcessId(hWnd, nullptr); + if (dwThreadId == 0) return false; + if (dwThreadId == GetCurrentThreadId()) { + proc(procParam); + return true; } + + HHOOK hook = SetWindowsHookEx( + WH_CALLWNDPROC, + [](int nCode, WPARAM wParam, LPARAM lParam) -> LRESULT { + if (nCode == HC_ACTION) { + const CWPSTRUCT* cwp = (const CWPSTRUCT*)lParam; + if (cwp->message == runFromWindowThreadRegisteredMsg) { + RUN_FROM_WINDOW_THREAD_PARAM* param = + (RUN_FROM_WINDOW_THREAD_PARAM*)cwp->lParam; + param->proc(param->procParam); + } + } + return CallNextHookEx(nullptr, nCode, wParam, lParam); + }, + nullptr, dwThreadId); + if (!hook) return false; + + RUN_FROM_WINDOW_THREAD_PARAM param; + param.proc = proc; + param.procParam = procParam; + SendMessage(hWnd, runFromWindowThreadRegisteredMsg, 0, (LPARAM)¶m); + UnhookWindowsHookEx(hook); + return true; } -void CreateNumberOverlay(FrameworkElement taskListButtonElement, int number) { - if (!taskListButtonElement || g_unloading) return; - +FrameworkElement CreateNumberOverlay(int number) { try { - auto iconPanelElement = FindChildByName(taskListButtonElement, L"IconPanel"); - if (!iconPanelElement) return; - - void* buttonPtr = winrt::get_abi(taskListButtonElement.as()); - - // Remove existing overlay - auto existingOverlay = FindChildByName(iconPanelElement, L"WindhawkNumberOverlay"); - if (existingOverlay) { - auto panel = iconPanelElement.as(); - auto children = panel.Children(); - uint32_t index; - if (children.IndexOf(existingOverlay, index)) { - children.RemoveAt(index); - } + // Validate number range to prevent invalid numbers like 11 + if (number < 1) { + Wh_Log(L"CreateNumberOverlay: Invalid number %d, skipping creation", number); + return nullptr; } - if (g_unloading) return; - - // Create text with stroke effect TextBlock numberText; numberText.Text(number == 10 ? L"0" : std::to_wstring(number)); numberText.FontSize(g_settings.numberSize); - // White text with parsed color + Wh_Log(L"CreateNumberOverlay: Creating overlay with number %d", number); + auto textColor = ParseHexColor(g_settings.numberColor); auto brush = SolidColorBrush(); brush.Color(textColor); numberText.Foreground(brush); - // Add stroke effect using multiple text elements Grid textContainer; textContainer.Name(L"WindhawkNumberOverlay"); - // Create stroke (background color texts at small offsets) + // Create stroke effect for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { if (dx == 0 && dy == 0) continue; @@ -230,21 +327,19 @@ void CreateNumberOverlay(FrameworkElement taskListButtonElement, int number) { textContainer.Children().Append(strokeText); } } - - // Add main text on top textContainer.Children().Append(numberText); // Position based on settings switch (g_settings.numberPosition) { - case NumberPosition::topLeft: + case NumberPosition::TopLeft: textContainer.HorizontalAlignment(HorizontalAlignment::Left); textContainer.VerticalAlignment(VerticalAlignment::Top); break; - case NumberPosition::topRight: + case NumberPosition::TopRight: textContainer.HorizontalAlignment(HorizontalAlignment::Right); textContainer.VerticalAlignment(VerticalAlignment::Top); break; - case NumberPosition::bottomLeft: + case NumberPosition::BottomLeft: textContainer.HorizontalAlignment(HorizontalAlignment::Left); textContainer.VerticalAlignment(VerticalAlignment::Bottom); break; @@ -256,165 +351,406 @@ void CreateNumberOverlay(FrameworkElement taskListButtonElement, int number) { textContainer.Margin(Thickness{0, 0, 2, 2}); Canvas::SetZIndex(textContainer, 1000); - - std::lock_guard lock(g_overlayMutex); - iconPanelElement.as().Children().Append(textContainer); - g_numberedButtons.insert(buttonPtr); + if (number > 10) textContainer.Visibility(Visibility::Collapsed); + return textContainer; } catch (...) { - // Ignore errors LogException(__FUNCTION__); + return nullptr; } } -void RemoveOverlay(winrt::Windows::UI::Xaml::FrameworkElement& button) { - auto iconPanel = FindChildByName(button, L"IconPanel"); - if (iconPanel) { - auto existingOverlay = - FindChildByName(iconPanel, L"WindhawkNumberOverlay"); - if (existingOverlay) { - auto panelChildren = iconPanel.as().Children(); - uint32_t index; - if (panelChildren.IndexOf(existingOverlay, index)) { - panelChildren.RemoveAt(index); +void RemoveAllNumberOverlays() { + try { + HWND taskbarWnd = FindCurrentProcessTaskbarWnd(); + if (!taskbarWnd) return; + + RunFromWindowThread(taskbarWnd, [](void*) { + try { + std::lock_guard lock(g_overlayMutex); + for (const auto& buttonInfo : g_trackedButtons) { + try { + winrt::Windows::Foundation::IUnknown buttonIUnknown; + winrt::copy_from_abi(buttonIUnknown, buttonInfo.pointer); + auto button = buttonIUnknown.try_as(); + if (button) { + auto iconPanel = FindChildByName(button, L"IconPanel"); + if (iconPanel) { + // Remove ALL overlays with our name + auto panelChildren = iconPanel.as().Children(); + for (uint32_t j = 0; j < panelChildren.Size();) { + auto child = panelChildren.GetAt(j).try_as(); + if (child && child.Name() == L"WindhawkNumberOverlay") { + panelChildren.RemoveAt(j); + } else { + j++; + } + } + } + } + } catch (...) {} + } + + g_trackedButtons.clear(); + + } catch (...) { + LogException(__FUNCTION__); } - } + }, nullptr); + } catch (...) { + LogException(__FUNCTION__); } } -void ClearAllOverlays() { - std::lock_guard lock(g_overlayMutex); - auto predicate = [](auto& button) { - RemoveOverlay((winrt::Windows::UI::Xaml::FrameworkElement&)button); - }; - std::for_each(g_numberedButtons.begin(), g_numberedButtons.end(), predicate); - g_numberedButtons.clear(); +void UpdateOverlayWithNewSettings(const ButtonInformation& buttonInformation) { + if (!buttonInformation.overlay) { + Wh_Log(L"UpdateOverlayWithNewSettings: No overlay to update for button %p", buttonInformation.pointer); + return; + } + + Wh_Log(L"UpdateOverlayWithNewSettings: Updating overlay for button %p, number %d", buttonInformation.pointer, buttonInformation.currentNumber); + + try { + auto grid = buttonInformation.overlay.as(); + auto children = grid.Children(); + std::wstring text = (buttonInformation.currentNumber == 10) ? L"0" : std::to_wstring(buttonInformation.currentNumber); + + // Clear existing children + children.Clear(); + + // Recreate with new settings + auto textColor = ParseHexColor(g_settings.numberColor); + auto strokeColor = ParseHexColor(g_settings.backgroundColor); + + // Create stroke effect + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + if (dx == 0 && dy == 0) continue; + + TextBlock strokeText; + strokeText.Text(text); + strokeText.FontSize(g_settings.numberSize); + strokeText.Margin(Thickness{static_cast(dx), static_cast(dy), 0, 0}); + + auto strokeBrush = SolidColorBrush(); + strokeBrush.Color(strokeColor); + strokeText.Foreground(strokeBrush); + + children.Append(strokeText); + } + } + + // Main text + TextBlock numberText; + numberText.Text(text); + numberText.FontSize(g_settings.numberSize); + + auto brush = SolidColorBrush(); + brush.Color(textColor); + numberText.Foreground(brush); + + children.Append(numberText); + + // Update position + switch (g_settings.numberPosition) { + case NumberPosition::TopLeft: + grid.HorizontalAlignment(HorizontalAlignment::Left); + grid.VerticalAlignment(VerticalAlignment::Top); + break; + case NumberPosition::TopRight: + grid.HorizontalAlignment(HorizontalAlignment::Right); + grid.VerticalAlignment(VerticalAlignment::Top); + break; + case NumberPosition::BottomLeft: + grid.HorizontalAlignment(HorizontalAlignment::Left); + grid.VerticalAlignment(VerticalAlignment::Bottom); + break; + default: // bottomRight + grid.HorizontalAlignment(HorizontalAlignment::Right); + grid.VerticalAlignment(VerticalAlignment::Bottom); + break; + } + + Wh_Log(L"UpdateOverlayWithNewSettings: Successfully updated overlay for button %p", buttonInformation.pointer); + + } catch (...) { + Wh_Log(L"UpdateOverlayWithNewSettings: Failed to update overlay for button %p", buttonInformation.pointer); + LogException(__FUNCTION__); + } } -// {0BD894F2-EDFC-5DDF-A166-2DB14BBFDF35} -constexpr winrt::guid IItemsRepeater{ - 0x0BD894F2, - 0xEDFC, - 0x5DDF, - {0xA1, 0x66, 0x2D, 0xB1, 0x4B, 0xBF, 0xDF, 0x35}}; +void UpdateButtonOverlay(FrameworkElement taskListButtonElement, int number, void* buttonPtr) { + if (!taskListButtonElement || g_unloading) return; -FrameworkElement ItemsRepeater_TryGetElement( - FrameworkElement taskbarFrameRepeaterElement, - int index) { - winrt::Windows::Foundation::IUnknown pThis = nullptr; - taskbarFrameRepeaterElement.as(IItemsRepeater, winrt::put_abi(pThis)); - - using TryGetElement_t = - HRESULT(WINAPI*)(void* pThis, int index, void** uiElement); - - void** vtable = *(void***)winrt::get_abi(pThis); - auto TryGetElement = (TryGetElement_t)vtable[20]; - - void* uiElement = nullptr; - TryGetElement(winrt::get_abi(pThis), index, &uiElement); + // Validate number range immediately to prevent invalid overlays + if (number < 1) { + Wh_Log(L"UpdateButtonOverlay: Invalid number %d for button %p, skipping", number, buttonPtr); + return; + } + + try { + auto iconPanelElement = FindChildByName(taskListButtonElement, L"IconPanel"); + if (!iconPanelElement) { + Wh_Log(L"UpdateButtonOverlay: No IconPanel found for button %p", buttonPtr); + return; + } - return UIElement{uiElement, winrt::take_ownership_from_abi} - .try_as(); + std::lock_guard lock(g_overlayMutex); + + auto it = g_trackedButtons.find(ButtonInformation(buttonPtr)); + ButtonInformation buttonInfo; + if (it != g_trackedButtons.end()) { + buttonInfo = *it; + g_trackedButtons.erase(it); + Wh_Log(L"UpdateButtonOverlay: Found existing button %p, current number %d, new number %d", + buttonPtr, buttonInfo.currentNumber, number); + } else { + buttonInfo = ButtonInformation(buttonPtr); + Wh_Log(L"UpdateButtonOverlay: New button %p, number %d", buttonPtr, number); + } + + const bool shouldShow = number <= 10; + const bool visibilityChanged = buttonInfo.isVisible != shouldShow; + const bool numberChanged = buttonInfo.currentNumber != number; + bool needsNewOverlay = !buttonInfo.overlay; + + Wh_Log(L"UpdateButtonOverlay: Button %p - number=%d, shouldShow=%d, visibilityChanged=%d, numberChanged=%d, needsNewOverlay=%d", buttonPtr, number, shouldShow, visibilityChanged, numberChanged, needsNewOverlay); + + // Always update if number changed, even if it was > 10 before + if (!visibilityChanged && !numberChanged && !needsNewOverlay) { + Wh_Log(L"UpdateButtonOverlay: No changes needed for button %p", buttonPtr); + g_trackedButtons.insert(buttonInfo); + return; + } + + // Force recreation for buttons that moved positions significantly + // This ensures reordering updates work correctly + if (numberChanged && !needsNewOverlay && buttonInfo.overlay) { + Wh_Log(L"UpdateButtonOverlay: Position change detected for button %p (%d -> %d), forcing recreation", buttonPtr, buttonInfo.currentNumber, number); + needsNewOverlay = true; + } + + // Update existing overlay if only number changed + if (buttonInfo.overlay && !needsNewOverlay && !visibilityChanged && numberChanged) { + Wh_Log(L"UpdateButtonOverlay: Updating text for button %p from %d to %d", + buttonPtr, buttonInfo.currentNumber, number); + try { + auto grid = buttonInfo.overlay.as(); + auto children = grid.Children(); + std::wstring newText = (number == 10) ? L"0" : std::to_wstring(number); + + // Update all TextBlock children (stroke + main text) + for (uint32_t i = 0; i < children.Size(); i++) { + auto textBlock = children.GetAt(i).try_as(); + if (textBlock) { + textBlock.Text(newText); + } + } + Wh_Log(L"UpdateButtonOverlay: Successfully updated text for button %p", buttonPtr); + } catch (...) { + Wh_Log(L"UpdateButtonOverlay: Failed to update text for button %p, will recreate overlay", buttonPtr); + needsNewOverlay = true; + } + } + + // Create new overlay if needed + if (needsNewOverlay) { + Wh_Log(L"UpdateButtonOverlay: Creating new overlay for button %p, number %d", buttonPtr, number); + + // Only remove ALL overlays if we're creating a new one (to handle position changes) + auto panelChildren = iconPanelElement.as().Children(); + uint32_t removedCount = 0; + for (uint32_t j = 0; j < panelChildren.Size();) { + auto child = panelChildren.GetAt(j).try_as(); + if (child && child.Name() == L"WindhawkNumberOverlay") { + panelChildren.RemoveAt(j); + removedCount++; + } else { + j++; + } + } + if (removedCount > 0) Wh_Log(L"UpdateButtonOverlay: Removed %d old overlays from button %p", removedCount, buttonPtr); + buttonInfo.overlay = nullptr; + + auto newOverlay = CreateNumberOverlay(number); + if (newOverlay) { + iconPanelElement.as().Children().Append(newOverlay); + buttonInfo.overlay = newOverlay; + Wh_Log(L"UpdateButtonOverlay: Successfully created new overlay for button %p", buttonPtr); + } else { + Wh_Log(L"UpdateButtonOverlay: Failed to create overlay for button %p", buttonPtr); + } + } + + // Update visibility + if (buttonInfo.overlay && visibilityChanged) { + const auto newVisibility = shouldShow ? Visibility::Visible : Visibility::Collapsed; + buttonInfo.overlay.Visibility(newVisibility); + Wh_Log(L"UpdateButtonOverlay: Changed visibility for button %p to %s", buttonPtr, shouldShow ? L"Visible" : L"Collapsed"); + } + + buttonInfo.currentNumber = number; + buttonInfo.isVisible = shouldShow; + + g_trackedButtons.insert(buttonInfo); + + } catch (...) { + Wh_Log(L"UpdateButtonOverlay: Exception occurred for button %p", buttonPtr); + LogException(__FUNCTION__); + } } void UpdateAllTaskbarNumbers(FrameworkElement taskbarRepeater) { if (g_unloading) return; + // Skip processing for first 2 seconds after init to avoid startup issues + if (GetTickCount() - g_initialTime < 2000) return; + try { + const auto xamlRoot = taskbarRepeater.XamlRoot(); + if (!xamlRoot || IsSecondaryTaskbar(xamlRoot)) return; + auto panel = taskbarRepeater.as(); if (!panel) return; auto children = panel.Children(); - - // Collect all TaskListButtons with their X positions std::vector> buttons; - Wh_Log(L"Total children found: %d", static_cast(children.Size())); - for (uint32_t i = 0; i < children.Size(); i++) { - auto child = ItemsRepeater_TryGetElement(panel, i); - // auto child = children.GetAt(i).try_as(); - if (!child || child.Name() != L"TaskListButton") continue; - - // More comprehensive filtering - if (child.Visibility() != Visibility::Visible) continue; + auto child = children.GetAt(i).try_as(); + if (!child) continue; + if (child.Name() != L"TaskListButton") continue; + if (child.Visibility() != Visibility::Visible) continue; if (child.ActualWidth() <= 0 || child.ActualHeight() <= 0) continue; + if (child.Opacity() <= 0.1) continue; double x = child.ActualOffset().x; if (x < 0) continue; - // Check opacity - if (child.Opacity() <= 0.1) continue; - - Wh_Log(L"Button %d: Valid (x=%f, w=%f, h=%f)", i, x, child.ActualWidth(), child.ActualHeight()); buttons.push_back({x, child}); } - Wh_Log(L"Valid buttons after filtering: %d", static_cast(buttons.size())); + // Skip processing if no buttons or during initial setup + if (buttons.empty()) { + Wh_Log(L"UpdateAllTaskbarNumbers: No valid buttons found, skipping"); + return; + } // Sort by X position (left to right) - std::sort(buttons.begin(), buttons.end(), - [](const auto& a, const auto& b) { return a.first < b.first; }); + std::sort(buttons.begin(), buttons.end(), [](const auto& a, const auto& b) { return a.first < b.first; }); + + Wh_Log(L"UpdateAllTaskbarNumbers: Found %zu taskbar buttons", buttons.size()); + + // Clean up buttons no longer present + std::unordered_set currentButtons; + for (const auto& [pos, button] : buttons) { + void* buttonPtr = winrt::get_abi(button.as()); + currentButtons.insert(buttonPtr); + } - // Number buttons in visual order - for (int i = 0; i < buttons.size(); i++) { + { + std::lock_guard lock(g_overlayMutex); + auto it = g_trackedButtons.begin(); + size_t removedCount = 0; + while (it != g_trackedButtons.end()) { + if (currentButtons.find(it->pointer) == currentButtons.end()) { + it = g_trackedButtons.erase(it); + removedCount++; + } else { + ++it; + } + } + if (removedCount > 0) Wh_Log(L"UpdateAllTaskbarNumbers: Removed %zu buttons no longer present", removedCount); + } + + // Update overlays for all buttons - ensure correct numbering + for (size_t i = 0; i < buttons.size(); i++) { auto button = buttons[i].second; + void* buttonPointer = winrt::get_abi(button.as()); + int buttonNumber = static_cast(i + 1); - if (i >= 10) { - // Remove overlay for apps beyond max numbers - RemoveOverlay(button); - } else { - Wh_Log(L"Creating overlay for button %d with number %d", i, i + 1); - CreateNumberOverlay(button, i + 1); + // Validate button number is in expected range + if (buttonNumber < 1 || buttonNumber > static_cast(buttons.size())) { + Wh_Log(L"UpdateAllTaskbarNumbers: Invalid button number %d for button %zu", buttonNumber, i); + continue; } + + Wh_Log(L"UpdateAllTaskbarNumbers: Button %zu at position %.1f gets number %d", i, buttons[i].first, buttonNumber); + UpdateButtonOverlay(button, buttonNumber, buttonPointer); } - + Wh_Log(L"UpdateAllTaskbarNumbers: Processing complete"); } catch (...) { + Wh_Log(L"UpdateAllTaskbarNumbers: Exception occurred"); LogException(__FUNCTION__); } } void WINAPI TaskListButton_UpdateVisualStates_Hook(void* pThis) { + // Call the original function first if (TaskListButton_UpdateVisualStates_Original) { TaskListButton_UpdateVisualStates_Original(pThis); } - if (g_unloading) return; - try { - void* taskListButtonIUnknownPtr = (void**)pThis + 3; + void* taskListButtonIUnknownPointer = (void**)pThis + 3; winrt::Windows::Foundation::IUnknown taskListButtonIUnknown; - winrt::copy_from_abi(taskListButtonIUnknown, taskListButtonIUnknownPtr); + winrt::copy_from_abi(taskListButtonIUnknown, taskListButtonIUnknownPointer); auto taskListButtonElement = taskListButtonIUnknown.as(); if (!taskListButtonElement) return; + + if (auto xamlRoot = taskListButtonElement.XamlRoot()) { + if (IsSecondaryTaskbar(xamlRoot)) return; + } - // Find parent repeater auto parent = Media::VisualTreeHelper::GetParent(taskListButtonElement).as(); if (!parent || parent.Name() != L"TaskbarFrameRepeater") return; - // Update all buttons at once to ensure consistent numbering - UpdateAllTaskbarNumbers(parent); + if (g_unloading) { + std::lock_guard lock(g_overlayMutex); + g_trackedButtons.clear(); + } else { + UpdateAllTaskbarNumbers(parent); + } } catch (...) { - // Ignore errors LogException(__FUNCTION__); } } +void WINAPI TaskListButton_UpdateButtonPadding_Hook(void* pThis) { + if (TaskListButton_UpdateButtonPadding_Original) { + TaskListButton_UpdateButtonPadding_Original(pThis); + } + TaskListButton_UpdateVisualStates_Hook(pThis); +} + +void WINAPI TaskListButton_UpdateBadgeSize_Hook(void* pThis) { + if (TaskListButton_UpdateBadgeSize_Original) { + TaskListButton_UpdateBadgeSize_Original(pThis); + } + TaskListButton_UpdateVisualStates_Hook(pThis); +} + + bool HookTaskbarViewDllSymbols(HMODULE module) { - // Taskbar.View.dll, ExplorerExtensions.dll WindhawkUtils::SYMBOL_HOOK symbolHooks[] = { - { - {LR"(public: virtual int __cdecl winrt::impl::produce::get_IsRunning(bool *))"}, - &TaskListButton_get_IsRunning_Original, - }, { {LR"(private: void __cdecl winrt::Taskbar::implementation::TaskListButton::UpdateVisualStates(void))"}, &TaskListButton_UpdateVisualStates_Original, TaskListButton_UpdateVisualStates_Hook, }, + { + {LR"(private: void __cdecl winrt::Taskbar::implementation::TaskListButton::UpdateButtonPadding(void))"}, + &TaskListButton_UpdateButtonPadding_Original, + TaskListButton_UpdateButtonPadding_Hook, + }, + { + {LR"(private: void __cdecl winrt::Taskbar::implementation::TaskListButton::UpdateBadgeSize(void))"}, + &TaskListButton_UpdateBadgeSize_Original, + TaskListButton_UpdateBadgeSize_Hook, + }, }; return HookSymbols(module, symbolHooks, ARRAYSIZE(symbolHooks)); @@ -422,42 +758,24 @@ bool HookTaskbarViewDllSymbols(HMODULE module) { HMODULE GetTaskbarViewModuleHandle() { HMODULE module = GetModuleHandle(L"Taskbar.View.dll"); - if (!module) { - module = GetModuleHandle(L"ExplorerExtensions.dll"); - } + if (!module) module = GetModuleHandle(L"ExplorerExtensions.dll"); return module; } -void HandleLoadedModuleIfTaskbarView(HMODULE module, LPCWSTR lpLibFileName) { - if (!g_taskbarViewDllLoaded && GetTaskbarViewModuleHandle() == module && - !g_taskbarViewDllLoaded.exchange(true)) { - - if (HookTaskbarViewDllSymbols(module)) { - Wh_ApplyHookOperations(); - } - } -} - -using LoadLibraryExW_t = decltype(&LoadLibraryExW); -LoadLibraryExW_t LoadLibraryExW_Original; HMODULE WINAPI LoadLibraryExW_Hook(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) { HMODULE module = LoadLibraryExW_Original(lpLibFileName, hFile, dwFlags); - if (module) { - HandleLoadedModuleIfTaskbarView(module, lpLibFileName); + if (module && !g_taskbarViewDllLoaded && GetTaskbarViewModuleHandle() == module && !g_taskbarViewDllLoaded.exchange(true)) { + if (HookTaskbarViewDllSymbols(module)) Wh_ApplyHookOperations(); } return module; } void LoadSettings() { PCWSTR position = Wh_GetStringSetting(L"numberPosition"); - g_settings.numberPosition = NumberPosition::bottomRight; - if (wcscmp(position, L"topLeft") == 0) { - g_settings.numberPosition = NumberPosition::topLeft; - } else if (wcscmp(position, L"topRight") == 0) { - g_settings.numberPosition = NumberPosition::topRight; - } else if (wcscmp(position, L"bottomLeft") == 0) { - g_settings.numberPosition = NumberPosition::bottomLeft; - } + g_settings.numberPosition = NumberPosition::BottomRight; + if (wcscmp(position, L"topLeft") == 0) g_settings.numberPosition = NumberPosition::TopLeft; + else if (wcscmp(position, L"topRight") == 0) g_settings.numberPosition = NumberPosition::TopRight; + else if (wcscmp(position, L"bottomLeft") == 0) g_settings.numberPosition = NumberPosition::BottomLeft; Wh_FreeStringSetting(position); g_settings.numberSize = Wh_GetIntSetting(L"numberSize"); @@ -474,13 +792,12 @@ void LoadSettings() { } BOOL Wh_ModInit() { + g_initialTime = GetTickCount(); LoadSettings(); if (HMODULE taskbarViewModule = GetTaskbarViewModuleHandle()) { g_taskbarViewDllLoaded = true; - if (!HookTaskbarViewDllSymbols(taskbarViewModule)) { - return FALSE; - } + if (!HookTaskbarViewDllSymbols(taskbarViewModule)) return FALSE; } else { HMODULE kernelBaseModule = GetModuleHandle(L"kernelbase.dll"); auto pKernelBaseLoadLibraryExW = (decltype(&LoadLibraryExW))GetProcAddress(kernelBaseModule, "LoadLibraryExW"); @@ -491,29 +808,33 @@ BOOL Wh_ModInit() { } void Wh_ModAfterInit() { - if (!g_taskbarViewDllLoaded) { - if (HMODULE taskbarViewModule = GetTaskbarViewModuleHandle()) { - if (!g_taskbarViewDllLoaded.exchange(true)) { - if (HookTaskbarViewDllSymbols(taskbarViewModule)) { - Wh_ApplyHookOperations(); - } - } - } - } + if (!g_taskbarViewDllLoaded) + if (HMODULE taskbarViewModule = GetTaskbarViewModuleHandle()) + if (!g_taskbarViewDllLoaded.exchange(true)) + if (HookTaskbarViewDllSymbols(taskbarViewModule)) Wh_ApplyHookOperations(); } void Wh_ModBeforeUninit() { g_unloading = true; - - std::lock_guard lock(g_overlayMutex); - g_numberedButtons.clear(); + RemoveAllNumberOverlays(); + Sleep(100); } -void Wh_ModUninit() { - // Cleanup handled in ModBeforeUninit -} +void Wh_ModUninit() {} void Wh_ModSettingsChanged() { + Wh_Log(L"Settings changed, updating existing overlays"); LoadSettings(); - ClearAllOverlays(); + + HWND taskbarWnd = FindCurrentProcessTaskbarWnd(); + if (taskbarWnd) { + RunFromWindowThread(taskbarWnd, [](void*) { + std::lock_guard lock(g_overlayMutex); + + Wh_Log(L"Updating %zu tracked buttons with new settings", g_trackedButtons.size()); + + // Update existing overlays with new settings + for (const auto& buttonInfo : g_trackedButtons) UpdateOverlayWithNewSettings(buttonInfo); + }, nullptr); + } } From b4f75797f29dacb85d0a8c75b335726b5230c591 Mon Sep 17 00:00:00 2001 From: Jesus Fernandez Date: Sun, 17 Aug 2025 21:28:06 +0200 Subject: [PATCH 10/13] Update taskbar-icon-numberer.wh.cpp --- mods/taskbar-icon-numberer.wh.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mods/taskbar-icon-numberer.wh.cpp b/mods/taskbar-icon-numberer.wh.cpp index 97c70dae8..3166c34f7 100644 --- a/mods/taskbar-icon-numberer.wh.cpp +++ b/mods/taskbar-icon-numberer.wh.cpp @@ -8,7 +8,7 @@ // @homepage https://tightcorner.substack.com/p/reverse-engineering-windows-11s-taskbar // @include explorer.exe // @architecture x86-64 -// @compilerOptions -DWINVER=0x0A00 -lole32 -loleaut32 -lruntimeobject -Wextra -lcomctl32 +// @compilerOptions -DWINVER=0x0A00 -lole32 -loleaut32 -lruntimeobject -lcomctl32 // ==/WindhawkMod== // ==WindhawkModReadme== @@ -735,6 +735,7 @@ void WINAPI TaskListButton_UpdateBadgeSize_Hook(void* pThis) { bool HookTaskbarViewDllSymbols(HMODULE module) { + // Taskbar.View.dll, ExplorerExtensions.dll WindhawkUtils::SYMBOL_HOOK symbolHooks[] = { { {LR"(private: void __cdecl winrt::Taskbar::implementation::TaskListButton::UpdateVisualStates(void))"}, From 4f113bda966093d5a85e4500309ecff21272b604 Mon Sep 17 00:00:00 2001 From: Jesus Fernandez Date: Mon, 18 Aug 2025 09:02:14 +0200 Subject: [PATCH 11/13] Update taskbar-icon-numberer.wh.cpp --- mods/taskbar-icon-numberer.wh.cpp | 130 ++++++++++++++++-------------- 1 file changed, 68 insertions(+), 62 deletions(-) diff --git a/mods/taskbar-icon-numberer.wh.cpp b/mods/taskbar-icon-numberer.wh.cpp index 3166c34f7..862d45c20 100644 --- a/mods/taskbar-icon-numberer.wh.cpp +++ b/mods/taskbar-icon-numberer.wh.cpp @@ -8,7 +8,7 @@ // @homepage https://tightcorner.substack.com/p/reverse-engineering-windows-11s-taskbar // @include explorer.exe // @architecture x86-64 -// @compilerOptions -DWINVER=0x0A00 -lole32 -loleaut32 -lruntimeobject -lcomctl32 +// @compilerOptions -DWINVER=0x0A00 -lole32 -loleaut32 -lruntimeobject -Wextra -lcomctl32 // ==/WindhawkMod== // ==WindhawkModReadme== @@ -104,10 +104,10 @@ using TaskListButton_UpdateBadgeSize_t = void(WINAPI*)(void* pThis); TaskListButton_UpdateBadgeSize_t TaskListButton_UpdateBadgeSize_Original; enum class NumberPosition { - TopLeft, - TopRight, - BottomLeft, - BottomRight + topLeft, + topRight, + bottomLeft, + bottomRight }; struct { @@ -119,11 +119,11 @@ struct { // Track only primary taskbar buttons with their overlay state struct ButtonInformation { - void* pointer = nullptr; + winrt::weak_ref button; FrameworkElement overlay = nullptr; int currentNumber = -1; bool isVisible = false; - bool operator<(const ButtonInformation& other) const { return pointer < other.pointer; } + bool operator<(const ButtonInformation& other) const { return winrt::get_abi(button) < winrt::get_abi(other.button); } }; std::atomic g_taskbarViewDllLoaded; @@ -327,19 +327,20 @@ FrameworkElement CreateNumberOverlay(int number) { textContainer.Children().Append(strokeText); } } + textContainer.Children().Append(numberText); // Position based on settings switch (g_settings.numberPosition) { - case NumberPosition::TopLeft: + case NumberPosition::topLeft: textContainer.HorizontalAlignment(HorizontalAlignment::Left); textContainer.VerticalAlignment(VerticalAlignment::Top); break; - case NumberPosition::TopRight: + case NumberPosition::topRight: textContainer.HorizontalAlignment(HorizontalAlignment::Right); textContainer.VerticalAlignment(VerticalAlignment::Top); break; - case NumberPosition::BottomLeft: + case NumberPosition::bottomLeft: textContainer.HorizontalAlignment(HorizontalAlignment::Left); textContainer.VerticalAlignment(VerticalAlignment::Bottom); break; @@ -351,7 +352,10 @@ FrameworkElement CreateNumberOverlay(int number) { textContainer.Margin(Thickness{0, 0, 2, 2}); Canvas::SetZIndex(textContainer, 1000); - if (number > 10) textContainer.Visibility(Visibility::Collapsed); + if (number > 10) { + textContainer.Visibility(Visibility::Collapsed); + } + return textContainer; } catch (...) { @@ -370,11 +374,9 @@ void RemoveAllNumberOverlays() { std::lock_guard lock(g_overlayMutex); for (const auto& buttonInfo : g_trackedButtons) { try { - winrt::Windows::Foundation::IUnknown buttonIUnknown; - winrt::copy_from_abi(buttonIUnknown, buttonInfo.pointer); - auto button = buttonIUnknown.try_as(); + auto button = buttonInfo.button; if (button) { - auto iconPanel = FindChildByName(button, L"IconPanel"); + auto iconPanel = FindChildByName(button.get(), L"IconPanel"); if (iconPanel) { // Remove ALL overlays with our name auto panelChildren = iconPanel.as().Children(); @@ -388,7 +390,9 @@ void RemoveAllNumberOverlays() { } } } - } catch (...) {} + } catch (...) { + LogException(__FUNCTION__); + } } g_trackedButtons.clear(); @@ -404,11 +408,11 @@ void RemoveAllNumberOverlays() { void UpdateOverlayWithNewSettings(const ButtonInformation& buttonInformation) { if (!buttonInformation.overlay) { - Wh_Log(L"UpdateOverlayWithNewSettings: No overlay to update for button %p", buttonInformation.pointer); + Wh_Log(L"UpdateOverlayWithNewSettings: No overlay to update for button %p", &buttonInformation.button); return; } - Wh_Log(L"UpdateOverlayWithNewSettings: Updating overlay for button %p, number %d", buttonInformation.pointer, buttonInformation.currentNumber); + Wh_Log(L"UpdateOverlayWithNewSettings: Updating overlay for button %p, number %d", &buttonInformation.button, buttonInformation.currentNumber); try { auto grid = buttonInformation.overlay.as(); @@ -453,15 +457,15 @@ void UpdateOverlayWithNewSettings(const ButtonInformation& buttonInformation) { // Update position switch (g_settings.numberPosition) { - case NumberPosition::TopLeft: + case NumberPosition::topLeft: grid.HorizontalAlignment(HorizontalAlignment::Left); grid.VerticalAlignment(VerticalAlignment::Top); break; - case NumberPosition::TopRight: + case NumberPosition::topRight: grid.HorizontalAlignment(HorizontalAlignment::Right); grid.VerticalAlignment(VerticalAlignment::Top); break; - case NumberPosition::BottomLeft: + case NumberPosition::bottomLeft: grid.HorizontalAlignment(HorizontalAlignment::Left); grid.VerticalAlignment(VerticalAlignment::Bottom); break; @@ -471,42 +475,46 @@ void UpdateOverlayWithNewSettings(const ButtonInformation& buttonInformation) { break; } - Wh_Log(L"UpdateOverlayWithNewSettings: Successfully updated overlay for button %p", buttonInformation.pointer); + Wh_Log(L"UpdateOverlayWithNewSettings: Successfully updated overlay for button %p", &buttonInformation.button); } catch (...) { - Wh_Log(L"UpdateOverlayWithNewSettings: Failed to update overlay for button %p", buttonInformation.pointer); + Wh_Log(L"UpdateOverlayWithNewSettings: Failed to update overlay for button %p", &buttonInformation.button); LogException(__FUNCTION__); } } -void UpdateButtonOverlay(FrameworkElement taskListButtonElement, int number, void* buttonPtr) { +void UpdateButtonOverlay(FrameworkElement taskListButtonElement, int number, void* buttonPointer) { if (!taskListButtonElement || g_unloading) return; // Validate number range immediately to prevent invalid overlays if (number < 1) { - Wh_Log(L"UpdateButtonOverlay: Invalid number %d for button %p, skipping", number, buttonPtr); + Wh_Log(L"UpdateButtonOverlay: Invalid number %d for button %p, skipping", number, buttonPointer); return; } try { auto iconPanelElement = FindChildByName(taskListButtonElement, L"IconPanel"); if (!iconPanelElement) { - Wh_Log(L"UpdateButtonOverlay: No IconPanel found for button %p", buttonPtr); + Wh_Log(L"UpdateButtonOverlay: No IconPanel found for button %p", buttonPointer); return; } std::lock_guard lock(g_overlayMutex); - auto it = g_trackedButtons.find(ButtonInformation(buttonPtr)); + winrt::Windows::Foundation::IUnknown buttonIUnknown; + winrt::copy_from_abi(buttonIUnknown, buttonPointer); + auto button = buttonIUnknown.try_as(); + + auto it = g_trackedButtons.find(ButtonInformation(button)); ButtonInformation buttonInfo; if (it != g_trackedButtons.end()) { buttonInfo = *it; g_trackedButtons.erase(it); Wh_Log(L"UpdateButtonOverlay: Found existing button %p, current number %d, new number %d", - buttonPtr, buttonInfo.currentNumber, number); + buttonPointer, buttonInfo.currentNumber, number); } else { - buttonInfo = ButtonInformation(buttonPtr); - Wh_Log(L"UpdateButtonOverlay: New button %p, number %d", buttonPtr, number); + buttonInfo = ButtonInformation(button); + Wh_Log(L"UpdateButtonOverlay: New button %p, number %d", buttonPointer, number); } const bool shouldShow = number <= 10; @@ -514,11 +522,11 @@ void UpdateButtonOverlay(FrameworkElement taskListButtonElement, int number, voi const bool numberChanged = buttonInfo.currentNumber != number; bool needsNewOverlay = !buttonInfo.overlay; - Wh_Log(L"UpdateButtonOverlay: Button %p - number=%d, shouldShow=%d, visibilityChanged=%d, numberChanged=%d, needsNewOverlay=%d", buttonPtr, number, shouldShow, visibilityChanged, numberChanged, needsNewOverlay); + Wh_Log(L"UpdateButtonOverlay: Button %p - number=%d, shouldShow=%d, visibilityChanged=%d, numberChanged=%d, needsNewOverlay=%d", buttonPointer, number, shouldShow, visibilityChanged, numberChanged, needsNewOverlay); // Always update if number changed, even if it was > 10 before if (!visibilityChanged && !numberChanged && !needsNewOverlay) { - Wh_Log(L"UpdateButtonOverlay: No changes needed for button %p", buttonPtr); + Wh_Log(L"UpdateButtonOverlay: No changes needed for button %p", buttonPointer); g_trackedButtons.insert(buttonInfo); return; } @@ -526,14 +534,14 @@ void UpdateButtonOverlay(FrameworkElement taskListButtonElement, int number, voi // Force recreation for buttons that moved positions significantly // This ensures reordering updates work correctly if (numberChanged && !needsNewOverlay && buttonInfo.overlay) { - Wh_Log(L"UpdateButtonOverlay: Position change detected for button %p (%d -> %d), forcing recreation", buttonPtr, buttonInfo.currentNumber, number); + Wh_Log(L"UpdateButtonOverlay: Position change detected for button %p (%d -> %d), forcing recreation", buttonPointer, buttonInfo.currentNumber, number); needsNewOverlay = true; } // Update existing overlay if only number changed if (buttonInfo.overlay && !needsNewOverlay && !visibilityChanged && numberChanged) { Wh_Log(L"UpdateButtonOverlay: Updating text for button %p from %d to %d", - buttonPtr, buttonInfo.currentNumber, number); + buttonPointer, buttonInfo.currentNumber, number); try { auto grid = buttonInfo.overlay.as(); auto children = grid.Children(); @@ -546,16 +554,16 @@ void UpdateButtonOverlay(FrameworkElement taskListButtonElement, int number, voi textBlock.Text(newText); } } - Wh_Log(L"UpdateButtonOverlay: Successfully updated text for button %p", buttonPtr); + Wh_Log(L"UpdateButtonOverlay: Successfully updated text for button %p", buttonPointer); } catch (...) { - Wh_Log(L"UpdateButtonOverlay: Failed to update text for button %p, will recreate overlay", buttonPtr); + Wh_Log(L"UpdateButtonOverlay: Failed to update text for button %p, will recreate overlay", buttonPointer); needsNewOverlay = true; } } // Create new overlay if needed if (needsNewOverlay) { - Wh_Log(L"UpdateButtonOverlay: Creating new overlay for button %p, number %d", buttonPtr, number); + Wh_Log(L"UpdateButtonOverlay: Creating new overlay for button %p, number %d", buttonPointer, number); // Only remove ALL overlays if we're creating a new one (to handle position changes) auto panelChildren = iconPanelElement.as().Children(); @@ -569,24 +577,26 @@ void UpdateButtonOverlay(FrameworkElement taskListButtonElement, int number, voi j++; } } - if (removedCount > 0) Wh_Log(L"UpdateButtonOverlay: Removed %d old overlays from button %p", removedCount, buttonPtr); + if (removedCount > 0) { + Wh_Log(L"UpdateButtonOverlay: Removed %d old overlays from button %p", removedCount, buttonPointer); + } buttonInfo.overlay = nullptr; auto newOverlay = CreateNumberOverlay(number); if (newOverlay) { iconPanelElement.as().Children().Append(newOverlay); buttonInfo.overlay = newOverlay; - Wh_Log(L"UpdateButtonOverlay: Successfully created new overlay for button %p", buttonPtr); + Wh_Log(L"UpdateButtonOverlay: Successfully created new overlay for button %p", buttonPointer); } else { - Wh_Log(L"UpdateButtonOverlay: Failed to create overlay for button %p", buttonPtr); + Wh_Log(L"UpdateButtonOverlay: Failed to create overlay for button %p", buttonPointer); } } // Update visibility if (buttonInfo.overlay && visibilityChanged) { - const auto newVisibility = shouldShow ? Visibility::Visible : Visibility::Collapsed; + auto newVisibility = shouldShow ? Visibility::Visible : Visibility::Collapsed; buttonInfo.overlay.Visibility(newVisibility); - Wh_Log(L"UpdateButtonOverlay: Changed visibility for button %p to %s", buttonPtr, shouldShow ? L"Visible" : L"Collapsed"); + Wh_Log(L"UpdateButtonOverlay: Changed visibility for button %p to %s", buttonPointer, shouldShow ? L"Visible" : L"Collapsed"); } buttonInfo.currentNumber = number; @@ -595,7 +605,7 @@ void UpdateButtonOverlay(FrameworkElement taskListButtonElement, int number, voi g_trackedButtons.insert(buttonInfo); } catch (...) { - Wh_Log(L"UpdateButtonOverlay: Exception occurred for button %p", buttonPtr); + Wh_Log(L"UpdateButtonOverlay: Exception occurred for button %p", buttonPointer); LogException(__FUNCTION__); } } @@ -603,9 +613,6 @@ void UpdateButtonOverlay(FrameworkElement taskListButtonElement, int number, voi void UpdateAllTaskbarNumbers(FrameworkElement taskbarRepeater) { if (g_unloading) return; - // Skip processing for first 2 seconds after init to avoid startup issues - if (GetTickCount() - g_initialTime < 2000) return; - try { const auto xamlRoot = taskbarRepeater.XamlRoot(); if (!xamlRoot || IsSecondaryTaskbar(xamlRoot)) return; @@ -643,10 +650,9 @@ void UpdateAllTaskbarNumbers(FrameworkElement taskbarRepeater) { Wh_Log(L"UpdateAllTaskbarNumbers: Found %zu taskbar buttons", buttons.size()); // Clean up buttons no longer present - std::unordered_set currentButtons; + std::unordered_set currentButtons; for (const auto& [pos, button] : buttons) { - void* buttonPtr = winrt::get_abi(button.as()); - currentButtons.insert(buttonPtr); + currentButtons.insert(button); } { @@ -654,7 +660,7 @@ void UpdateAllTaskbarNumbers(FrameworkElement taskbarRepeater) { auto it = g_trackedButtons.begin(); size_t removedCount = 0; while (it != g_trackedButtons.end()) { - if (currentButtons.find(it->pointer) == currentButtons.end()) { + if (currentButtons.find(it->button.get()) == currentButtons.end()) { it = g_trackedButtons.erase(it); removedCount++; } else { @@ -679,6 +685,7 @@ void UpdateAllTaskbarNumbers(FrameworkElement taskbarRepeater) { Wh_Log(L"UpdateAllTaskbarNumbers: Button %zu at position %.1f gets number %d", i, buttons[i].first, buttonNumber); UpdateButtonOverlay(button, buttonNumber, buttonPointer); } + Wh_Log(L"UpdateAllTaskbarNumbers: Processing complete"); } catch (...) { Wh_Log(L"UpdateAllTaskbarNumbers: Exception occurred"); @@ -707,35 +714,34 @@ void WINAPI TaskListButton_UpdateVisualStates_Hook(void* pThis) { auto parent = Media::VisualTreeHelper::GetParent(taskListButtonElement).as(); if (!parent || parent.Name() != L"TaskbarFrameRepeater") return; - if (g_unloading) { - std::lock_guard lock(g_overlayMutex); - g_trackedButtons.clear(); - } else { - UpdateAllTaskbarNumbers(parent); - } - + if (!g_unloading) UpdateAllTaskbarNumbers(parent); } catch (...) { LogException(__FUNCTION__); } } +// Also hook UpdateButtonPadding which is called during layout updates void WINAPI TaskListButton_UpdateButtonPadding_Hook(void* pThis) { if (TaskListButton_UpdateButtonPadding_Original) { TaskListButton_UpdateButtonPadding_Original(pThis); } + + // This function is called during layout updates - call our UpdateVisualStates hook TaskListButton_UpdateVisualStates_Hook(pThis); } +// Hook UpdateBadgeSize which is also called during updates void WINAPI TaskListButton_UpdateBadgeSize_Hook(void* pThis) { if (TaskListButton_UpdateBadgeSize_Original) { TaskListButton_UpdateBadgeSize_Original(pThis); } + + // Also trigger overlay creation here TaskListButton_UpdateVisualStates_Hook(pThis); } bool HookTaskbarViewDllSymbols(HMODULE module) { - // Taskbar.View.dll, ExplorerExtensions.dll WindhawkUtils::SYMBOL_HOOK symbolHooks[] = { { {LR"(private: void __cdecl winrt::Taskbar::implementation::TaskListButton::UpdateVisualStates(void))"}, @@ -773,10 +779,10 @@ HMODULE WINAPI LoadLibraryExW_Hook(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dw void LoadSettings() { PCWSTR position = Wh_GetStringSetting(L"numberPosition"); - g_settings.numberPosition = NumberPosition::BottomRight; - if (wcscmp(position, L"topLeft") == 0) g_settings.numberPosition = NumberPosition::TopLeft; - else if (wcscmp(position, L"topRight") == 0) g_settings.numberPosition = NumberPosition::TopRight; - else if (wcscmp(position, L"bottomLeft") == 0) g_settings.numberPosition = NumberPosition::BottomLeft; + g_settings.numberPosition = NumberPosition::bottomRight; + if (wcscmp(position, L"topLeft") == 0) g_settings.numberPosition = NumberPosition::topLeft; + else if (wcscmp(position, L"topRight") == 0) g_settings.numberPosition = NumberPosition::topRight; + else if (wcscmp(position, L"bottomLeft") == 0) g_settings.numberPosition = NumberPosition::bottomLeft; Wh_FreeStringSetting(position); g_settings.numberSize = Wh_GetIntSetting(L"numberSize"); From f0af466a4d445fd336b3b7bf12be112e49409437 Mon Sep 17 00:00:00 2001 From: Jesus Fernandez Date: Mon, 18 Aug 2025 11:11:02 +0200 Subject: [PATCH 12/13] Update taskbar-icon-numberer.wh.cpp --- mods/taskbar-icon-numberer.wh.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/mods/taskbar-icon-numberer.wh.cpp b/mods/taskbar-icon-numberer.wh.cpp index 862d45c20..3cc55f304 100644 --- a/mods/taskbar-icon-numberer.wh.cpp +++ b/mods/taskbar-icon-numberer.wh.cpp @@ -742,6 +742,7 @@ void WINAPI TaskListButton_UpdateBadgeSize_Hook(void* pThis) { bool HookTaskbarViewDllSymbols(HMODULE module) { + // Taskbar.View.dll, ExplorerExtensions.dll WindhawkUtils::SYMBOL_HOOK symbolHooks[] = { { {LR"(private: void __cdecl winrt::Taskbar::implementation::TaskListButton::UpdateVisualStates(void))"}, From 648c224a4d6ad642a583d64a17f027f8dee0da85 Mon Sep 17 00:00:00 2001 From: Jesus Fernandez Date: Sun, 7 Sep 2025 23:16:58 +0200 Subject: [PATCH 13/13] Update taskbar-icon-numberer.wh.cpp --- mods/taskbar-icon-numberer.wh.cpp | 373 ++++++++++++------------------ 1 file changed, 148 insertions(+), 225 deletions(-) diff --git a/mods/taskbar-icon-numberer.wh.cpp b/mods/taskbar-icon-numberer.wh.cpp index 3cc55f304..c55fc445c 100644 --- a/mods/taskbar-icon-numberer.wh.cpp +++ b/mods/taskbar-icon-numberer.wh.cpp @@ -8,17 +8,14 @@ // @homepage https://tightcorner.substack.com/p/reverse-engineering-windows-11s-taskbar // @include explorer.exe // @architecture x86-64 -// @compilerOptions -DWINVER=0x0A00 -lole32 -loleaut32 -lruntimeobject -Wextra -lcomctl32 +// @compilerOptions -DWINVER=0x0A00 -lole32 -loleaut32 -lruntimeobject // ==/WindhawkMod== // ==WindhawkModReadme== /* # Taskbar Icon Numberer for Windows 11 - Displays keyboard shortcut numbers (1-9, 0) as overlays on Windows 11 taskbar icons, replicating the functionality of the classic 7+ Taskbar Numberer tool. - ![](https://raw.githubusercontent.com/jsfdez/images/main/taskbar-icon-numberer.wh.cpp.png) - ## Features - Numbers 1-9 for first nine apps, 0 for tenth app - Fully customizable appearance (position, size, colors) @@ -26,14 +23,11 @@ Displays keyboard shortcut numbers (1-9, 0) as overlays on Windows 11 taskbar ic - Supports light and dark themes - Hidden overlays for apps beyond position 10 - Efficient overlay management with minimal performance impact - ## Usage Numbers correspond to Windows + [number] keyboard shortcuts for quick app launching. Simply press Win+1 to open the first app, Win+2 for second, etc. - ## Known Issues - Numbers don't appear until first taskbar interaction (hover over any button) - Occasional wrong numbers during initial display (self-corrects after reordering) - ## Customization Configure number position (corners), font size (8-16px), text color, and stroke color through mod settings. */ @@ -78,7 +72,6 @@ Configure number position (corners), font size (8-16px), text color, and stroke #include #include #include -#include #include #include #include @@ -97,17 +90,11 @@ using RunFromWindowThreadProc_t = void(WINAPI*)(void* parameter); using TaskListButton_UpdateVisualStates_t = void(WINAPI*)(void* pThis); TaskListButton_UpdateVisualStates_t TaskListButton_UpdateVisualStates_Original; -using TaskListButton_UpdateButtonPadding_t = void(WINAPI*)(void* pThis); -TaskListButton_UpdateButtonPadding_t TaskListButton_UpdateButtonPadding_Original; - -using TaskListButton_UpdateBadgeSize_t = void(WINAPI*)(void* pThis); -TaskListButton_UpdateBadgeSize_t TaskListButton_UpdateBadgeSize_Original; - enum class NumberPosition { topLeft, topRight, bottomLeft, - bottomRight + bottomRight, }; struct { @@ -123,14 +110,30 @@ struct ButtonInformation { FrameworkElement overlay = nullptr; int currentNumber = -1; bool isVisible = false; - bool operator<(const ButtonInformation& other) const { return winrt::get_abi(button) < winrt::get_abi(other.button); } + + // Constructor for easier creation + explicit ButtonInformation(const FrameworkElement& button = nullptr) : button(button) {} + + bool isSameButton(const FrameworkElement& otherButton) const { + auto thisButton = button.get(); + if (!thisButton) return false; + return winrt::get_abi(thisButton.as()) == winrt::get_abi(otherButton.as()); + } }; -std::atomic g_taskbarViewDllLoaded; -std::atomic g_unloading; -std::atomic g_initialTime; +// Simplified globals +std::atomic g_taskbarViewDllLoaded{false}; +std::atomic g_unloading{false}; std::mutex g_overlayMutex; -std::set g_trackedButtons; +std::vector g_trackedButtons; + +// Helper function to find button in vector +auto FindButtonInVector = [](const std::vector& buttons, const FrameworkElement& targetButton) { + return std::find_if(buttons.begin(), buttons.end(), + [&targetButton](const ButtonInformation& info) { + return info.isSameButton(targetButton); + }); +}; // Utility functions void LogException(const char* functionName) { @@ -287,24 +290,30 @@ bool RunFromWindowThread(HWND hWnd, RunFromWindowThreadProc_t proc, void* procPa return true; } +// Simplified text creation helper +TextBlock CreateTextBlock(const std::wstring& text, double fontSize, const winrt::Windows::UI::Color& color, const Thickness& margin = {}) { + TextBlock textBlock; + textBlock.Text(text); + textBlock.FontSize(fontSize); + textBlock.Margin(margin); + + auto brush = SolidColorBrush(); + brush.Color(color); + textBlock.Foreground(brush); + + return textBlock; +} + FrameworkElement CreateNumberOverlay(int number) { + if (number < 1) { + Wh_Log(L"CreateNumberOverlay: Invalid number %d, skipping creation", number); + return nullptr; + } + try { - // Validate number range to prevent invalid numbers like 11 - if (number < 1) { - Wh_Log(L"CreateNumberOverlay: Invalid number %d, skipping creation", number); - return nullptr; - } - - TextBlock numberText; - numberText.Text(number == 10 ? L"0" : std::to_wstring(number)); - numberText.FontSize(g_settings.numberSize); - - Wh_Log(L"CreateNumberOverlay: Creating overlay with number %d", number); - + std::wstring text = (number == 10) ? L"0" : std::to_wstring(number); auto textColor = ParseHexColor(g_settings.numberColor); - auto brush = SolidColorBrush(); - brush.Color(textColor); - numberText.Foreground(brush); + auto strokeColor = ParseHexColor(g_settings.backgroundColor); Grid textContainer; textContainer.Name(L"WindhawkNumberOverlay"); @@ -314,23 +323,17 @@ FrameworkElement CreateNumberOverlay(int number) { for (int dy = -1; dy <= 1; dy++) { if (dx == 0 && dy == 0) continue; - TextBlock strokeText; - strokeText.Text(number == 10 ? L"0" : std::to_wstring(number)); - strokeText.FontSize(g_settings.numberSize); - strokeText.Margin(Thickness{static_cast(dx), static_cast(dy), 0, 0}); - - auto strokeColor = ParseHexColor(g_settings.backgroundColor); - auto strokeBrush = SolidColorBrush(); - strokeBrush.Color(strokeColor); - strokeText.Foreground(strokeBrush); - + auto strokeText = CreateTextBlock(text, g_settings.numberSize, strokeColor, + Thickness{static_cast(dx), static_cast(dy), 0, 0}); textContainer.Children().Append(strokeText); } } + // Main text + auto numberText = CreateTextBlock(text, g_settings.numberSize, textColor); textContainer.Children().Append(numberText); - // Position based on settings + // Set position and properties switch (g_settings.numberPosition) { case NumberPosition::topLeft: textContainer.HorizontalAlignment(HorizontalAlignment::Left); @@ -349,13 +352,15 @@ FrameworkElement CreateNumberOverlay(int number) { textContainer.VerticalAlignment(VerticalAlignment::Bottom); break; } - textContainer.Margin(Thickness{0, 0, 2, 2}); + textContainer.Margin(Thickness{0, 0, 2, 2}); Canvas::SetZIndex(textContainer, 1000); + if (number > 10) { textContainer.Visibility(Visibility::Collapsed); } + Wh_Log(L"CreateNumberOverlay: Created overlay with number %d", number); return textContainer; } catch (...) { @@ -364,6 +369,29 @@ FrameworkElement CreateNumberOverlay(int number) { } } +// Simplified button cleanup +void CleanupButtonOverlays(const ButtonInformation& buttonInfo) { + try { + auto button = buttonInfo.button.get(); + if (!button) return; + + auto iconPanel = FindChildByName(button, L"IconPanel"); + if (!iconPanel) return; + + auto panelChildren = iconPanel.as().Children(); + for (uint32_t j = 0; j < panelChildren.Size();) { + auto child = panelChildren.GetAt(j).try_as(); + if (child && child.Name() == L"WindhawkNumberOverlay") { + panelChildren.RemoveAt(j); + } else { + j++; + } + } + } catch (...) { + LogException(__FUNCTION__); + } +} + void RemoveAllNumberOverlays() { try { HWND taskbarWnd = FindCurrentProcessTaskbarWnd(); @@ -373,30 +401,9 @@ void RemoveAllNumberOverlays() { try { std::lock_guard lock(g_overlayMutex); for (const auto& buttonInfo : g_trackedButtons) { - try { - auto button = buttonInfo.button; - if (button) { - auto iconPanel = FindChildByName(button.get(), L"IconPanel"); - if (iconPanel) { - // Remove ALL overlays with our name - auto panelChildren = iconPanel.as().Children(); - for (uint32_t j = 0; j < panelChildren.Size();) { - auto child = panelChildren.GetAt(j).try_as(); - if (child && child.Name() == L"WindhawkNumberOverlay") { - panelChildren.RemoveAt(j); - } else { - j++; - } - } - } - } - } catch (...) { - LogException(__FUNCTION__); - } + CleanupButtonOverlays(buttonInfo); } - g_trackedButtons.clear(); - } catch (...) { LogException(__FUNCTION__); } @@ -406,54 +413,41 @@ void RemoveAllNumberOverlays() { } } +// Simplified overlay update for settings changes +void UpdateOverlayAppearance(Grid& grid, const std::wstring& text) { + auto children = grid.Children(); + children.Clear(); + + auto textColor = ParseHexColor(g_settings.numberColor); + auto strokeColor = ParseHexColor(g_settings.backgroundColor); + + // Create stroke effect + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + if (dx == 0 && dy == 0) continue; + + auto strokeText = CreateTextBlock(text, g_settings.numberSize, strokeColor, + Thickness{static_cast(dx), static_cast(dy), 0, 0}); + children.Append(strokeText); + } + } + + // Main text + auto numberText = CreateTextBlock(text, g_settings.numberSize, textColor); + children.Append(numberText); +} + void UpdateOverlayWithNewSettings(const ButtonInformation& buttonInformation) { if (!buttonInformation.overlay) { - Wh_Log(L"UpdateOverlayWithNewSettings: No overlay to update for button %p", &buttonInformation.button); + Wh_Log(L"UpdateOverlayWithNewSettings: No overlay to update"); return; } - Wh_Log(L"UpdateOverlayWithNewSettings: Updating overlay for button %p, number %d", &buttonInformation.button, buttonInformation.currentNumber); - try { auto grid = buttonInformation.overlay.as(); - auto children = grid.Children(); std::wstring text = (buttonInformation.currentNumber == 10) ? L"0" : std::to_wstring(buttonInformation.currentNumber); - // Clear existing children - children.Clear(); - - // Recreate with new settings - auto textColor = ParseHexColor(g_settings.numberColor); - auto strokeColor = ParseHexColor(g_settings.backgroundColor); - - // Create stroke effect - for (int dx = -1; dx <= 1; dx++) { - for (int dy = -1; dy <= 1; dy++) { - if (dx == 0 && dy == 0) continue; - - TextBlock strokeText; - strokeText.Text(text); - strokeText.FontSize(g_settings.numberSize); - strokeText.Margin(Thickness{static_cast(dx), static_cast(dy), 0, 0}); - - auto strokeBrush = SolidColorBrush(); - strokeBrush.Color(strokeColor); - strokeText.Foreground(strokeBrush); - - children.Append(strokeText); - } - } - - // Main text - TextBlock numberText; - numberText.Text(text); - numberText.FontSize(g_settings.numberSize); - - auto brush = SolidColorBrush(); - brush.Color(textColor); - numberText.Foreground(brush); - - children.Append(numberText); + UpdateOverlayAppearance(grid, text); // Update position switch (g_settings.numberPosition) { @@ -475,27 +469,41 @@ void UpdateOverlayWithNewSettings(const ButtonInformation& buttonInformation) { break; } - Wh_Log(L"UpdateOverlayWithNewSettings: Successfully updated overlay for button %p", &buttonInformation.button); + Wh_Log(L"UpdateOverlayWithNewSettings: Successfully updated overlay"); } catch (...) { - Wh_Log(L"UpdateOverlayWithNewSettings: Failed to update overlay for button %p", &buttonInformation.button); + Wh_Log(L"UpdateOverlayWithNewSettings: Failed to update overlay"); LogException(__FUNCTION__); } } +void RemoveExistingOverlays(FrameworkElement iconPanel) { + auto panelChildren = iconPanel.as().Children(); + uint32_t removedCount = 0; + for (uint32_t j = 0; j < panelChildren.Size();) { + auto child = panelChildren.GetAt(j).try_as(); + if (child && child.Name() == L"WindhawkNumberOverlay") { + panelChildren.RemoveAt(j); + removedCount++; + } else { + j++; + } + } + if (removedCount > 0) { + Wh_Log(L"RemoveExistingOverlays: Removed %d old overlays", removedCount); + } +} + void UpdateButtonOverlay(FrameworkElement taskListButtonElement, int number, void* buttonPointer) { - if (!taskListButtonElement || g_unloading) return; - - // Validate number range immediately to prevent invalid overlays - if (number < 1) { - Wh_Log(L"UpdateButtonOverlay: Invalid number %d for button %p, skipping", number, buttonPointer); + if (!taskListButtonElement || g_unloading || number < 1) { + if (number < 1) Wh_Log(L"UpdateButtonOverlay: Invalid number %d, skipping", number); return; } try { auto iconPanelElement = FindChildByName(taskListButtonElement, L"IconPanel"); if (!iconPanelElement) { - Wh_Log(L"UpdateButtonOverlay: No IconPanel found for button %p", buttonPointer); + Wh_Log(L"UpdateButtonOverlay: No IconPanel found"); return; } @@ -503,109 +511,53 @@ void UpdateButtonOverlay(FrameworkElement taskListButtonElement, int number, voi winrt::Windows::Foundation::IUnknown buttonIUnknown; winrt::copy_from_abi(buttonIUnknown, buttonPointer); - auto button = buttonIUnknown.try_as(); + auto button = buttonIUnknown.as(); - auto it = g_trackedButtons.find(ButtonInformation(button)); + auto it = FindButtonInVector(g_trackedButtons, button); ButtonInformation buttonInfo; + if (it != g_trackedButtons.end()) { buttonInfo = *it; g_trackedButtons.erase(it); - Wh_Log(L"UpdateButtonOverlay: Found existing button %p, current number %d, new number %d", - buttonPointer, buttonInfo.currentNumber, number); + Wh_Log(L"UpdateButtonOverlay: Found existing button, current number %d, new number %d", + buttonInfo.currentNumber, number); } else { buttonInfo = ButtonInformation(button); - Wh_Log(L"UpdateButtonOverlay: New button %p, number %d", buttonPointer, number); + Wh_Log(L"UpdateButtonOverlay: New button, number %d", number); } const bool shouldShow = number <= 10; const bool visibilityChanged = buttonInfo.isVisible != shouldShow; const bool numberChanged = buttonInfo.currentNumber != number; - bool needsNewOverlay = !buttonInfo.overlay; + const bool needsNewOverlay = !buttonInfo.overlay || (numberChanged && buttonInfo.overlay); - Wh_Log(L"UpdateButtonOverlay: Button %p - number=%d, shouldShow=%d, visibilityChanged=%d, numberChanged=%d, needsNewOverlay=%d", buttonPointer, number, shouldShow, visibilityChanged, numberChanged, needsNewOverlay); - - // Always update if number changed, even if it was > 10 before if (!visibilityChanged && !numberChanged && !needsNewOverlay) { - Wh_Log(L"UpdateButtonOverlay: No changes needed for button %p", buttonPointer); - g_trackedButtons.insert(buttonInfo); + g_trackedButtons.push_back(buttonInfo); return; } - // Force recreation for buttons that moved positions significantly - // This ensures reordering updates work correctly - if (numberChanged && !needsNewOverlay && buttonInfo.overlay) { - Wh_Log(L"UpdateButtonOverlay: Position change detected for button %p (%d -> %d), forcing recreation", buttonPointer, buttonInfo.currentNumber, number); - needsNewOverlay = true; - } - - // Update existing overlay if only number changed - if (buttonInfo.overlay && !needsNewOverlay && !visibilityChanged && numberChanged) { - Wh_Log(L"UpdateButtonOverlay: Updating text for button %p from %d to %d", - buttonPointer, buttonInfo.currentNumber, number); - try { - auto grid = buttonInfo.overlay.as(); - auto children = grid.Children(); - std::wstring newText = (number == 10) ? L"0" : std::to_wstring(number); - - // Update all TextBlock children (stroke + main text) - for (uint32_t i = 0; i < children.Size(); i++) { - auto textBlock = children.GetAt(i).try_as(); - if (textBlock) { - textBlock.Text(newText); - } - } - Wh_Log(L"UpdateButtonOverlay: Successfully updated text for button %p", buttonPointer); - } catch (...) { - Wh_Log(L"UpdateButtonOverlay: Failed to update text for button %p, will recreate overlay", buttonPointer); - needsNewOverlay = true; - } - } - // Create new overlay if needed if (needsNewOverlay) { - Wh_Log(L"UpdateButtonOverlay: Creating new overlay for button %p, number %d", buttonPointer, number); - - // Only remove ALL overlays if we're creating a new one (to handle position changes) - auto panelChildren = iconPanelElement.as().Children(); - uint32_t removedCount = 0; - for (uint32_t j = 0; j < panelChildren.Size();) { - auto child = panelChildren.GetAt(j).try_as(); - if (child && child.Name() == L"WindhawkNumberOverlay") { - panelChildren.RemoveAt(j); - removedCount++; - } else { - j++; - } - } - if (removedCount > 0) { - Wh_Log(L"UpdateButtonOverlay: Removed %d old overlays from button %p", removedCount, buttonPointer); - } + RemoveExistingOverlays(iconPanelElement); buttonInfo.overlay = nullptr; auto newOverlay = CreateNumberOverlay(number); if (newOverlay) { iconPanelElement.as().Children().Append(newOverlay); buttonInfo.overlay = newOverlay; - Wh_Log(L"UpdateButtonOverlay: Successfully created new overlay for button %p", buttonPointer); - } else { - Wh_Log(L"UpdateButtonOverlay: Failed to create overlay for button %p", buttonPointer); } } // Update visibility if (buttonInfo.overlay && visibilityChanged) { - auto newVisibility = shouldShow ? Visibility::Visible : Visibility::Collapsed; - buttonInfo.overlay.Visibility(newVisibility); - Wh_Log(L"UpdateButtonOverlay: Changed visibility for button %p to %s", buttonPointer, shouldShow ? L"Visible" : L"Collapsed"); + buttonInfo.overlay.Visibility(shouldShow ? Visibility::Visible : Visibility::Collapsed); } buttonInfo.currentNumber = number; buttonInfo.isVisible = shouldShow; - - g_trackedButtons.insert(buttonInfo); + g_trackedButtons.push_back(buttonInfo); } catch (...) { - Wh_Log(L"UpdateButtonOverlay: Exception occurred for button %p", buttonPointer); LogException(__FUNCTION__); } } @@ -638,14 +590,14 @@ void UpdateAllTaskbarNumbers(FrameworkElement taskbarRepeater) { buttons.push_back({x, child}); } - // Skip processing if no buttons or during initial setup if (buttons.empty()) { Wh_Log(L"UpdateAllTaskbarNumbers: No valid buttons found, skipping"); return; } - // Sort by X position (left to right) - std::sort(buttons.begin(), buttons.end(), [](const auto& a, const auto& b) { return a.first < b.first; }); + std::sort(buttons.begin(), buttons.end(), [](const auto& a, const auto& b) { + return a.first < b.first; + }); Wh_Log(L"UpdateAllTaskbarNumbers: Found %zu taskbar buttons", buttons.size()); @@ -660,29 +612,31 @@ void UpdateAllTaskbarNumbers(FrameworkElement taskbarRepeater) { auto it = g_trackedButtons.begin(); size_t removedCount = 0; while (it != g_trackedButtons.end()) { - if (currentButtons.find(it->button.get()) == currentButtons.end()) { + auto button = it->button.get(); + if (!button || currentButtons.find(button) == currentButtons.end()) { it = g_trackedButtons.erase(it); removedCount++; } else { ++it; } } - if (removedCount > 0) Wh_Log(L"UpdateAllTaskbarNumbers: Removed %zu buttons no longer present", removedCount); + if (removedCount > 0) { + Wh_Log(L"UpdateAllTaskbarNumbers: Removed %zu buttons no longer present", removedCount); + } } - // Update overlays for all buttons - ensure correct numbering for (size_t i = 0; i < buttons.size(); i++) { auto button = buttons[i].second; void* buttonPointer = winrt::get_abi(button.as()); int buttonNumber = static_cast(i + 1); - // Validate button number is in expected range if (buttonNumber < 1 || buttonNumber > static_cast(buttons.size())) { Wh_Log(L"UpdateAllTaskbarNumbers: Invalid button number %d for button %zu", buttonNumber, i); continue; } - Wh_Log(L"UpdateAllTaskbarNumbers: Button %zu at position %.1f gets number %d", i, buttons[i].first, buttonNumber); + Wh_Log(L"UpdateAllTaskbarNumbers: Button %zu at position %.1f gets number %d", + i, buttons[i].first, buttonNumber); UpdateButtonOverlay(button, buttonNumber, buttonPointer); } @@ -720,27 +674,6 @@ void WINAPI TaskListButton_UpdateVisualStates_Hook(void* pThis) { } } -// Also hook UpdateButtonPadding which is called during layout updates -void WINAPI TaskListButton_UpdateButtonPadding_Hook(void* pThis) { - if (TaskListButton_UpdateButtonPadding_Original) { - TaskListButton_UpdateButtonPadding_Original(pThis); - } - - // This function is called during layout updates - call our UpdateVisualStates hook - TaskListButton_UpdateVisualStates_Hook(pThis); -} - -// Hook UpdateBadgeSize which is also called during updates -void WINAPI TaskListButton_UpdateBadgeSize_Hook(void* pThis) { - if (TaskListButton_UpdateBadgeSize_Original) { - TaskListButton_UpdateBadgeSize_Original(pThis); - } - - // Also trigger overlay creation here - TaskListButton_UpdateVisualStates_Hook(pThis); -} - - bool HookTaskbarViewDllSymbols(HMODULE module) { // Taskbar.View.dll, ExplorerExtensions.dll WindhawkUtils::SYMBOL_HOOK symbolHooks[] = { @@ -749,16 +682,6 @@ bool HookTaskbarViewDllSymbols(HMODULE module) { &TaskListButton_UpdateVisualStates_Original, TaskListButton_UpdateVisualStates_Hook, }, - { - {LR"(private: void __cdecl winrt::Taskbar::implementation::TaskListButton::UpdateButtonPadding(void))"}, - &TaskListButton_UpdateButtonPadding_Original, - TaskListButton_UpdateButtonPadding_Hook, - }, - { - {LR"(private: void __cdecl winrt::Taskbar::implementation::TaskListButton::UpdateBadgeSize(void))"}, - &TaskListButton_UpdateBadgeSize_Original, - TaskListButton_UpdateBadgeSize_Hook, - }, }; return HookSymbols(module, symbolHooks, ARRAYSIZE(symbolHooks)); @@ -800,7 +723,6 @@ void LoadSettings() { } BOOL Wh_ModInit() { - g_initialTime = GetTickCount(); LoadSettings(); if (HMODULE taskbarViewModule = GetTaskbarViewModuleHandle()) { @@ -841,8 +763,9 @@ void Wh_ModSettingsChanged() { Wh_Log(L"Updating %zu tracked buttons with new settings", g_trackedButtons.size()); - // Update existing overlays with new settings - for (const auto& buttonInfo : g_trackedButtons) UpdateOverlayWithNewSettings(buttonInfo); + for (const auto& buttonInfo : g_trackedButtons) { + UpdateOverlayWithNewSettings(buttonInfo); + } }, nullptr); } }