Skip to content

Add Mod: Windows 11 Better Ultrawide Taskbar#2803

Open
roeseth wants to merge 4 commits intoramensoftware:mainfrom
roeseth:main
Open

Add Mod: Windows 11 Better Ultrawide Taskbar#2803
roeseth wants to merge 4 commits intoramensoftware:mainfrom
roeseth:main

Conversation

@roeseth
Copy link
Copy Markdown

@roeseth roeseth commented Dec 15, 2025

Add a mod to center taskbar and system tray as a single unit to provide better usability in ultrawide screen.
Also offer an offset only mode to just offset the system tray.

@m417z
Copy link
Copy Markdown
Member

m417z commented Dec 15, 2025

Thanks for the submission. Here's how it looks on my computer with the default settings, I assume the edges shouldn't look like this:

image

Also, you're using XAML Diagnostics (InitializeXamlDiagnosticsEx), which makes the mod incompatible with other mods or customization tools that use it. Specifically, it makes the mod incompatible with Windows 11 Taskbar Styler.

You can keep it as is, in which case I think it'd be a good idea to add a notice about it in the readme. Alternatively, you might want to update the implementation such that XAML Diagnostics API isn't used. There are several mod examples which can serve as an example, for example Start button always on the left:

XamlRoot xamlRoot = nullptr;
if (_wcsicmp(szClassName, L"Shell_TrayWnd") == 0) {
xamlRoot = GetTaskbarXamlRoot(hWnd);
} else if (_wcsicmp(szClassName, L"Shell_SecondaryTrayWnd") == 0) {
xamlRoot = GetSecondaryTaskbarXamlRoot(hWnd);
} else {
return TRUE;
}
if (!xamlRoot) {
Wh_Log(L"Getting XamlRoot failed");
return TRUE;
}
if (!ApplyStyle(xamlRoot)) {
Wh_Log(L"ApplyStyles failed");
return TRUE;
}

@roeseth
Copy link
Copy Markdown
Author

roeseth commented Dec 15, 2025

thank you, yea I'll take a look at other examples. I want to minimize the conflict with other mods, but I'm pretty new to the modding here, do you mind helping me understand some other potential conflicts or preferred method that I should look into?

@m417z
Copy link
Copy Markdown
Member

m417z commented Dec 15, 2025

XAML Diagnostics API is a unique conflict, as only one consumer can use it due to the way it works. The API was designed for debugging, and has several shortcomings. No other conflicts are expected unless you do something specific.

preferred method that I should look into?

A good starting point is to take a mod which uses GetTaskbarXamlRoot, strip all the pieces that are specific to that mod, and see if what's left works for you, e.g. a function that is called to apply/undo taskbar customizations.

I provided taskbar-start-button-position as an example. Perhaps taskbar-multirow is a better example since it's a smaller mod.

@roeseth
Copy link
Copy Markdown
Author

roeseth commented Dec 22, 2025

Updated to use GetTaskbarXamlRoot instead of InitializeXamlDiagnosticsEx

{
try
{
taskbarFrame.LayoutUpdated(g_layoutUpdatedToken);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be run from the taskbar (UI) thread.

Something like:

HWND hTaskbarWnd = FindCurrentProcessTaskbarWnd();
if (hTaskbarWnd)
{
    RunFromWindowThread(
        hTaskbarWnd, [](void *pParam)
        { /* cleanup code here */ }, 0);
}

Also generally, I suggest to at least print errors instead of having empty catch blocks.

@TheGamer1445891
Copy link
Copy Markdown

@roeseth is the mod incompability fixed? (i really need this mod (even tho my screen isnt ultrawide))

@TheGamer1445891
Copy link
Copy Markdown

@roeseth i found a bug!
when you change the position of the tasklist buttons, the system tray glitches and is now blocking the taskbar! (only happens if the system tray is flipped (using taskbar styler mod))

Screen.Recording.2026-02-21.112629.mp4

@TheGamer1445891
Copy link
Copy Markdown

@roeseth wait, i its always like that (even when no flipped)

@roeseth
Copy link
Copy Markdown
Author

roeseth commented Feb 21, 2026

Hi @TheGamer1445891
Yea I just freshly re-installed a new 24H2 which broke my mod so I was working on fixing other compatibility issues by rewriting the code with some more features. Could you tell me what taskbar styler settings are you using and the exact step I should do to reproduce the issue?

@TheGamer1445891
Copy link
Copy Markdown

Hi @TheGamer1445891, Yea I just freshly re-installed a new 24H2 which broke my mod so I was working on fixing other compatibility issues by rewriting the code with some more features. Could you tell me what taskbar styler settings are you using and the exact step I should do to reproduce the issue?

Hi @roeseth , i have fixed the mod myself. i will share you the fixed tray positioning bug (also, i dont know how to make it support left alignment, so its now broken for left alignment 😅)

// ==WindhawkMod==
// @id              windows-11-beter-ultrawide-taskbar
// @name            Windows 11 Better Ultrawide Taskbar
// @description     Centers the taskbar and system tray as a single unit
// @version         1.1
// @author          Molko
// @github          https://github.com/roeseth
// @include         explorer.exe
// @architecture    x86-64
// @compilerOptions -lcomctl32 -lole32 -loleaut32 -lruntimeobject -ladvapi32
// ==/WindhawkMod==

// Source code is published under The GNU General Public License v3.0.
//
// For bug reports and feature requests, please open an issue here:
// https://github.com/ramensoftware/windhawk-mods/issues

// ==WindhawkModReadme==
/*
# Windows 11 Better Ultrawide Taskbar

Centers the taskbar buttons and system tray as a single unit on the Windows 11
taskbar. The mod dynamically adjusts positioning when windows are opened or
closed.

## Modes

### Center Mode (default)
Centers both the taskbar buttons and system tray together as a unified group.
The centering is calculated based on the combined width of both elements.

**Settings:**
- **Minimum total width**: Set a minimum width for the centered group
- **Gap**: Add spacing between taskbar buttons and system tray
- **Maximum taskbar width**: Limit the taskbar buttons area width

Center Mode (System Taskbar Alignment set to Center):
![Center Mode with System Taskbar Alignment set to Center](https://i.imgur.com/j8a5deC.png)

### Offset Only Mode
Only shifts the system tray by a fixed offset, reducing the taskbar width to
allow the system tray to overlap into the taskbar area. The taskbar buttons
remain in their original position.

Offset Mode (System Taskbar Alignment set to Center):
![Offset Mode with System Taskbar Alignment set to Center](https://i.imgur.com/DfhNDc4.png)

Offset Mode (System Taskbar Alignment set to Left):
![Offset Mode with System Taskbar Alignment set to Left](https://i.imgur.com/LZdzlka.png)

**Settings:**
- **Offset value**: How many pixels to shift the system tray left



## Multi-Monitor Support

The mod applies centering/offset to the primary monitor's taskbar only.
Secondary monitor taskbars are not affected.
*/
// ==/WindhawkModReadme==

// ==WindhawkModSettings==
/*
- mode: center
  $name: Mode
  $description: >-
    center = Center mode: Centers both taskbar and system tray as a single unit.
    offset = Offset only mode: Only applies offset to system tray, reduces taskbar width to create overlap.
  $options:
  - center: Center
  - offset: Offset only
- OffsetOnlySettings:
    - offsetValue: 200
      $name: Offset value
      $description: >-
        The offset (in pixels) to shift the system tray left. The taskbar width
        will be reduced by 2x this value to allow overlap.
- CenterSettings:
    - minTotalWidth: 0
      $name: Minimum total width
      $description: >-
        The minimum combined width (in pixels) for the taskbar buttons and system
        tray. If the actual width is less than this value, the centering offset
        will be calculated as if the width were this minimum value. Set to 0 to
        disable.
    - gap: 0
      $name: Gap
      $description: >-
        Additional spacing (in pixels) between the taskbar buttons and system tray.
        Positive values push them apart, negative values pull them closer together.
    - maxTotalWidth: 0
      $name: Maximum taskbar width
      $description: >-
        The maximum width (in pixels) for the taskbar buttons area. This sets the
        Width property on TaskbarFrame directly, leveraging native XAML layout.
        Set to 0 to disable (no maximum).

*/
// ==/WindhawkModSettings==

#include <windhawk_utils.h>

#include <atomic>
#include <functional>
#include <limits>

#undef GetCurrentTime

#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.UI.Xaml.Media.h>
#include <winrt/Windows.UI.Xaml.h>

using namespace winrt::Windows::UI::Xaml;

// Settings
enum class TaskbarMode { Center, Offset };

struct
{
    TaskbarMode mode;
    int offsetValue;
    int minTotalWidth;
    int gap;
    int maxTotalWidth;
} g_settings;

std::atomic<bool> g_taskbarViewDllLoaded;
std::atomic<bool> g_unloading;

// Store original taskbar frame width for offset mode
double g_originalTaskbarFrameWidth = 0;

// Cached element references - avoids walking the visual tree on every hook call
// These are invalidated (set to nullptr) when settings change or mod unloads
winrt::weak_ref<FrameworkElement> g_cachedRootPanel;
winrt::weak_ref<FrameworkElement> g_cachedTaskbarFrame;
winrt::weak_ref<FrameworkElement> g_cachedTaskbarFrameRepeater;
winrt::weak_ref<FrameworkElement> g_cachedSystemTrayFrame;

// Track which mode we last applied so we know when a full reset is needed
enum class AppliedMode { None, Center, Offset };
AppliedMode g_appliedMode = AppliedMode::None;

// SizeChanged event token for TaskbarFrameRepeater
// This fires immediately when pinned items are added/removed/dragged,
// giving us the correct trigger to reposition the systray in sync.
winrt::event_token g_repeaterSizeChangedToken{};

// Registry watcher for TaskbarAl alignment changes
// Event token for LayoutUpdated handler (used for deferred width capture)
winrt::event_token g_layoutUpdatedToken{};

// Weak reference to taskbar frame for deferred width capture
winrt::weak_ref<FrameworkElement> g_taskbarFrameWeakRef;

// XamlRoot weak reference for applying style after width capture
winrt::weak_ref<XamlRoot> g_pendingXamlRootWeakRef;

HWND FindCurrentProcessTaskbarWnd()
{
    HWND hTaskbarWnd = nullptr;

    EnumWindows(
        [](HWND hWnd, LPARAM lParam) -> BOOL
        {
            DWORD dwProcessId;
            WCHAR className[32];
            if (GetWindowThreadProcessId(hWnd, &dwProcessId) &&
                dwProcessId == GetCurrentProcessId() &&
                GetClassName(hWnd, className, ARRAYSIZE(className)) &&
                _wcsicmp(className, L"Shell_TrayWnd") == 0)
            {
                *reinterpret_cast<HWND *>(lParam) = hWnd;
                return FALSE;
            }
            return TRUE;
        },
        reinterpret_cast<LPARAM>(&hTaskbarWnd));

    return hTaskbarWnd;
}

FrameworkElement EnumChildElements(
    FrameworkElement element,
    std::function<bool(FrameworkElement)> enumCallback)
{
    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)
        {
            Wh_Log(L"Failed to get child %d of %d", i + 1, childrenCount);
            continue;
        }

        if (enumCallback(child))
        {
            return child;
        }
    }

    return nullptr;
}

FrameworkElement FindChildByName(FrameworkElement element, PCWSTR name)
{
    return EnumChildElements(element, [name](FrameworkElement child)
                             { return child.Name() == name; });
}

FrameworkElement FindChildByClassName(FrameworkElement element,
                                      PCWSTR className)
{
    return EnumChildElements(element, [className](FrameworkElement child)
                             { return winrt::get_class_name(child) == className; });
}

// Calculate total width of visible taskbar buttons
// Use ActualWidth of the repeater container directly - this is stable and
// accurate even during/after drag operations, unlike summing individual children
// which can give wrong results when items are being reordered.
double CalculateTaskbarButtonsWidth(FrameworkElement taskbarFrameRepeater)
{
    return taskbarFrameRepeater.ActualWidth();
}

// Apply a TranslateTransform to visually offset an element
void ApplyTransformOffset(FrameworkElement element, double offsetX)
{
    try
    {
        // Get or create the TranslateTransform
        Media::TranslateTransform transform{nullptr};
        auto existingTransform = element.RenderTransform();
        if (auto tt = existingTransform.try_as<Media::TranslateTransform>())
        {
            transform = tt;
        }
        else
        {
            transform = Media::TranslateTransform();
            transform.Y(0);
            element.RenderTransform(transform);
        }

        transform.X(offsetX);
    }
    catch (winrt::hresult_error const &ex)
    {
        Wh_Log(L"Error applying transform %08X: %s", ex.code().value, ex.message().c_str());
    }
}

// Helper function to reset taskbar state to defaults
// Used both for unloading and for cleaning up previous mode state
void ResetTaskbarState(FrameworkElement taskbarFrame, FrameworkElement systemTrayFrame)
{
    // Clear transforms
    taskbarFrame.RenderTransform(nullptr);
    systemTrayFrame.RenderTransform(nullptr);

    // Reset margins
    auto taskbarMargin = taskbarFrame.Margin();
    taskbarMargin.Left = 0;
    taskbarMargin.Right = 0;
    taskbarFrame.Margin(taskbarMargin);

    auto systemTrayMargin = systemTrayFrame.Margin();
    systemTrayMargin.Left = 0;
    systemTrayMargin.Right = 0;
    systemTrayFrame.Margin(systemTrayMargin);

    // Reset width to auto
    taskbarFrame.Width(std::numeric_limits<double>::quiet_NaN());

    // Reset SystemTrayFrame alignment to default (Right)
    systemTrayFrame.HorizontalAlignment(HorizontalAlignment::Right);
}

// Forward declarations
bool ApplyStyleFull(XamlRoot xamlRoot);
void ApplyPositionOnly(FrameworkElement rootPanel, FrameworkElement taskbarFrame, FrameworkElement taskbarFrameRepeater, FrameworkElement systemTrayFrame);

// Handler for LayoutUpdated event to capture width after layout completes (offset mode init only)
void OnLayoutUpdatedForWidthCapture(winrt::Windows::Foundation::IInspectable const& sender,
                                     winrt::Windows::Foundation::IInspectable const& args)
{
    auto taskbarFrame = g_taskbarFrameWeakRef.get();
    if (!taskbarFrame)
    {
        Wh_Log(L"LayoutUpdated: TaskbarFrame weak ref expired");
        return;
    }

    // Unsubscribe immediately to prevent multiple calls
    if (g_layoutUpdatedToken)
    {
        try { taskbarFrame.LayoutUpdated(g_layoutUpdatedToken); } catch (...) {}
        g_layoutUpdatedToken = {};
    }

    double actualWidth = taskbarFrame.ActualWidth();
    if (actualWidth > 0)
    {
        g_originalTaskbarFrameWidth = actualWidth;
        Wh_Log(L"LayoutUpdated: Captured original TaskbarFrame ActualWidth: %f", g_originalTaskbarFrameWidth);

        auto xamlRoot = g_pendingXamlRootWeakRef.get();
        if (xamlRoot && !g_unloading)
        {
            ApplyStyleFull(xamlRoot);
        }
    }
    else
    {
        Wh_Log(L"LayoutUpdated: ActualWidth still 0, width capture failed");
    }

    g_taskbarFrameWeakRef = nullptr;
    g_pendingXamlRootWeakRef = nullptr;
}

// Called when TaskbarFrameRepeater changes size (pins added/removed/dragged).
// This is the correct trigger for repositioning the systray — it fires immediately
// and synchronously on the XAML thread, with the new ActualWidth already committed.
void OnRepeaterSizeChanged(winrt::Windows::Foundation::IInspectable const& sender,
                            SizeChangedEventArgs const& args)
{
    if (g_unloading)
        return;

    auto taskbarFrame         = g_cachedTaskbarFrame.get();
    auto taskbarFrameRepeater = g_cachedTaskbarFrameRepeater.get();
    auto systemTrayFrame      = g_cachedSystemTrayFrame.get();

    auto rootPanel = g_cachedRootPanel.get();
    if (rootPanel && taskbarFrame && taskbarFrameRepeater && systemTrayFrame)
    {
        Wh_Log(L"RepeaterSizeChanged: new width=%f", args.NewSize().Width);
        ApplyPositionOnly(rootPanel, taskbarFrame, taskbarFrameRepeater, systemTrayFrame);
    }
}

// Unsubscribe the repeater SizeChanged handler and clear the token
void UnsubscribeRepeaterSizeChanged()
{
    if (g_repeaterSizeChangedToken)
    {
        auto repeater = g_cachedTaskbarFrameRepeater.get();
        if (repeater)
        {
            try { repeater.SizeChanged(g_repeaterSizeChangedToken); } catch (...) {}
        }
        g_repeaterSizeChangedToken = {};
    }
}

// Resolve and cache element references from the XamlRoot visual tree.
// Returns false if any required element is not found.
bool ResolveCachedElements(XamlRoot xamlRoot)
{
    auto xamlRootContent = xamlRoot.Content().try_as<FrameworkElement>();
    if (!xamlRootContent)
        return false;

    FrameworkElement taskbarFrame = nullptr;
    FrameworkElement taskbarFrameRepeater = nullptr;
    FrameworkElement child = xamlRootContent;

    if (child && (child = FindChildByClassName(child, L"Taskbar.TaskbarFrame")))
    {
        taskbarFrame = child;
        if ((child = FindChildByName(child, L"RootGrid")) &&
            (child = FindChildByName(child, L"TaskbarFrameRepeater")))
        {
            taskbarFrameRepeater = child;
        }
    }

    auto systemTrayFrame = FindChildByClassName(xamlRootContent, L"SystemTray.SystemTrayFrame");

    if (!taskbarFrame || !taskbarFrameRepeater || !systemTrayFrame)
    {
        Wh_Log(L"ResolveCachedElements: failed to find required elements");
        return false;
    }

    // Unsubscribe any previous SizeChanged handler before re-caching
    UnsubscribeRepeaterSizeChanged();

    g_cachedRootPanel = xamlRootContent;
    g_cachedTaskbarFrame = taskbarFrame;
    g_cachedTaskbarFrameRepeater = taskbarFrameRepeater;
    g_cachedSystemTrayFrame = systemTrayFrame;

    // Subscribe to SizeChanged on the repeater — this fires immediately whenever
    // pinned items are added, removed, or reordered (drag), giving us the right
    // moment to update the systray offset with the new repeater width.
    try
    {
        g_repeaterSizeChangedToken = taskbarFrameRepeater.SizeChanged(OnRepeaterSizeChanged);
        Wh_Log(L"Subscribed to TaskbarFrameRepeater SizeChanged");
    }
    catch (winrt::hresult_error const& ex)
    {
        Wh_Log(L"Failed to subscribe to SizeChanged: %08X", ex.code().value);
    }

    return true;
}

// Read the Windows taskbar alignment setting from registry.
// 0 = Left, 1 = Center (default)
bool IsTaskbarAlignedLeft()
{
    DWORD value = 0;
    DWORD size = sizeof(value);
    RegGetValueW(
        HKEY_CURRENT_USER,
        L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced",
        L"TaskbarAl",
        RRF_RT_REG_DWORD,
        nullptr,
        &value,
        &size);
    return value == 0; // 0 = Left, 1 = Center
}

// Apply only position/transform updates using already-resolved elements.
// Does NOT call ResetTaskbarState, so it does NOT trigger a XAML re-layout,
// meaning it does NOT cause another SystemTrayExtent callback — no feedback loop.
void ApplyPositionOnly(FrameworkElement rootPanel, FrameworkElement taskbarFrame, FrameworkElement taskbarFrameRepeater, FrameworkElement systemTrayFrame)
{
    double systemTrayWidth = systemTrayFrame.ActualWidth();
    double taskbarButtonsWidth = taskbarFrameRepeater.ActualWidth();

    Wh_Log(L"ApplyPositionOnly: TaskbarButtons=%f, SystemTray=%f", taskbarButtonsWidth, systemTrayWidth);

    if (g_settings.mode == TaskbarMode::Offset)
    {
        double offsetValue = static_cast<double>(g_settings.offsetValue);
        constexpr double minTaskbarWidth = 100.0;
        double newTaskbarWidth = g_originalTaskbarFrameWidth - (2.0 * offsetValue);
        newTaskbarWidth = (std::max)(newTaskbarWidth, minTaskbarWidth);

        taskbarFrame.Width(newTaskbarWidth);

        auto systemTrayMargin = systemTrayFrame.Margin();
        systemTrayMargin.Right = offsetValue;
        systemTrayFrame.Margin(systemTrayMargin);

        Wh_Log(L"Offset mode: margin.Right=%f, width=%f", offsetValue, newTaskbarWidth);
    }
    else
    {
        // Center mode
        // Apply max width if configured
        if (g_settings.maxTotalWidth > 0)
            taskbarFrame.Width(static_cast<double>(g_settings.maxTotalWidth));

        if (IsTaskbarAlignedLeft())
        {
            // Left alignment: the systray sits at the far right of the screen.
            // taskbarButtonsWidth (repeater.ActualWidth) is NOT the repeater's
            // screen-relative right edge — the repeater is a child of RootGrid which
            // is a child of TaskbarFrame, both of which may have offsets/margins.
            // Use TransformToVisual(rootPanel) to get the true screen-relative
            // right edge of the repeater, then position the systray flush to it.
            if (systemTrayFrame.HorizontalAlignment() != HorizontalAlignment::Right)
                systemTrayFrame.HorizontalAlignment(HorizontalAlignment::Right);
            // Clear any taskbar transform left over from center mode
            if (taskbarFrame.RenderTransform())
                ApplyTransformOffset(taskbarFrame, 0.0);

            double gap        = static_cast<double>(g_settings.gap);
            double screenWidth = rootPanel.XamlRoot().Size().Width;

            // Get the repeater's screen-relative right edge via TransformToVisual.
            // The repeater is nested inside RootGrid > TaskbarFrame so its local
            // x=0 is not screen x=0 — we must transform to rootPanel coordinates.
            double repeaterRightEdge = 0.0;
            try
            {
                auto tx = taskbarFrameRepeater.TransformToVisual(rootPanel);
                repeaterRightEdge = tx.TransformPoint({0, 0}).X + taskbarButtonsWidth;
            }
            catch (...)
            {
                repeaterRightEdge = taskbarButtonsWidth;
            }

            // The systray is HorizontalAlignment::Right with no margin, so its
            // natural (untransformed) left edge is exactly screenWidth - systemTrayWidth.
            // We compute the offset needed to move it to sit after the repeater.
            // This is a pure calculation — no TransformToVisual on the systray,
            // which avoids any feedback-loop issues with previously applied transforms.
            double naturalSystrayLeft = screenWidth - systemTrayWidth;
            double desiredSystrayLeft = repeaterRightEdge + gap + 3.0;
            double systemTrayOffset   = desiredSystrayLeft - naturalSystrayLeft;

            ApplyTransformOffset(systemTrayFrame, systemTrayOffset);

            Wh_Log(L"Center/Left mode: screenWidth=%f repeaterRight=%f gap=%f naturalLeft=%f offset=%f",
                   screenWidth, repeaterRightEdge, gap, naturalSystrayLeft, systemTrayOffset);
        }
        else
        {
            // Center alignment: shift taskbar left and systray right symmetrically
            // so both together appear centered as a single unit.
            if (systemTrayFrame.HorizontalAlignment() != HorizontalAlignment::Center)
                systemTrayFrame.HorizontalAlignment(HorizontalAlignment::Center);

            double halfGap = g_settings.gap / 2.0;

            double effectiveTotalWidth = taskbarButtonsWidth + systemTrayWidth;
            if (g_settings.minTotalWidth > 0 && effectiveTotalWidth < g_settings.minTotalWidth)
            {
                double extraGap = (g_settings.minTotalWidth - effectiveTotalWidth) / 2.0;
                halfGap += extraGap;
            }

            double taskbarOffset    = -(systemTrayWidth / 2.0 + halfGap);
            double systemTrayOffset =   taskbarButtonsWidth / 2.0 + halfGap;

            ApplyTransformOffset(taskbarFrame, taskbarOffset);
            ApplyTransformOffset(systemTrayFrame, systemTrayOffset);

            Wh_Log(L"Center/Center mode: taskbarOffset=%f, systemTrayOffset=%f", taskbarOffset, systemTrayOffset);
        }
    }
}

// Full setup: resolves elements, resets state, sets mode-specific one-time properties,
// captures original width if needed, then applies position.
// Called on init, settings change, and unload — NOT on every hook invocation.
bool ApplyStyleFull(XamlRoot xamlRoot)
{
    if (!xamlRoot)
        return false;

    if (!ResolveCachedElements(xamlRoot))
        return false;

    auto taskbarFrame        = g_cachedTaskbarFrame.get();
    auto taskbarFrameRepeater = g_cachedTaskbarFrameRepeater.get();
    auto systemTrayFrame     = g_cachedSystemTrayFrame.get();

    if (!taskbarFrame || !taskbarFrameRepeater || !systemTrayFrame)
        return false;

    if (g_unloading)
    {
        UnsubscribeRepeaterSizeChanged();
        ResetTaskbarState(taskbarFrame, systemTrayFrame);
        g_appliedMode = AppliedMode::None;
        g_cachedRootPanel = nullptr;
        g_cachedTaskbarFrame = nullptr;
        g_cachedTaskbarFrameRepeater = nullptr;
        g_cachedSystemTrayFrame = nullptr;
        Wh_Log(L"Unloading: restored original taskbar state");
        return true;
    }

    // Determine the target mode
    AppliedMode targetMode;
    switch (g_settings.mode)
    {
        case TaskbarMode::Offset:   targetMode = AppliedMode::Offset;   break;
        default:                    targetMode = AppliedMode::Center;   break;
    }

    // Full reset only when switching modes (avoids spurious layout passes during normal updates)
    if (g_appliedMode != targetMode)
    {
        Wh_Log(L"Mode change detected, performing full reset");
        ResetTaskbarState(taskbarFrame, systemTrayFrame);
        g_appliedMode = AppliedMode::None; // will be set after successful apply
    }

    // For offset mode, capture the original TaskbarFrame width once.
    // We MUST do this before setting any explicit Width on taskbarFrame.
    if (targetMode == AppliedMode::Offset && g_originalTaskbarFrameWidth <= 0)
    {
        double currentWidth = taskbarFrame.ActualWidth();
        if (currentWidth > 0)
        {
            g_originalTaskbarFrameWidth = currentWidth;
            Wh_Log(L"Captured original TaskbarFrame width: %f", g_originalTaskbarFrameWidth);
        }
        else
        {
            // Defer: subscribe to LayoutUpdated and retry after layout completes
            Wh_Log(L"ActualWidth=0, deferring via LayoutUpdated");
            g_taskbarFrameWeakRef = taskbarFrame;
            g_pendingXamlRootWeakRef = xamlRoot;
            try
            {
                g_layoutUpdatedToken = taskbarFrame.LayoutUpdated(OnLayoutUpdatedForWidthCapture);
                taskbarFrame.UpdateLayout();
            }
            catch (winrt::hresult_error const& ex)
            {
                Wh_Log(L"LayoutUpdated subscribe failed: %08X", ex.code().value);
                g_originalTaskbarFrameWidth = 1000.0; // safe fallback
            }
            return true;
        }
    }

    // Set HorizontalAlignment once here (not in the hot path) to avoid
    // triggering unnecessary layout passes on every SystemTrayExtent call.
    // We re-read alignment live in case user switches without reloading.
    if (targetMode == AppliedMode::Center)
    {
        if (IsTaskbarAlignedLeft())
            systemTrayFrame.HorizontalAlignment(HorizontalAlignment::Right);
        else
            systemTrayFrame.HorizontalAlignment(HorizontalAlignment::Center);
    }


    auto rootPanel = g_cachedRootPanel.get();
    if (!rootPanel) return false;
    ApplyPositionOnly(rootPanel, taskbarFrame, taskbarFrameRepeater, systemTrayFrame);
    g_appliedMode = targetMode;
    return true;
}

// Public entry point for full setup
bool ApplyStyle(XamlRoot xamlRoot)
{
    return ApplyStyleFull(xamlRoot);
}

// Symbol hook targets
void *CTaskBand_ITaskListWndSite_vftable;
void *CSecondaryTaskBand_ITaskListWndSite_vftable;

using CTaskBand_GetTaskbarHost_t = void *(WINAPI *)(void *pThis, void **result);
CTaskBand_GetTaskbarHost_t CTaskBand_GetTaskbarHost_Original;

void *TaskbarHost_FrameHeight_Original;

using CSecondaryTaskBand_GetTaskbarHost_t = void *(WINAPI *)(void *pThis, void **result);
CSecondaryTaskBand_GetTaskbarHost_t CSecondaryTaskBand_GetTaskbarHost_Original;

using std__Ref_count_base__Decref_t = void(WINAPI *)(void *pThis);
std__Ref_count_base__Decref_t std__Ref_count_base__Decref_Original;

XamlRoot XamlRootFromTaskbarHostSharedPtr(void *taskbarHostSharedPtr[2])
{
    if (!taskbarHostSharedPtr[0] && !taskbarHostSharedPtr[1])
    {
        return nullptr;
    }

    size_t taskbarElementIUnknownOffset = 0x48;

#if defined(_M_X64)
    {
        // 48:83EC 28 | sub rsp,28
        // 48:83C1 48 | add rcx,48
        const BYTE *b = (const BYTE *)TaskbarHost_FrameHeight_Original;
        if (b[0] == 0x48 && b[1] == 0x83 && b[2] == 0xEC && b[4] == 0x48 &&
            b[5] == 0x83 && b[6] == 0xC1 && b[7] <= 0x7F)
        {
            taskbarElementIUnknownOffset = b[7];
        }
        else
        {
            Wh_Log(L"Unsupported TaskbarHost::FrameHeight");
        }
    }
#elif defined(_M_ARM64)
    // Just use the default offset which will hopefully work in most cases.
#else
#error "Unsupported architecture"
#endif

    auto *taskbarElementIUnknown =
        *(IUnknown **)((BYTE *)taskbarHostSharedPtr[0] +
                       taskbarElementIUnknownOffset);

    FrameworkElement taskbarElement = nullptr;
    taskbarElementIUnknown->QueryInterface(winrt::guid_of<FrameworkElement>(),
                                           winrt::put_abi(taskbarElement));

    auto result = taskbarElement ? taskbarElement.XamlRoot() : nullptr;

    std__Ref_count_base__Decref_Original(taskbarHostSharedPtr[1]);

    return result;
}

XamlRoot GetTaskbarXamlRoot(HWND hTaskbarWnd)
{
    HWND hTaskSwWnd = (HWND)GetProp(hTaskbarWnd, L"TaskbandHWND");
    if (!hTaskSwWnd)
    {
        return nullptr;
    }

    void *taskBand = (void *)GetWindowLongPtr(hTaskSwWnd, 0);
    void *taskBandForTaskListWndSite = taskBand;
    for (int i = 0; *(void **)taskBandForTaskListWndSite !=
                    CTaskBand_ITaskListWndSite_vftable;
         i++)
    {
        if (i == 20)
        {
            return nullptr;
        }

        taskBandForTaskListWndSite = (void **)taskBandForTaskListWndSite + 1;
    }

    void *taskbarHostSharedPtr[2]{};
    CTaskBand_GetTaskbarHost_Original(taskBandForTaskListWndSite,
                                      taskbarHostSharedPtr);

    return XamlRootFromTaskbarHostSharedPtr(taskbarHostSharedPtr);
}

XamlRoot GetSecondaryTaskbarXamlRoot(HWND hSecondaryTaskbarWnd)
{
    HWND hTaskSwWnd =
        (HWND)FindWindowEx(hSecondaryTaskbarWnd, nullptr, L"WorkerW", nullptr);
    if (!hTaskSwWnd)
    {
        return nullptr;
    }

    void *taskBand = (void *)GetWindowLongPtr(hTaskSwWnd, 0);
    void *taskBandForTaskListWndSite = taskBand;
    for (int i = 0; *(void **)taskBandForTaskListWndSite !=
                    CSecondaryTaskBand_ITaskListWndSite_vftable;
         i++)
    {
        if (i == 20)
        {
            return nullptr;
        }

        taskBandForTaskListWndSite = (void **)taskBandForTaskListWndSite + 1;
    }

    void *taskbarHostSharedPtr[2]{};
    CSecondaryTaskBand_GetTaskbarHost_Original(taskBandForTaskListWndSite,
                                               taskbarHostSharedPtr);

    return XamlRootFromTaskbarHostSharedPtr(taskbarHostSharedPtr);
}

using RunFromWindowThreadProc_t = void(WINAPI *)(void *parameter);

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)&param);

    UnhookWindowsHookEx(hook);

    return true;
}

// Forward declaration
void ApplySettingsFromTaskbarThread();

// Subclass ID for our taskbar window subclass
constexpr UINT_PTR SUBCLASS_ID = 0x574B4241; // 'WKBA'
// Timer ID for deferred re-apply after alignment change
constexpr UINT_PTR REALIGN_TIMER_ID = 0x574B4242;

// Taskbar window subclass proc — intercepts WM_SETTINGCHANGE to detect
// alignment switches instantly, with zero CPU overhead when idle.
LRESULT CALLBACK TaskbarSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam,
                                      LPARAM lParam, UINT_PTR uIdSubclass,
                                      DWORD_PTR dwRefData)
{
    if (uMsg == WM_SETTINGCHANGE && !g_unloading)
    {
        // Re-apply on any WM_SETTINGCHANGE — alignment changes broadcast this.
        // Timer lets Windows finish its own layout before we reposition.
        Wh_Log(L"WM_SETTINGCHANGE: scheduling re-apply (lParam=%s)",
               lParam ? reinterpret_cast<LPCWSTR>(lParam) : L"null");
        SetTimer(hWnd, REALIGN_TIMER_ID, 200, nullptr);
    }
    else if (uMsg == WM_TIMER && wParam == REALIGN_TIMER_ID && !g_unloading)
    {
        KillTimer(hWnd, REALIGN_TIMER_ID);
        Wh_Log(L"Re-applying after alignment change");
        g_appliedMode = AppliedMode::None;
        ApplySettingsFromTaskbarThread();
    }
    else if (uMsg == WM_NCDESTROY)
    {
        RemoveWindowSubclass(hWnd, TaskbarSubclassProc, uIdSubclass);
    }

    return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}

void ApplySettingsFromTaskbarThread()
{
    Wh_Log(L"Applying settings");

    EnumThreadWindows(
        GetCurrentThreadId(),
        [](HWND hWnd, LPARAM lParam) -> BOOL
        {
            WCHAR szClassName[32];
            if (GetClassName(hWnd, szClassName, ARRAYSIZE(szClassName)) == 0)
            {
                return TRUE;
            }

            XamlRoot xamlRoot = nullptr;
            if (_wcsicmp(szClassName, L"Shell_TrayWnd") == 0)
            {
                xamlRoot = GetTaskbarXamlRoot(hWnd);
            }
            else if (_wcsicmp(szClassName, L"Shell_SecondaryTrayWnd") == 0)
            {
                // Skip secondary taskbars for now - only apply to primary
                return TRUE;
            }
            else
            {
                return TRUE;
            }

            if (!xamlRoot)
            {
                Wh_Log(L"Getting XamlRoot failed");
            return TRUE;
            }

            if (!ApplyStyle(xamlRoot))
            {
                Wh_Log(L"ApplyStyle failed");
                return TRUE;
            }

                return TRUE;
        },
        0);
}

void ApplySettings(HWND hTaskbarWnd)
{
    RunFromWindowThread(
        hTaskbarWnd, [](void *pParam)
        { ApplySettingsFromTaskbarThread(); }, 0);
}

// Hook for TaskbarFrame::SystemTrayExtent - called when system tray size changes
using TaskbarFrame_SystemTrayExtent_t = void(WINAPI *)(void *pThis, double value);
TaskbarFrame_SystemTrayExtent_t TaskbarFrame_SystemTrayExtent_Original;
void WINAPI TaskbarFrame_SystemTrayExtent_Hook(void *pThis, double value)
{
    Wh_Log(L"> SystemTrayExtent: %f", value);

    TaskbarFrame_SystemTrayExtent_Original(pThis, value);

    if (g_unloading)
    {
        return;
    }

    // Validate pThis pointer before accessing
    if (!pThis)
    {
        Wh_Log(L"pThis is null, skipping");
        return;
    }

    // Get the TaskbarFrame element
    // The IUnknown pointer is at offset 1 in the object's vtable array
    IUnknown **pThisArray = (IUnknown **)pThis;
    IUnknown *pUnknown = pThisArray[1];
    if (!pUnknown)
    {
        Wh_Log(L"IUnknown pointer at offset 1 is null, skipping");
        return;
    }

    FrameworkElement taskbarFrameElement = nullptr;
    HRESULT hr = pUnknown->QueryInterface(winrt::guid_of<FrameworkElement>(),
                                          winrt::put_abi(taskbarFrameElement));
    if (FAILED(hr) || !taskbarFrameElement)
    {
        Wh_Log(L"QueryInterface failed or returned null element, hr=%08X", hr);
        return;
    }

    // Fast path: use cached element references and just update transforms/margins.
    // Do NOT call ApplyStyleFull here — that calls ResetTaskbarState which sets
    // taskbarFrame.Width(NaN), triggers a XAML re-layout, which fires
    // SystemTrayExtent again, creating a feedback loop that delays the systray
    // by one full layout cycle (visible lag). Instead, if we have cached
    // elements, just recalculate and apply position directly with no side effects.
    {
        auto taskbarFrame         = g_cachedTaskbarFrame.get();
        auto taskbarFrameRepeater = g_cachedTaskbarFrameRepeater.get();
        auto systemTrayFrame      = g_cachedSystemTrayFrame.get();

        auto rootPanel = g_cachedRootPanel.get();
        if (rootPanel && taskbarFrame && taskbarFrameRepeater && systemTrayFrame)
        {
            // Hot path: skip tree walk, skip reset, just update numbers
            ApplyPositionOnly(rootPanel, taskbarFrame, taskbarFrameRepeater, systemTrayFrame);
        }
        else
        {
            // Cache miss (first run or after settings change): do the full setup
            auto xamlRoot = taskbarFrameElement.XamlRoot();
            if (xamlRoot)
            {
                ApplyStyleFull(xamlRoot);
            }
        }
    }
}

bool HookTaskbarDllSymbols()
{
    HMODULE module =
        LoadLibraryEx(L"taskbar.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
    if (!module)
    {
        Wh_Log(L"Failed to load taskbar.dll");
        return false;
    }

    WindhawkUtils::SYMBOL_HOOK taskbarDllHooks[] = {
        {
            {LR"(const CTaskBand::`vftable'{for `ITaskListWndSite'})"},
            &CTaskBand_ITaskListWndSite_vftable,
        },
        {
            {LR"(const CSecondaryTaskBand::`vftable'{for `ITaskListWndSite'})"},
            &CSecondaryTaskBand_ITaskListWndSite_vftable,
        },
        {
            {LR"(public: virtual class std::shared_ptr<class TaskbarHost> __cdecl CTaskBand::GetTaskbarHost(void)const )"},
            &CTaskBand_GetTaskbarHost_Original,
        },
        {
            {LR"(public: int __cdecl TaskbarHost::FrameHeight(void)const )"},
            &TaskbarHost_FrameHeight_Original,
        },
        {
            {LR"(public: virtual class std::shared_ptr<class TaskbarHost> __cdecl CSecondaryTaskBand::GetTaskbarHost(void)const )"},
            &CSecondaryTaskBand_GetTaskbarHost_Original,
        },
        {
            {LR"(public: void __cdecl std::_Ref_count_base::_Decref(void))"},
            &std__Ref_count_base__Decref_Original,
        },
    };

    if (!HookSymbols(module, taskbarDllHooks, ARRAYSIZE(taskbarDllHooks)))
    {
        Wh_Log(L"HookSymbols failed for taskbar.dll");
        return false;
    }

    return true;
}

bool HookTaskbarViewDllSymbols(HMODULE module)
{
    WindhawkUtils::SYMBOL_HOOK symbolHooks[] = {
        {
            {LR"(public: void __cdecl winrt::Taskbar::implementation::TaskbarFrame::SystemTrayExtent(double))"},
            &TaskbarFrame_SystemTrayExtent_Original,
            TaskbarFrame_SystemTrayExtent_Hook,
        },
    };

    if (!HookSymbols(module, symbolHooks, ARRAYSIZE(symbolHooks)))
    {
        Wh_Log(L"HookSymbols failed for Taskbar.View.dll");
        return false;
    }

    return true;
}

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))
    {
        Wh_Log(L"Loaded %s", lpLibFileName);

        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()
{
    // Load mode
    {
        auto modeSetting = Wh_GetStringSetting(L"mode");
        if (wcscmp(modeSetting, L"offset") == 0)
            g_settings.mode = TaskbarMode::Offset;
        else
            g_settings.mode = TaskbarMode::Center;
        Wh_FreeStringSetting(modeSetting);
    }

    int offsetValue = Wh_GetIntSetting(L"OffsetOnlySettings.offsetValue");
    g_settings.offsetValue = (std::max)(0, offsetValue);

    int minTotalWidth = Wh_GetIntSetting(L"CenterSettings.minTotalWidth");
    g_settings.minTotalWidth = (std::max)(0, minTotalWidth);

    g_settings.gap = Wh_GetIntSetting(L"CenterSettings.gap");

    int maxTotalWidth = Wh_GetIntSetting(L"CenterSettings.maxTotalWidth");
    g_settings.maxTotalWidth = (std::max)(0, maxTotalWidth);


    Wh_Log(L"Settings loaded: mode=%d, offsetValue=%d, minTotalWidth=%d, gap=%d, maxTotalWidth=%d",
           (int)g_settings.mode, g_settings.offsetValue, g_settings.minTotalWidth, g_settings.gap, g_settings.maxTotalWidth);
}

BOOL Wh_ModInit()
{
    Wh_Log(L">");

    LoadSettings();

    if (!HookTaskbarDllSymbols())
    {
        return FALSE;
    }

    if (HMODULE taskbarViewModule = GetTaskbarViewModuleHandle())
    {
        g_taskbarViewDllLoaded = true;
        if (!HookTaskbarViewDllSymbols(taskbarViewModule))
        {
            return FALSE;
        }
    }
    else
    {
        Wh_Log(L"Taskbar view module not loaded yet");

        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()
{
    Wh_Log(L">");

    if (!g_taskbarViewDllLoaded)
    {
        if (HMODULE taskbarViewModule = GetTaskbarViewModuleHandle())
        {
            if (!g_taskbarViewDllLoaded.exchange(true))
            {
                Wh_Log(L"Got Taskbar.View.dll");

                if (HookTaskbarViewDllSymbols(taskbarViewModule))
                {
                    Wh_ApplyHookOperations();
                }
            }
        }
    }

    HWND hTaskbarWnd = FindCurrentProcessTaskbarWnd();
    if (hTaskbarWnd)
    {
        ApplySettings(hTaskbarWnd);
        SetWindowSubclass(hTaskbarWnd, TaskbarSubclassProc, SUBCLASS_ID, 0);
    }
}

void Wh_ModBeforeUninit()
{
    Wh_Log(L">");

    g_unloading = true;

    // Remove taskbar window subclass
    HWND hTaskbarWnd2 = FindCurrentProcessTaskbarWnd();
    if (hTaskbarWnd2)
        RemoveWindowSubclass(hTaskbarWnd2, TaskbarSubclassProc, SUBCLASS_ID);

    // Clean up SizeChanged subscription on repeater
    UnsubscribeRepeaterSizeChanged();

    // Clean up any pending LayoutUpdated event subscription
    if (g_layoutUpdatedToken)
    {
        auto taskbarFrame = g_taskbarFrameWeakRef.get();
        if (taskbarFrame)
        {
            try
            {
                taskbarFrame.LayoutUpdated(g_layoutUpdatedToken);
            }
            catch (...)
            {
                // Ignore errors during cleanup
            }
        }
        g_layoutUpdatedToken = {};
    }
    g_taskbarFrameWeakRef = nullptr;
    g_pendingXamlRootWeakRef = nullptr;

    HWND hTaskbarWnd = FindCurrentProcessTaskbarWnd();
    if (hTaskbarWnd)
    {
        ApplySettings(hTaskbarWnd);
    }
}

void Wh_ModUninit()
{
    Wh_Log(L">");
}

void Wh_ModSettingsChanged()
{
    Wh_Log(L">");

    // Unsubscribe SizeChanged and invalidate cache so ApplyStyleFull does a clean setup
    UnsubscribeRepeaterSizeChanged();
    g_cachedRootPanel = nullptr;
    g_cachedTaskbarFrame = nullptr;
    g_cachedTaskbarFrameRepeater = nullptr;
    g_cachedSystemTrayFrame = nullptr;
    g_appliedMode = AppliedMode::None;
    // Reset original width so offset mode re-captures it with any new taskbar size
    g_originalTaskbarFrameWidth = 0;

    LoadSettings();

    HWND hTaskbarWnd = FindCurrentProcessTaskbarWnd();
    if (hTaskbarWnd)
    {
        ApplySettings(hTaskbarWnd);
    }
}

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants