// ==WindhawkMod==
// @id              taskbar-thumbnail-no-hover-delay
// @name            Taskbar Thumbnail No Hover Delay
// @description     Removes the delay between hovering over a taskbar icon and the thumbnail preview appearing
// @version         1.1.0
// @author          Alchemy
// @github          https://github.com/Alchemy
// @include         explorer.exe
// @architecture    x86-64
// @compilerOptions -luser32 -lcomctl32
// ==/WindhawkMod==

// ==WindhawkModReadme==
/*
# Taskbar Thumbnail No Hover Delay

Removes the ~400ms delay between hovering over a taskbar app icon and
the thumbnail preview window appearing. The preview now opens instantly.

Supports both the old Win32 taskbar and the new Windows 11 XAML taskbar.

## How it works

**Old taskbar (Win32):** Hooks `TrackMouseEvent` so that when the
taskbar requests a hover notification, the hover time is set to 1ms
instead of the default ~400ms.

**New taskbar (XAML / Taskbar.View.dll):** Hooks the internal
`HoverFlyoutController::OnHoverFlyoutTimerTick` method. When the
pointer enters a taskbar button, the XAML taskbar starts a
DispatcherTimer with a ~400ms interval. On tick it calls
`OnHoverFlyoutTimerTick` which shows the flyout. We hook the method
that *schedules* this timer (`ScheduleHoverFlyout`) and reduce the
DispatcherTimer interval to 1 tick so the flyout appears immediately.
As a fallback, we also try patching `OnHoverFlyoutTimerTick` to
ensure it always fires through.
*/
// ==/WindhawkModReadme==

#include <windhawk_utils.h>

#include <windows.h>
#include <commctrl.h>

// ==========================================================================
// Old taskbar: detect taskbar list windows to scope the hook
// ==========================================================================

bool IsTaskbarListWindow(HWND hWnd) {
    if (!hWnd || !IsWindow(hWnd)) {
        return false;
    }

    WCHAR className[64];
    int len = GetClassNameW(hWnd, className, ARRAYSIZE(className));
    if (len <= 0) {
        return false;
    }

    return wcscmp(className, L"MSTaskListWClass") == 0 ||
           wcscmp(className, L"MSTaskSwWClass") == 0;
}

bool IsTaskbarWindow(HWND hWnd) {
    if (IsTaskbarListWindow(hWnd)) {
        return true;
    }

    HWND hParent = GetParent(hWnd);
    while (hParent) {
        if (IsTaskbarListWindow(hParent)) {
            return true;
        }
        hParent = GetParent(hParent);
    }

    return false;
}

// ==========================================================================
// Hook: TrackMouseEvent (old Win32 taskbar)
// ==========================================================================

using TrackMouseEvent_t = decltype(&TrackMouseEvent);
TrackMouseEvent_t TrackMouseEvent_Original;

BOOL WINAPI TrackMouseEvent_Hook(LPTRACKMOUSEEVENT lpEventTrack) {
    if (lpEventTrack &&
        (lpEventTrack->dwFlags & TME_HOVER) &&
        IsTaskbarWindow(lpEventTrack->hwndTrack)) {
        Wh_Log(L"TrackMouseEvent: reducing hover time from %u to 1",
               lpEventTrack->dwHoverTime);
        lpEventTrack->dwHoverTime = 1;
    }

    return TrackMouseEvent_Original(lpEventTrack);
}

// ==========================================================================
// Hooks for Taskbar.View.dll (new XAML taskbar)
//
// The hover flyout delay is driven by a DispatcherTimer inside
// HoverFlyoutController. The flow is:
//
//   PointerEntered -> ScheduleHoverFlyout (starts DispatcherTimer)
//     -> [~400ms delay] -> OnHoverFlyoutTimerTick
//       -> ShowTaskListButtonHoverFlyout
//
// We hook ScheduleHoverFlyout to reduce the DispatcherTimer's
// Interval property so the tick fires almost immediately.
// ==========================================================================

// OnHoverFlyoutTimerTick - passthrough hook for logging/verification
using OnHoverFlyoutTimerTick_t =
    void(WINAPI*)(void* pThis, void* sender, void* args);
OnHoverFlyoutTimerTick_t OnHoverFlyoutTimerTick_Original;

void WINAPI OnHoverFlyoutTimerTick_Hook(void* pThis,
                                        void* sender,
                                        void* args) {
    Wh_Log(L"OnHoverFlyoutTimerTick fired");
    OnHoverFlyoutTimerTick_Original(pThis, sender, args);
}

// ScheduleHoverFlyout - this is where the DispatcherTimer delay is set.
// Hook variants for different Windows 11 builds.

// Variant: void ScheduleHoverFlyout(FrameworkElement, IInspectable, InputDeviceKind, int)
using ScheduleHoverFlyout_t =
    void(WINAPI*)(void* pThis, void* element, void* inspectable,
                  int inputDeviceKind, int flyoutKind);
ScheduleHoverFlyout_t ScheduleHoverFlyout_Original;

void WINAPI ScheduleHoverFlyout_Hook(void* pThis,
                                     void* element,
                                     void* inspectable,
                                     int inputDeviceKind,
                                     int flyoutKind) {
    Wh_Log(L"ScheduleHoverFlyout: deviceKind=%d flyoutKind=%d",
           inputDeviceKind, flyoutKind);

    // The original function creates/starts a DispatcherTimer with a delay.
    // We call the original, then immediately patch the timer interval.
    // However since we can't easily access the DispatcherTimer from here,
    // we rely on the SetTimer/DispatcherTimer approach below.
    ScheduleHoverFlyout_Original(pThis, element, inspectable,
                                 inputDeviceKind, flyoutKind);
}

// Variant: void ScheduleHoverFlyout(FrameworkElement, IInspectable, InputDeviceKind)
// Pre-KB5053656 builds
using ScheduleHoverFlyout_Old_t =
    void(WINAPI*)(void* pThis, void* element, void* inspectable,
                  int inputDeviceKind);
ScheduleHoverFlyout_Old_t ScheduleHoverFlyout_Old_Original;

void WINAPI ScheduleHoverFlyout_Old_Hook(void* pThis,
                                         void* element,
                                         void* inspectable,
                                         int inputDeviceKind) {
    Wh_Log(L"ScheduleHoverFlyout (old): deviceKind=%d", inputDeviceKind);
    ScheduleHoverFlyout_Old_Original(pThis, element, inspectable,
                                     inputDeviceKind);
}

// ==========================================================================
// Hook: SetTimer (catch DispatcherTimer's internal Win32 timer)
//
// WinRT DispatcherTimer ultimately calls Win32 SetTimer under the hood
// via the dispatcher infrastructure. We reduce any timer set on the
// taskbar's XAML island windows that looks like a hover delay timer.
// ==========================================================================

using SetTimer_t = decltype(&SetTimer);
SetTimer_t SetTimer_Original;

bool IsXamlIslandWindow(HWND hWnd) {
    if (!hWnd || !IsWindow(hWnd)) {
        return false;
    }

    WCHAR className[128];
    int len = GetClassNameW(hWnd, className, ARRAYSIZE(className));
    if (len <= 0) {
        return false;
    }

    return wcscmp(className,
                  L"Windows.UI.Composition.DesktopWindowContentBridge") == 0;
}

bool IsTaskbarXamlWindow(HWND hWnd) {
    if (!hWnd || !IsWindow(hWnd)) {
        return false;
    }

    WCHAR className[128];
    int len = GetClassNameW(hWnd, className, ARRAYSIZE(className));
    if (len <= 0) {
        return false;
    }

    // Check for the XAML island hosting the taskbar
    if (wcscmp(className,
               L"Windows.UI.Composition.DesktopWindowContentBridge") == 0) {
        return true;
    }

    // Also check for the taskbar XAML host
    if (wcscmp(className, L"Xaml_WindowedPopupClass") == 0) {
        return true;
    }

    return false;
}

UINT_PTR WINAPI SetTimer_Hook(HWND hWnd,
                              UINT_PTR nIDEvent,
                              UINT uElapse,
                              TIMERPROC lpTimerFunc) {
    // Reduce hover-delay-range timers on XAML taskbar windows
    if (hWnd && uElapse >= 200 && uElapse <= 600 &&
        IsTaskbarXamlWindow(hWnd)) {
        Wh_Log(L"SetTimer: reducing elapse %u -> 1 on XAML window %p",
               uElapse, hWnd);
        uElapse = 1;
    }

    return SetTimer_Original(hWnd, nIDEvent, uElapse, lpTimerFunc);
}

// ==========================================================================
// Taskbar.View.dll symbol hooking
// ==========================================================================

bool HookTaskbarViewDllSymbols(HMODULE hModule) {
    Wh_Log(L"Attempting to hook Taskbar.View.dll symbols at %p", hModule);

    WindhawkUtils::SYMBOL_HOOK hooks[] = {
        // OnHoverFlyoutTimerTick
        {
            {
                LR"(private: void __cdecl winrt::Taskbar::implementation::HoverFlyoutController::OnHoverFlyoutTimerTick(struct winrt::Windows::Foundation::IInspectable const &,struct winrt::Windows::Foundation::IInspectable const &))",
            },
            (void**)&OnHoverFlyoutTimerTick_Original,
            (void*)OnHoverFlyoutTimerTick_Hook,
            true,
        },
        // ScheduleHoverFlyout (new variant, 4 params after pThis)
        {
            {
                LR"(private: void __cdecl winrt::Taskbar::implementation::HoverFlyoutController::ScheduleHoverFlyout(struct winrt::Windows::UI::Xaml::FrameworkElement const &,struct winrt::Windows::Foundation::IInspectable const &,enum winrt::Windows::UI::Input::PointerDeviceType const &,enum winrt::Taskbar::implementation::HoverFlyoutController::FlyoutKind const &))",
                LR"(private: void __cdecl winrt::Taskbar::implementation::HoverFlyoutController::ScheduleHoverFlyout(struct winrt::Windows::UI::Xaml::FrameworkElement const &,struct winrt::Windows::Foundation::IInspectable const &,winrt::Windows::UI::Input::PointerDeviceType const &,winrt::Taskbar::implementation::HoverFlyoutController::FlyoutKind const &))",
            },
            (void**)&ScheduleHoverFlyout_Original,
            (void*)ScheduleHoverFlyout_Hook,
            true,
        },
        // ScheduleHoverFlyout (old variant, 3 params after pThis)
        {
            {
                LR"(private: void __cdecl winrt::Taskbar::implementation::HoverFlyoutController::ScheduleHoverFlyout(struct winrt::Windows::UI::Xaml::FrameworkElement const &,struct winrt::Windows::Foundation::IInspectable const &,enum winrt::Windows::UI::Input::PointerDeviceType const &))",
                LR"(private: void __cdecl winrt::Taskbar::implementation::HoverFlyoutController::ScheduleHoverFlyout(struct winrt::Windows::UI::Xaml::FrameworkElement const &,struct winrt::Windows::Foundation::IInspectable const &,winrt::Windows::UI::Input::PointerDeviceType const &))",
            },
            (void**)&ScheduleHoverFlyout_Old_Original,
            (void*)ScheduleHoverFlyout_Old_Hook,
            true,
        },
    };

    bool result = WindhawkUtils::HookSymbols(hModule, hooks, ARRAYSIZE(hooks));

    if (result) {
        Wh_Log(L"Symbol hooks set, applying operations");
        Wh_ApplyHookOperations();
    }

    Wh_Log(L"HookSymbols result: %d", result);
    Wh_Log(L"  OnHoverFlyoutTimerTick: %s",
           OnHoverFlyoutTimerTick_Original ? L"hooked" : L"not found");
    Wh_Log(L"  ScheduleHoverFlyout (new): %s",
           ScheduleHoverFlyout_Original ? L"hooked" : L"not found");
    Wh_Log(L"  ScheduleHoverFlyout (old): %s",
           ScheduleHoverFlyout_Old_Original ? L"hooked" : L"not found");

    return result;
}

// ==========================================================================
// LoadLibrary interception for Taskbar.View.dll
// ==========================================================================

using LoadLibraryExW_t = decltype(&LoadLibraryExW);
LoadLibraryExW_t LoadLibraryExW_Original;

bool g_taskbarViewDllHooked = false;

HMODULE WINAPI LoadLibraryExW_Hook(LPCWSTR lpLibFileName,
                                   HANDLE hFile,
                                   DWORD dwFlags) {
    HMODULE hModule = LoadLibraryExW_Original(lpLibFileName, hFile, dwFlags);

    if (hModule && !g_taskbarViewDllHooked && lpLibFileName) {
        PCWSTR fileName = wcsrchr(lpLibFileName, L'\\');
        if (!fileName) {
            fileName = lpLibFileName;
        } else {
            fileName++;
        }

        if (_wcsicmp(fileName, L"Taskbar.View.dll") == 0) {
            Wh_Log(L"Taskbar.View.dll loaded at %p, hooking symbols",
                   hModule);
            HookTaskbarViewDllSymbols(hModule);
            g_taskbarViewDllHooked = true;
        }
    }

    return hModule;
}

// ==========================================================================
// Mod lifecycle
// ==========================================================================

BOOL Wh_ModInit() {
    Wh_Log(L"Initializing taskbar-thumbnail-no-hover-delay v1.1.0");

    // Hook TrackMouseEvent for the old Win32 taskbar hover delay
    Wh_SetFunctionHook((void*)TrackMouseEvent,
                       (void*)TrackMouseEvent_Hook,
                       (void**)&TrackMouseEvent_Original);
    Wh_Log(L"TrackMouseEvent hook set");

    // Hook SetTimer for the XAML taskbar's internal delay timers
    Wh_SetFunctionHook((void*)SetTimer,
                       (void*)SetTimer_Hook,
                       (void**)&SetTimer_Original);
    Wh_Log(L"SetTimer hook set");

    // Hook LoadLibraryExW to intercept Taskbar.View.dll loading
    Wh_SetFunctionHook((void*)LoadLibraryExW,
                       (void*)LoadLibraryExW_Hook,
                       (void**)&LoadLibraryExW_Original);
    Wh_Log(L"LoadLibraryExW hook set");

    // If Taskbar.View.dll is already loaded, hook it now
    HMODULE hTaskbarViewDll = GetModuleHandleW(L"Taskbar.View.dll");
    if (hTaskbarViewDll) {
        Wh_Log(L"Taskbar.View.dll already loaded at %p", hTaskbarViewDll);
        HookTaskbarViewDllSymbols(hTaskbarViewDll);
        g_taskbarViewDllHooked = true;
    } else {
        Wh_Log(L"Taskbar.View.dll not yet loaded, waiting for LoadLibrary");
    }

    Wh_Log(L"Initialization complete");
    return TRUE;
}

void Wh_ModAfterInit() {
    // Double-check for Taskbar.View.dll in case it loaded during init
    if (!g_taskbarViewDllHooked) {
        HMODULE hTaskbarViewDll = GetModuleHandleW(L"Taskbar.View.dll");
        if (hTaskbarViewDll) {
            Wh_Log(L"Taskbar.View.dll found in AfterInit at %p",
                   hTaskbarViewDll);
            HookTaskbarViewDllSymbols(hTaskbarViewDll);
            g_taskbarViewDllHooked = true;
        }
    }
}

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