Reverse Engineering Windows 11's Taskbar: Custom Number Overlays
Restoring 7+ Taskbar Numberer Functionality on Modern Windows
The 7+ Taskbar Numberer tool provided visual indicators for keyboard shortcuts (Win + 1, Win + 2, etc.) on Windows taskbars until Windows 11's architectural changes broke compatibility. This post details a Windhawk mod that restores this functionality through runtime hooking of the new XAML-based taskbar implementation.
Windhawk Platform Overview
Windhawk is a Windows customization framework that uses runtime hooking instead of file patching. Key advantages:
Runtime modifications: No permanent system file changes
Update resilience: Modifications survive Windows updates
Modular architecture: Individual mods can be enabled/disabled
Community repository: Centralized mod distribution and updates
Installation
Download from windhawk.net
Install and run the application
Navigate to "Mods" → Search "Taskbar Icon Numberer" → Install
Technical Architecture Analysis
Windows 11 Taskbar Structure
Windows 11 replaced the legacy taskbar with a WinRT/XAML implementation residing in Taskbar.View.dll and ExplorerExtensions.dll. The key architectural components:
TaskbarFrame
└── RootGrid/TaskbarFrameBorder
└── TaskbarFrameRepeater (ItemsRepeater)
└── TaskListButton elements
└── IconPanel (Grid)
├── Icon (Image)
├── OverlayIcon/BadgeControl
├── RunningIndicator
└── ProgressIndicatorSymbol Hooking Implementation
The mod hooks TaskListButton::UpdateVisualStates() using symbol-based function interception:
WindhawkUtils::SYMBOL_HOOK symbolHooks[] = {
{
{LR"(private: void __cdecl winrt::Taskbar::implementation::TaskListButton::UpdateVisualStates(void))"},
&TaskListButton_UpdateVisualStates_Original,
TaskListButton_UpdateVisualStates_Hook,
},
};This function is called during:
Button state changes (hover, pressed, focused)
Visual theme updates
Layout recalculations
Running state modifications
Visual Tree Navigation
The mod traverses the XAML visual tree using VisualTreeHelper APIs:
FrameworkElement FindChildByName(FrameworkElement element, PCWSTR name) {
int childrenCount = Media::VisualTreeHelper::GetChildrenCount(element);
for (int i = 0; i < childrenCount; i++) {
auto child = Media::VisualTreeHelper::GetChild(element, i)
.try_as<FrameworkElement>();
if (child && child.Name() == name) {
return child;
}
}
return nullptr;
}ItemsRepeater Interface
Windows 11 uses ItemsRepeater for taskbar button virtualization. The mod accesses this through COM interface casting:
constexpr winrt::guid IItemsRepeater{
0x0BD894F2, 0xEDFC, 0x5DDF,
{0xA1, 0x66, 0x2D, 0xB1, 0x4B, 0xBF, 0xDF, 0x35}
};
winrt::Windows::Foundation::IUnknown pThis = nullptr;
taskbarFrameRepeaterElement.as(IItemsRepeater, winrt::put_abi(pThis));
void** vtable = *(void***)winrt::get_abi(pThis);
auto GetElementIndex = (GetElementIndex_t)vtable[19];Spatial Ordering Algorithm
Taskbar buttons in the DOM don't necessarily match visual order. The mod implements position-based sorting:
std::vector<std::pair<double, FrameworkElement>> buttons;
for (uint32_t i = 0; i < children.Size(); i++) {
auto child = children.GetAt(i).try_as<FrameworkElement>();
if (child && child.Name() == L"TaskListButton") {
double x = child.ActualOffset().x; // Physical X coordinate
buttons.push_back({x, child});
}
}
std::sort(buttons.begin(), buttons.end(),
[](const auto& a, const auto& b) { return a.first < b.first; });Overlay Rendering Implementation
Stroke Effect Algorithm
XAML TextBlock lacks native stroke support. The mod implements stroke rendering using multiple overlaid text elements:
Grid textContainer;
textContainer.Name(L"WindhawkNumberOverlay");
// Generate 8-directional stroke
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
if (dx == 0 && dy == 0) continue; // Skip center position
TextBlock strokeText;
strokeText.Text(numberString);
strokeText.FontSize(g_settings.numberSize);
strokeText.Margin(Thickness{
static_cast<double>(dx),
static_cast<double>(dy), 0, 0
});
auto strokeBrush = SolidColorBrush();
strokeBrush.Color(ParseHexColor(g_settings.backgroundColor));
strokeText.Foreground(strokeBrush);
textContainer.Children().Append(strokeText);
}
}
// Add main text on top
textContainer.Children().Append(mainText);Color Parsing System
The mod supports both RGB and ARGB hex formats:
winrt::Windows::UI::Color ParseHexColor(const std::wstring& hex) {
winrt::Windows::UI::Color color{};
if (hex.length() >= 7 && hex[0] == L'#') {
if (hex.length() == 9) { // #AARRGGBB
color.A = static_cast<uint8_t>(
std::wcstoul(hex.substr(1, 2).c_str(), nullptr, 16));
color.R = static_cast<uint8_t>(
std::wcstoul(hex.substr(3, 2).c_str(), nullptr, 16));
color.G = static_cast<uint8_t>(
std::wcstoul(hex.substr(5, 2).c_str(), nullptr, 16));
color.B = static_cast<uint8_t>(
std::wcstoul(hex.substr(7, 2).c_str(), nullptr, 16));
} else { // #RRGGBB
color.A = 255;
color.R = static_cast<uint8_t>(
std::wcstoul(hex.substr(1, 2).c_str(), nullptr, 16));
// ... G, B parsing
}
}
return color;
}Thread Safety and State Management
Synchronization Strategy
Windows UI operations occur across multiple threads. The mod uses mutex-protected state management:
std::mutex g_overlayMutex;
std::unordered_set<void*> g_numberedButtons;
void CreateNumberOverlay(FrameworkElement button, int number) {
// ... overlay creation logic
std::lock_guard<std::mutex> lock(g_overlayMutex);
iconPanel.as<Panel>().Children().Append(textContainer);
g_numberedButtons.insert(buttonPtr);
}Memory Management
The mod tracks button pointers to prevent duplicate overlays and ensure proper cleanup:
void* buttonPtr = winrt::get_abi(
taskListButtonElement.as<winrt::Windows::Foundation::IUnknown>());
// Check for existing overlay before creation
auto existingOverlay = FindChildByName(iconPanel, L"WindhawkNumberOverlay");
if (existingOverlay) {
// Remove and recreate to ensure consistency
}WinRT Integration Challenges
COM Interface Marshaling
The mod must navigate between native Win32 hooks and WinRT objects:
void* taskListButtonIUnknownPtr = (void**)pThis + 3; // Object offset
winrt::Windows::Foundation::IUnknown taskListButtonIUnknown;
winrt::copy_from_abi(taskListButtonIUnknown, taskListButtonIUnknownPtr);
auto taskListButtonElement = taskListButtonIUnknown.as<FrameworkElement>();Exception Handling
WinRT operations can throw exceptions that would crash the host process. The mod uses catch-all blocks to prevent crashes:
try {
// WinRT operations
} catch (...) {
// Generic catch-all to prevent crashes
// Does not provide specific error handling
}Performance Considerations
Update Batching
Instead of individual button updates, the mod processes all buttons simultaneously to minimize UI thread impact:
void UpdateAllTaskbarNumbers(FrameworkElement taskbarRepeater) {
// Single pass through all buttons
// Reduces layout thrashing and improves consistency
}Z-Index Management
Overlays use high Z-index values to ensure visibility above other taskbar elements:
Canvas::SetZIndex(textContainer, 1000);Configuration Architecture
The mod exposes settings through Windhawk's configuration system:
Position: 4 corner placement options
Font Size: 8-16 pixel range with bounds checking
Colors: Hex color parsing with fallback defaults
Behavior: Running app filtering and maximum count limits
Settings changes trigger overlay recreation through the Wh_ModSettingsChanged() callback.
Debugging and Development
Development involved extensive use of:
Symbol analysis: Using tools like
dumpbinand IDA to identify hook pointsXAML inspection: Visual Studio's Live Visual Tree for structure analysis
Logging: Strategic
Wh_Log()placement for state trackingMemory debugging: Ensuring proper COM reference counting
This implementation demonstrates advanced Windows internals manipulation while maintaining system stability through careful error handling and resource management.

