Skip to content

create explorer-single-window-tabs mod#3457

Open
almas-cp wants to merge 7 commits intoramensoftware:mainfrom
almas-cp:explorer-tabs-only
Open

create explorer-single-window-tabs mod#3457
almas-cp wants to merge 7 commits intoramensoftware:mainfrom
almas-cp:explorer-tabs-only

Conversation

@almas-cp
Copy link
Copy Markdown
Contributor

@almas-cp almas-cp commented Mar 3, 2026

Adds a new mod that forces File Explorer to use a single window with tabs instead of opening multiple windows.

Comment thread mods/explorer-single-window-tabs.wh.cpp Outdated

// ==WindhawkModSettings==
/*
- enabled: true
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.

What's the purpose of the option compared to just disabling the mod?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

nothing, should i remove it?

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.

Yes.

Comment thread mods/explorer-single-window-tabs.wh.cpp Outdated
}

// Hide and close the duplicate window
ShowWindow(newWnd, SW_HIDE);
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.

Perhaps it's worth hiding the window earlier?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

can you clarify please?

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.

I haven't tried the mod yet, but if the new window is shown and then hidden, perhaps it's better to either hide it earlier, or, even better, to prevent it from ever being visible, either by removing the CreateWindowExW visible flag, or by hooking the relevant show function (probably ShowWindow).

@m417z
Copy link
Copy Markdown
Member

m417z commented Mar 3, 2026

I tried the mod, it indeed flashes, and hooking ShowWindow fixes it. Here's the quick change that I tried:

diff --git a/mods/explorer-single-window-tabs.wh.cpp b/mods/explorer-single-window-tabs.wh.cpp
index b305c87..bc498dd 100644
--- a/mods/explorer-single-window-tabs.wh.cpp
+++ b/mods/explorer-single-window-tabs.wh.cpp
@@ -64,6 +64,8 @@ Hold **Shift** while opening a folder to force a separate window.
 
 static bool g_enabled = true;
 
+static HWND g_lastPrimaryWindow = nullptr;
+
 struct RedirectInfo {
     HWND newWindow;
     HWND primaryWindow;
@@ -384,6 +386,7 @@ HWND WINAPI CreateWindowExW_Hook(DWORD dwExStyle, LPCWSTR lpClassName,
     if (hwnd && isCabinet && g_enabled) {
         HWND existing = FindExistingExplorer(hwnd);
         if (existing && !(GetAsyncKeyState(VK_SHIFT) & 0x8000)) {
+            g_lastPrimaryWindow = existing;
             // Redirect ΓÇö window stays hidden, thread will close it
             auto* ri = new RedirectInfo{hwnd, existing};
             HANDLE hThread =
@@ -399,6 +402,19 @@ HWND WINAPI CreateWindowExW_Hook(DWORD dwExStyle, LPCWSTR lpClassName,
     return hwnd;
 }
 
+// ---------------------------------------------------------------------------
+// Hook: ShowWindow
+// ---------------------------------------------------------------------------
+using ShowWindow_t = decltype(&ShowWindow);
+ShowWindow_t ShowWindow_Original;
+BOOL WINAPI ShowWindow_Hook(HWND hWnd, int nCmdShow) {
+    if (g_enabled && hWnd == g_lastPrimaryWindow) {
+        // Prevent the original window from flashing
+        nCmdShow = SW_HIDE;
+    }
+    return ShowWindow_Original(hWnd, nCmdShow);
+}
+
 // ---------------------------------------------------------------------------
 // Mod lifecycle
 // ---------------------------------------------------------------------------
@@ -411,6 +427,9 @@ BOOL Wh_ModInit() {
     Wh_SetFunctionHook(reinterpret_cast<void*>(CreateWindowExW),
                         reinterpret_cast<void*>(CreateWindowExW_Hook),
                         reinterpret_cast<void**>(&CreateWindowExW_Original));
+    Wh_SetFunctionHook(reinterpret_cast<void*>(ShowWindow),
+                        reinterpret_cast<void*>(ShowWindow_Hook),
+                        reinterpret_cast<void**>(&ShowWindow_Original));
     return TRUE;
 }
 


// Wait for the new tab to register in IShellWindows (max ~3s)
bool navigated = false;
for (int wait = 0; wait < 30; wait++) {
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 often times out because prevCount includes the window that isn't closed yet. Perhaps you can replace this:

    // Close the duplicate window (already hidden via WS_VISIBLE strip)
    PostMessageW(newWnd, WM_CLOSE, 0, 0);

    // Snapshot tab count before creating a new tab
    int prevCount = GetShellWindowCount();

with this:

    // Snapshot tab count before creating a new tab
    int prevCount = GetShellWindowCount() - 1;

    // Close the duplicate window (already hidden via WS_VISIBLE strip)
    PostMessageW(newWnd, WM_CLOSE, 0, 0);

@m417z
Copy link
Copy Markdown
Member

m417z commented Mar 3, 2026

I experimented some more, and made some additional changes to make it more stable:

diff --git a/mods/explorer-single-window-tabs.wh.cpp b/mods/explorer-single-window-tabs.wh.cpp
index b305c87..d428a0a 100644
--- a/mods/explorer-single-window-tabs.wh.cpp
+++ b/mods/explorer-single-window-tabs.wh.cpp
@@ -38,16 +38,6 @@ Hold **Shift** while opening a folder to force a separate window.
 */
 // ==/WindhawkModReadme==
 
-// ==WindhawkModSettings==
-/*
-- enabled: true
-  $name: Enable single-window mode
-  $description: >-
-    When enabled, new Explorer windows are redirected to tabs in the primary
-    window. Hold Shift to bypass.
-*/
-// ==/WindhawkModSettings==
-
 #include <windhawk_utils.h>
 #include <windows.h>
 #include <shlobj.h>
@@ -62,7 +52,8 @@ Hold **Shift** while opening a folder to force a separate window.
 #define WC_SHELLTAB  L"ShellTabWindowClass"
 #define CMD_NEW_TAB  0xA21B  // Internal Explorer "new tab" command
 
-static bool g_enabled = true;
+static HWND g_lastPrimaryWindow = nullptr;
+static thread_local bool g_creatingExplorerWindow = false;
 
 struct RedirectInfo {
     HWND newWindow;
@@ -311,12 +302,12 @@ static DWORD WINAPI RedirectThread(LPVOID param) {
         return 0;
     }
 
+    // Snapshot tab count before creating a new tab
+    int prevCount = GetShellWindowCount() - 1;
+
     // Close the duplicate window (already hidden via WS_VISIBLE strip)
     PostMessageW(newWnd, WM_CLOSE, 0, 0);
 
-    // Snapshot tab count before creating a new tab
-    int prevCount = GetShellWindowCount();
-
     // Create a new tab via internal WM_COMMAND
     HWND shellTab = FindWindowExW(primary, nullptr, WC_SHELLTAB, nullptr);
     if (!shellTab) {
@@ -366,44 +357,81 @@ HWND WINAPI CreateWindowExW_Hook(DWORD dwExStyle, LPCWSTR lpClassName,
                                   int X, int Y, int nWidth, int nHeight,
                                   HWND hWndParent, HMENU hMenu,
                                   HINSTANCE hInstance, LPVOID lpParam) {
+    auto original = [=]() {
+        return CreateWindowExW_Original(dwExStyle, lpClassName, lpWindowName,
+                                        dwStyle, X, Y, nWidth, nHeight,
+                                        hWndParent, hMenu, hInstance, lpParam);
+    };
+
     bool isCabinet = lpClassName && !IS_INTRESOURCE(lpClassName) &&
                      wcscmp(lpClassName, WC_CABINET) == 0;
+    if (!isCabinet) {
+        return original();
+    }
+
+    // Bypass if Shift is held
+    if (GetAsyncKeyState(VK_SHIFT) & 0x8000) {
+        return original();
+    }
+
+    HWND existing = FindExistingExplorer(nullptr);
 
-    // Strip WS_VISIBLE so the window is never shown before we decide
-    bool wasVisible = false;
-    if (isCabinet && g_enabled) {
-        wasVisible = (dwStyle & WS_VISIBLE) != 0;
-        dwStyle &= ~WS_VISIBLE;
+    // First window
+    if (!existing) {
+        return original();
     }
 
+    // Strip WS_VISIBLE so the window is never shown
+    dwStyle &= ~WS_VISIBLE;
+
+    Wh_Log(L"Creating Explorer window");
+    g_creatingExplorerWindow = true;
+
     HWND hwnd = CreateWindowExW_Original(dwExStyle, lpClassName, lpWindowName,
                                           dwStyle, X, Y, nWidth, nHeight,
                                           hWndParent, hMenu, hInstance,
                                           lpParam);
 
-    if (hwnd && isCabinet && g_enabled) {
-        HWND existing = FindExistingExplorer(hwnd);
-        if (existing && !(GetAsyncKeyState(VK_SHIFT) & 0x8000)) {
-            // Redirect ΓÇö window stays hidden, thread will close it
-            auto* ri = new RedirectInfo{hwnd, existing};
-            HANDLE hThread =
-                CreateThread(nullptr, 0, RedirectThread, ri, 0, nullptr);
-            if (hThread) CloseHandle(hThread);
-        } else {
-            // First window or Shift bypass ΓÇö restore visibility
-            if (wasVisible)
-                ShowWindow(hwnd, SW_SHOW);
-        }
+    Wh_Log(L"Created window %p", hwnd);
+    g_creatingExplorerWindow = false;
+
+    if (hwnd) {
+        g_lastPrimaryWindow = hwnd;
+
+        // Redirect ΓÇö window stays hidden, thread will close it
+        auto* ri = new RedirectInfo{hwnd, existing};
+        HANDLE hThread =
+            CreateThread(nullptr, 0, RedirectThread, ri, 0, nullptr);
+        if (hThread) CloseHandle(hThread);
     }
 
     return hwnd;
 }
 
+// ---------------------------------------------------------------------------
+// Hook: ShowWindow
+// ---------------------------------------------------------------------------
+using ShowWindow_t = decltype(&ShowWindow);
+ShowWindow_t ShowWindow_Original;
+BOOL WINAPI ShowWindow_Hook(HWND hWnd, int nCmdShow) {
+    if (g_creatingExplorerWindow || hWnd == g_lastPrimaryWindow) {
+        WCHAR className[64];
+        if (GetClassNameW(hWnd, className, ARRAYSIZE(className)) &&
+            wcscmp(className, WC_CABINET) == 0) {
+            Wh_Log(
+                L"Blocked ShowWindow for %p %s", hWnd,
+                g_creatingExplorerWindow ? L"(creating)" : L"(last created)");
+            return TRUE;
+        }
+    }
+    return ShowWindow_Original(hWnd, nCmdShow);
+}
+
 // ---------------------------------------------------------------------------
 // Mod lifecycle
 // ---------------------------------------------------------------------------
 static void LoadSettings() {
-    g_enabled = Wh_GetIntSetting(L"enabled") != 0;
+    // No settings for now
 }
 
 BOOL Wh_ModInit() {
@@ -411,6 +439,9 @@ BOOL Wh_ModInit() {
     Wh_SetFunctionHook(reinterpret_cast<void*>(CreateWindowExW),
                         reinterpret_cast<void*>(CreateWindowExW_Hook),
                         reinterpret_cast<void**>(&CreateWindowExW_Original));
+    Wh_SetFunctionHook(reinterpret_cast<void*>(ShowWindow),
+                        reinterpret_cast<void*>(ShowWindow_Hook),
+                        reinterpret_cast<void**>(&ShowWindow_Original));
     return TRUE;
 }
 

It also removes the "enabled" option.

Full code:

Details

// ==WindhawkMod==
// @id              explorer-single-window-tabs
// @name            Explorer Single Window Tabs
// @description     Redirects new File Explorer windows to open as tabs in a single existing window
// @version         0.7
// @author          ALMAS CP
// @github          https://github.com/almas-cp
// @homepage        https://github.com/almas-cp
// @include         explorer.exe
// @compilerOptions -lole32 -loleaut32 -lshlwapi -lshell32 -luuid -lcomctl32
// ==/WindhawkMod==

// ==WindhawkModReadme==
/*
# Explorer Single Window Tabs

Prevents File Explorer from opening multiple windows. Every new folder is
redirected into a **new tab** inside the first (primary) Explorer window.

## How It Works

Uses internal Windows Shell COM interfaces and Explorer's undocumented
`WM_COMMAND` messages to programmatically create tabs and navigate them.

1. Hooks `CreateWindowExW` to detect new Explorer windows.
2. Extracts the navigation path via `IShellWindows` / `IWebBrowser2`.
3. Closes the new window and sends `WM_COMMAND 0xA21B` to
   `ShellTabWindowClass` to create a tab.
4. Navigates the new tab using `IWebBrowser2::Navigate2`.

## Bypass

Hold **Shift** while opening a folder to force a separate window.

## Requirements

- **Windows 11 22H2** or later (tabbed File Explorer).
*/
// ==/WindhawkModReadme==

#include <windhawk_utils.h>
#include <windows.h>
#include <shlobj.h>
#include <exdisp.h>
#include <shlwapi.h>
#include <string>

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#define WC_CABINET   L"CabinetWClass"
#define WC_SHELLTAB  L"ShellTabWindowClass"
#define CMD_NEW_TAB  0xA21B  // Internal Explorer "new tab" command

static HWND g_lastPrimaryWindow = nullptr;
static thread_local bool g_creatingExplorerWindow = false;

struct RedirectInfo {
    HWND newWindow;
    HWND primaryWindow;
};

// ---------------------------------------------------------------------------
// Find an existing visible CabinetWClass window (cross-process)
// ---------------------------------------------------------------------------
struct FindExplorerCtx {
    HWND exclude;
    HWND result;
};

static BOOL CALLBACK EnumFindExplorer(HWND hwnd, LPARAM lParam) {
    auto* ctx = reinterpret_cast<FindExplorerCtx*>(lParam);
    if (hwnd == ctx->exclude)
        return TRUE;

    WCHAR cls[64];
    if (GetClassNameW(hwnd, cls, _countof(cls)) &&
        wcscmp(cls, WC_CABINET) == 0 &&
        IsWindowVisible(hwnd)) {
        ctx->result = hwnd;
        return FALSE;
    }
    return TRUE;
}

static HWND FindExistingExplorer(HWND exclude) {
    FindExplorerCtx ctx{exclude, nullptr};
    EnumWindows(EnumFindExplorer, reinterpret_cast<LPARAM>(&ctx));
    return ctx.result;
}

// ---------------------------------------------------------------------------
// COM: Extract navigation path from an Explorer window by HWND
// ---------------------------------------------------------------------------
static std::wstring GetExplorerWindowPath(HWND targetHwnd) {
    std::wstring result;

    IShellWindows* pSW = nullptr;
    if (FAILED(CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL,
                                IID_PPV_ARGS(&pSW))))
        return result;

    long count = 0;
    pSW->get_Count(&count);

    for (long i = 0; i < count; i++) {
        VARIANT idx;
        VariantInit(&idx);
        idx.vt   = VT_I4;
        idx.lVal = i;

        IDispatch* pDisp = nullptr;
        if (FAILED(pSW->Item(idx, &pDisp)) || !pDisp)
            continue;

        IWebBrowser2* pBrowser = nullptr;
        if (SUCCEEDED(pDisp->QueryInterface(IID_PPV_ARGS(&pBrowser)))) {
            SHANDLE_PTR hwndPtr = 0;
            pBrowser->get_HWND(&hwndPtr);

            if (reinterpret_cast<HWND>(static_cast<ULONG_PTR>(hwndPtr)) ==
                targetHwnd) {
                // Method 1: LocationURL → filesystem path
                BSTR url = nullptr;
                if (SUCCEEDED(pBrowser->get_LocationURL(&url)) && url &&
                    wcslen(url) > 0) {
                    WCHAR path[MAX_PATH * 2];
                    DWORD pathLen = MAX_PATH * 2;
                    if (SUCCEEDED(PathCreateFromUrlW(url, path, &pathLen, 0)))
                        result = path;
                    else
                        result = url;
                    SysFreeString(url);
                } else {
                    if (url) SysFreeString(url);
                }

                // Method 2: PIDL via IShellBrowser (for special folders)
                if (result.empty()) {
                    IServiceProvider* pSP = nullptr;
                    if (SUCCEEDED(
                            pDisp->QueryInterface(IID_PPV_ARGS(&pSP)))) {
                        IShellBrowser* pSB = nullptr;
                        if (SUCCEEDED(pSP->QueryService(
                                SID_STopLevelBrowser, IID_PPV_ARGS(&pSB)))) {
                            IShellView* pSV = nullptr;
                            if (SUCCEEDED(
                                    pSB->QueryActiveShellView(&pSV))) {
                                IFolderView* pFV = nullptr;
                                if (SUCCEEDED(pSV->QueryInterface(
                                        IID_PPV_ARGS(&pFV)))) {
                                    IPersistFolder2* pPF = nullptr;
                                    if (SUCCEEDED(pFV->GetFolder(
                                            IID_PPV_ARGS(&pPF)))) {
                                        PIDLIST_ABSOLUTE pidl = nullptr;
                                        if (SUCCEEDED(
                                                pPF->GetCurFolder(&pidl))) {
                                            PWSTR name = nullptr;
                                            if (SUCCEEDED(
                                                    SHGetNameFromIDList(
                                                        pidl,
                                                        SIGDN_DESKTOPABSOLUTEPARSING,
                                                        &name))) {
                                                result = name;
                                                CoTaskMemFree(name);
                                            }
                                            CoTaskMemFree(pidl);
                                        }
                                        pPF->Release();
                                    }
                                    pFV->Release();
                                }
                                pSV->Release();
                            }
                            pSB->Release();
                        }
                        pSP->Release();
                    }
                }

                pBrowser->Release();
                pDisp->Release();
                break;
            }
            pBrowser->Release();
        }
        pDisp->Release();
    }
    pSW->Release();
    return result;
}

// ---------------------------------------------------------------------------
// COM: Count current IShellWindows entries
// ---------------------------------------------------------------------------
static int GetShellWindowCount() {
    IShellWindows* pSW = nullptr;
    if (FAILED(CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL,
                                IID_PPV_ARGS(&pSW))))
        return 0;
    long count = 0;
    pSW->get_Count(&count);
    pSW->Release();
    return static_cast<int>(count);
}

// ---------------------------------------------------------------------------
// COM: Navigate the most recently added tab to the target path
// ---------------------------------------------------------------------------
static bool NavigateNewTab(HWND primaryHwnd, const std::wstring& path,
                           int prevCount) {
    IShellWindows* pSW = nullptr;
    if (FAILED(CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL,
                                IID_PPV_ARGS(&pSW))))
        return false;

    long count = 0;
    pSW->get_Count(&count);

    bool success = false;
    for (long i = count - 1; i >= 0; i--) {
        VARIANT idx;
        VariantInit(&idx);
        idx.vt   = VT_I4;
        idx.lVal = i;

        IDispatch* pDisp = nullptr;
        if (FAILED(pSW->Item(idx, &pDisp)) || !pDisp)
            continue;

        IWebBrowser2* pBrowser = nullptr;
        if (SUCCEEDED(pDisp->QueryInterface(IID_PPV_ARGS(&pBrowser)))) {
            SHANDLE_PTR hwndPtr = 0;
            pBrowser->get_HWND(&hwndPtr);

            if (reinterpret_cast<HWND>(static_cast<ULONG_PTR>(hwndPtr)) ==
                primaryHwnd) {
                // Only navigate empty/home tabs to avoid clobbering
                BSTR url = nullptr;
                pBrowser->get_LocationURL(&url);
                bool isEmptyOrNew =
                    (!url || wcslen(url) == 0 || i >= prevCount);
                if (url) SysFreeString(url);

                if (isEmptyOrNew) {
                    VARIANT target;
                    VariantInit(&target);
                    target.vt      = VT_BSTR;
                    target.bstrVal = SysAllocString(path.c_str());

                    VARIANT empty;
                    VariantInit(&empty);

                    HRESULT hr = pBrowser->Navigate2(&target, &empty,
                                                      &empty, &empty, &empty);
                    SysFreeString(target.bstrVal);

                    Wh_Log(L"Navigate2 [%d] hr=0x%08X", static_cast<int>(i),
                           hr);
                    success = SUCCEEDED(hr);

                    pBrowser->Release();
                    pDisp->Release();
                    break;
                }
            }
            pBrowser->Release();
        }
        pDisp->Release();
    }
    pSW->Release();
    return success;
}

// ---------------------------------------------------------------------------
// Redirect thread: close new window → create tab → navigate
// ---------------------------------------------------------------------------
static DWORD WINAPI RedirectThread(LPVOID param) {
    auto* info      = static_cast<RedirectInfo*>(param);
    HWND  newWnd    = info->newWindow;
    HWND  primary   = info->primaryWindow;
    delete info;

    if (FAILED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED)))
        return 1;

    // Wait for the new window to finish navigating (max ~6s)
    std::wstring path;
    for (int attempt = 0; attempt < 60 && IsWindow(newWnd); attempt++) {
        Sleep(100);
        path = GetExplorerWindowPath(newWnd);
        if (!path.empty()) break;
    }

    // Re-verify primary is still alive
    if (!IsWindow(primary))
        primary = FindExistingExplorer(newWnd);

    if (path.empty() || !primary || !IsWindow(primary) ||
        newWnd == primary) {
        CoUninitialize();
        return 0;
    }

    // Snapshot tab count before creating a new tab
    int prevCount = GetShellWindowCount() - 1;

    // Close the duplicate window (already hidden via WS_VISIBLE strip)
    PostMessageW(newWnd, WM_CLOSE, 0, 0);

    // Create a new tab via internal WM_COMMAND
    HWND shellTab = FindWindowExW(primary, nullptr, WC_SHELLTAB, nullptr);
    if (!shellTab) {
        Wh_Log(L"ShellTabWindowClass not found on primary %p", primary);
        CoUninitialize();
        return 0;
    }
    PostMessageW(shellTab, WM_COMMAND, CMD_NEW_TAB, 0);

    // Wait for the new tab to register in IShellWindows (max ~3s)
    bool navigated = false;
    for (int wait = 0; wait < 30; wait++) {
        Sleep(100);
        if (GetShellWindowCount() > prevCount) {
            navigated = NavigateNewTab(primary, path, prevCount);
            break;
        }
    }

    // Fallback: try navigating the last matching entry
    if (!navigated)
        navigated = NavigateNewTab(primary, path, 0);

    // Bring primary window to foreground
    if (IsIconic(primary))
        ShowWindow(primary, SW_RESTORE);

    DWORD pid = 0;
    GetWindowThreadProcessId(primary, &pid);
    AllowSetForegroundWindow(pid);
    SetForegroundWindow(primary);

    Wh_Log(L"%s → %s", navigated ? L"Redirected" : L"Failed", path.c_str());

    CoUninitialize();
    return 0;
}

// ---------------------------------------------------------------------------
// Hook: CreateWindowExW
// ---------------------------------------------------------------------------
using CreateWindowExW_t = decltype(&CreateWindowExW);
CreateWindowExW_t CreateWindowExW_Original;

HWND WINAPI CreateWindowExW_Hook(DWORD dwExStyle, LPCWSTR lpClassName,
                                  LPCWSTR lpWindowName, DWORD dwStyle,
                                  int X, int Y, int nWidth, int nHeight,
                                  HWND hWndParent, HMENU hMenu,
                                  HINSTANCE hInstance, LPVOID lpParam) {
    auto original = [=]() {
        return CreateWindowExW_Original(dwExStyle, lpClassName, lpWindowName,
                                        dwStyle, X, Y, nWidth, nHeight,
                                        hWndParent, hMenu, hInstance, lpParam);
    };

    bool isCabinet = lpClassName && !IS_INTRESOURCE(lpClassName) &&
                     wcscmp(lpClassName, WC_CABINET) == 0;
    if (!isCabinet) {
        return original();
    }

    // Bypass if Shift is held
    if (GetAsyncKeyState(VK_SHIFT) & 0x8000) {
        return original();
    }

    HWND existing = FindExistingExplorer(nullptr);

    // First window
    if (!existing) {
        return original();
    }

    // Strip WS_VISIBLE so the window is never shown
    dwStyle &= ~WS_VISIBLE;

    Wh_Log(L"Creating Explorer window");
    g_creatingExplorerWindow = true;

    HWND hwnd = CreateWindowExW_Original(dwExStyle, lpClassName, lpWindowName,
                                          dwStyle, X, Y, nWidth, nHeight,
                                          hWndParent, hMenu, hInstance,
                                          lpParam);

    Wh_Log(L"Created window %p", hwnd);
    g_creatingExplorerWindow = false;

    if (hwnd) {
        g_lastPrimaryWindow = hwnd;

        // Redirect — window stays hidden, thread will close it
        auto* ri = new RedirectInfo{hwnd, existing};
        HANDLE hThread =
            CreateThread(nullptr, 0, RedirectThread, ri, 0, nullptr);
        if (hThread) CloseHandle(hThread);
    }

    return hwnd;
}

// ---------------------------------------------------------------------------
// Hook: ShowWindow
// ---------------------------------------------------------------------------
using ShowWindow_t = decltype(&ShowWindow);
ShowWindow_t ShowWindow_Original;
BOOL WINAPI ShowWindow_Hook(HWND hWnd, int nCmdShow) {
    if (g_creatingExplorerWindow || hWnd == g_lastPrimaryWindow) {
        WCHAR className[64];
        if (GetClassNameW(hWnd, className, ARRAYSIZE(className)) &&
            wcscmp(className, WC_CABINET) == 0) {
            Wh_Log(
                L"Blocked ShowWindow for %p %s", hWnd,
                g_creatingExplorerWindow ? L"(creating)" : L"(last created)");
            return TRUE;
        }
    }
    return ShowWindow_Original(hWnd, nCmdShow);
}

// ---------------------------------------------------------------------------
// Mod lifecycle
// ---------------------------------------------------------------------------
static void LoadSettings() {
    // No settings for now
}

BOOL Wh_ModInit() {
    LoadSettings();
    Wh_SetFunctionHook(reinterpret_cast<void*>(CreateWindowExW),
                        reinterpret_cast<void*>(CreateWindowExW_Hook),
                        reinterpret_cast<void**>(&CreateWindowExW_Original));
    Wh_SetFunctionHook(reinterpret_cast<void*>(ShowWindow),
                        reinterpret_cast<void*>(ShowWindow_Hook),
                        reinterpret_cast<void**>(&ShowWindow_Original));
    return TRUE;
}

void Wh_ModUninit() {}

void Wh_ModSettingsChanged() {
    LoadSettings();
}

@ahzvenol
Copy link
Copy Markdown
Contributor

ahzvenol commented Mar 3, 2026

Glad to see this mod! I'd like to use it to replace ExplorerTabUtility, since I only really need its basic features.
However, I tried this mod and it seems to also capture Control Panel windows, which prevents them from displaying. We need to handle cases where Control Panel is opened while File Explorer is already running, or vice versa.

@ahzvenol
Copy link
Copy Markdown
Contributor

ahzvenol commented Mar 4, 2026

Following up on my previous comment — tested the latest commit. Opening Control Panel while File Explorer is already running now works, but the reverse — opening File Explorer while Control Panel is already open — still doesn't work as expected.

@ahzvenol
Copy link
Copy Markdown
Contributor

ahzvenol commented Mar 4, 2026

The Control Panel issue is resolved now! 🙌

Though I found a couple more issues 😅:

  1. When a File Explorer window is already open, clicking the Recycle Bin icon on the desktop doesn't open it.
  2. Dragging a tab out to create a separate window (which seems possible on newer versions like 24H2) gets intercepted — the detached window immediately gets merged back.

@ahzvenol
Copy link
Copy Markdown
Contributor

ahzvenol commented Mar 5, 2026

Tested again. Recycle Bin now opens in a separate window — is this expected? Since Recycle Bin can be a tab, I personally think merging it into the single window would be better.
Also, after merging the Recycle Bin into the single window and closing that tab, I can't reopen Recycle Bin until I navigate to another folder in the remaining tab. After testing, this seems to be a Windows issue.

@almas-cp
Copy link
Copy Markdown
Contributor Author

almas-cp commented Mar 5, 2026

@ahzvenol
First of all, thanks for thoroughly testing the script and helping make it perfect. And yes, that is expected behavior. What are your suggestions?

@ahzvenol
Copy link
Copy Markdown
Contributor

ahzvenol commented Mar 5, 2026

As I mentioned earlier, I'd like to use this mod to replace ExplorerTabUtility. In terms of basic functionality, this mod already works better than ExplorerTabUtility, so I'm hoping it behaves the same way — specifically, automatically merging the Recycle Bin into the single window as a tab. If you have a different approach in mind, adding an option for this might be a good compromise.

A minor suggestion on the readme structure: consider moving Requirements and Bypass sections higher up (or including the requirements in the mod name), and moving How It Works to the end, removing it, or putting it in code comments — regular users don't usually need to know the implementation details.

But of course, these are just my personal thoughts — the decision is yours.

@ahzvenol
Copy link
Copy Markdown
Contributor

ahzvenol commented Mar 5, 2026

Regarding the detached tab issue, I tried implementing a heuristic approach similar to ExplorerTabUtility. The logic is to assume the user is dragging a tab out if a new window opens immediately after a tab with the exact same path was closed.

I also noticed a race condition when opening multiple windows quickly. I replaced g_lastPrimaryWindow with g_hiddenWindows and added a mutex lock to fix this. However, in this case, some windows may still flash briefly before being hidden. Since this is not a common use case, we can investigate it later.

Here is the modified code:

Click to expand code
// ==WindhawkMod==
// @id              explorer-single-window-tabs
// @name            Explorer Single Window Tabs
// @description     Redirects new File Explorer windows to open as tabs in a single existing window
// @version         0.9
// @author          ALMAS CP
// @github          https://github.com/almas-cp
// @homepage        https://github.com/almas-cp
// @include         explorer.exe
// @compilerOptions -lole32 -loleaut32 -lshlwapi -lshell32 -luuid -lcomctl32
// ==/WindhawkMod==

// ==WindhawkModReadme==
/*
# Explorer Single Window Tabs

Prevents File Explorer from opening multiple windows. Every new folder is
redirected into a **new tab** inside the first (primary) Explorer window.

**Requires Windows 11 22H2 or later** (tabbed File Explorer).

## Bypass

Hold **Shift** while opening a folder to force a separate window.

## Known Limitations

- Control Panel always opens in its own window (by design).
- The tab detaching detection relies on timing. In rare edge cases where
  you close a tab and immediately open the same folder (within 1 seconds),
  it may incorrectly allow a separate window instead of opening as a tab.

*/
// ==/WindhawkModReadme==

#include <windhawk_utils.h>
#include <windows.h>
#include <shlobj.h>
#include <exdisp.h>
#include <shlwapi.h>
#include <string>
#include <vector>
#include <unordered_set>
#include <mutex>
#include <algorithm>

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#define WC_CABINET   L"CabinetWClass"
#define WC_SHELLTAB  L"ShellTabWindowClass"
#define CMD_NEW_TAB  0xA21B  // Internal Explorer "new tab" command

// Time window for detecting detached tabs
#define DETACH_TIME_WINDOW_MS 1000

// CLSIDs for special windows that should NOT be redirected into tabs
static const WCHAR kControlPanelCLSID[] =
    L"::{26EE0668-A00A-44D7-9371-BEB064C98683}";

static std::unordered_set<HWND> g_hiddenWindows;
static std::mutex g_hiddenMutex;

// Tab creation lock to prevent multi-threading race conditions
static std::mutex g_tabCreationMutex;

static thread_local bool g_creatingExplorerWindow = false;

// ---------------------------------------------------------------------------
// Track windows closed by the mod to ignore them during OnQuit
// ---------------------------------------------------------------------------
struct IgnoredHwndRecord {
    HWND hwnd;
    DWORD timestamp;
};
static std::vector<IgnoredHwndRecord> g_ignoredHwnds;
static std::mutex g_ignoredHwndsMutex;

static void IgnoreWindowClose(HWND hwnd) {
    std::lock_guard<std::mutex> lock(g_ignoredHwndsMutex);
    DWORD now = GetTickCount();
    
    // Cleanup old entries (> 5000ms) to prevent memory leak
    g_ignoredHwnds.erase(
        std::remove_if(g_ignoredHwnds.begin(), g_ignoredHwnds.end(),
            [now](const IgnoredHwndRecord& r) {
                return (now - r.timestamp) > 5000;
            }),
        g_ignoredHwnds.end());
        
    g_ignoredHwnds.push_back({hwnd, now});
}

static bool CheckAndClearIgnoredWindow(HWND hwnd) {
    std::lock_guard<std::mutex> lock(g_ignoredHwndsMutex);
    for (auto it = g_ignoredHwnds.begin(); it != g_ignoredHwnds.end(); ++it) {
        if (it->hwnd == hwnd) {
            g_ignoredHwnds.erase(it);
            return true;
        }
    }
    return false;
}

// ---------------------------------------------------------------------------
// Track recently closed tabs to detect tab detachment
// ---------------------------------------------------------------------------
struct ClosedTabRecord {
    std::wstring path;
    DWORD closedTime;
};

static std::vector<ClosedTabRecord> g_closedTabs;
static std::mutex g_closedTabsMutex;

// Add a closed tab record
static void RecordClosedTab(const std::wstring& path) {
    if (path.empty()) return;
    
    std::lock_guard<std::mutex> lock(g_closedTabsMutex);
    
    // Remove entries older than DETACH_TIME_WINDOW_MS
    DWORD now = GetTickCount();
    g_closedTabs.erase(
        std::remove_if(g_closedTabs.begin(), g_closedTabs.end(),
            [now](const ClosedTabRecord& r) {
                return (now - r.closedTime) > DETACH_TIME_WINDOW_MS;
            }),
        g_closedTabs.end());
    
    // Add new record
    g_closedTabs.push_back({path, now});
    
    Wh_Log(L"Recorded closed tab: %s", path.c_str());
}

// Check whether the path matches a recently closed tab
static bool IsRecentlyClosedTab(const std::wstring& path) {
    if (path.empty()) return false;
    
    std::lock_guard<std::mutex> lock(g_closedTabsMutex);
    
    DWORD now = GetTickCount();
    
    // Search from newest to oldest
    for (auto it = g_closedTabs.rbegin(); it != g_closedTabs.rend(); ++it) {
        // Check time window
        if ((now - it->closedTime) > DETACH_TIME_WINDOW_MS) {
            break;  // All remaining entries are too old
        }
        
        if (_wcsicmp(it->path.c_str(), path.c_str()) == 0) {
            Wh_Log(L"Detected detached tab: %s", path.c_str());
            
            // Remove the matched entry so it's not matched again
            g_closedTabs.erase((it + 1).base());
            return true;
        }
    }
    
    return false;
}

static void AddHiddenWindow(HWND hwnd) {
    std::lock_guard<std::mutex> lock(g_hiddenMutex);
    g_hiddenWindows.insert(hwnd);
}

// Safely remove a hidden window
static void RemoveHiddenWindow(HWND hwnd) {
    std::lock_guard<std::mutex> lock(g_hiddenMutex);
    g_hiddenWindows.erase(hwnd);
}

// Check if a window is in the hidden list
static bool IsHiddenWindow(HWND hwnd) {
    std::lock_guard<std::mutex> lock(g_hiddenMutex);
    return g_hiddenWindows.contains(hwnd);
}

// Forward declaration (needed by RedirectThread before ShowWindow hook)
using ShowWindow_t = decltype(&ShowWindow);
static ShowWindow_t ShowWindow_Original;

struct RedirectInfo {
    HWND newWindow;
    HWND primaryWindow;
};

// ---------------------------------------------------------------------------
// Check whether a path is a normal folder that can be opened as a tab.
// Returns false for Control Panel and other special shell locations.
// ---------------------------------------------------------------------------
static bool IsRedirectablePath(const std::wstring& path) {
    if (path.find(kControlPanelCLSID) != std::wstring::npos)
        return false;

    if (path.compare(0, 9, L"shell:::{" ) == 0)
        return false;

    return true;
}

// ---------------------------------------------------------------------------
// Restore a window that was hidden for redirect but needs to be shown
// (e.g. Control Panel, detached tabs, or abort cases).
// ---------------------------------------------------------------------------
static void AbortRedirect(HWND hwnd) {
    RemoveHiddenWindow(hwnd);

    if (!IsWindow(hwnd))
        return;

    // Remove transparency and restore the window
    LONG exStyle = GetWindowLongW(hwnd, GWL_EXSTYLE);
    if (exStyle & WS_EX_LAYERED) {
        SetWindowLongW(hwnd, GWL_EXSTYLE, exStyle & ~WS_EX_LAYERED);
    }
    SetWindowPos(hwnd, nullptr, 0, 0, 0, 0,
                 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
                 SWP_NOACTIVATE | SWP_FRAMECHANGED);
    ShowWindow_Original(hwnd, SW_SHOW);
}

// ---------------------------------------------------------------------------
// Find an existing visible File Explorer window (not Control Panel).
// Uses IShellWindows COM to check each window's actual path.
// ---------------------------------------------------------------------------
static HWND FindExistingExplorer(HWND exclude) {
    IShellWindows* pSW = nullptr;
    if (FAILED(CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL,
                                IID_PPV_ARGS(&pSW))))
        return nullptr;

    long count = 0;
    pSW->get_Count(&count);

    HWND result = nullptr;
    for (long i = 0; i < count; i++) {
        VARIANT idx;
        VariantInit(&idx);
        idx.vt   = VT_I4;
        idx.lVal = i;

        IDispatch* pDisp = nullptr;
        if (FAILED(pSW->Item(idx, &pDisp)) || !pDisp)
            continue;

        IWebBrowser2* pBrowser = nullptr;
        if (SUCCEEDED(pDisp->QueryInterface(IID_PPV_ARGS(&pBrowser)))) {
            SHANDLE_PTR hwndPtr = 0;
            pBrowser->get_HWND(&hwndPtr);
            HWND hwnd =
                reinterpret_cast<HWND>(static_cast<ULONG_PTR>(hwndPtr));

            if (hwnd && hwnd != exclude && !IsHiddenWindow(hwnd) && IsWindowVisible(hwnd)) {
                // Get the window's path to verify it's real File Explorer
                std::wstring path;

                BSTR url = nullptr;
                if (SUCCEEDED(pBrowser->get_LocationURL(&url)) && url &&
                    wcslen(url) > 0) {
                    WCHAR filePath[MAX_PATH * 2];
                    DWORD len = MAX_PATH * 2;
                    if (SUCCEEDED(PathCreateFromUrlW(url, filePath, &len, 0)))
                        path = filePath;
                    else
                        path = url;
                    SysFreeString(url);
                } else {
                    if (url) SysFreeString(url);

                    // Fallback: PIDL path (for "This PC", etc.)
                    IServiceProvider* pSP = nullptr;
                    if (SUCCEEDED(
                            pDisp->QueryInterface(IID_PPV_ARGS(&pSP)))) {
                        IShellBrowser* pSB = nullptr;
                        if (SUCCEEDED(pSP->QueryService(
                                SID_STopLevelBrowser, IID_PPV_ARGS(&pSB)))) {
                            IShellView* pSV = nullptr;
                            if (SUCCEEDED(
                                    pSB->QueryActiveShellView(&pSV))) {
                                IFolderView* pFV = nullptr;
                                if (SUCCEEDED(pSV->QueryInterface(
                                        IID_PPV_ARGS(&pFV)))) {
                                    IPersistFolder2* pPF = nullptr;
                                    if (SUCCEEDED(pFV->GetFolder(
                                            IID_PPV_ARGS(&pPF)))) {
                                        PIDLIST_ABSOLUTE pidl = nullptr;
                                        if (SUCCEEDED(
                                                pPF->GetCurFolder(&pidl))) {
                                            PWSTR name = nullptr;
                                            if (SUCCEEDED(
                                                    SHGetNameFromIDList(
                                                        pidl,
                                                        SIGDN_DESKTOPABSOLUTEPARSING,
                                                        &name))) {
                                                path = name;
                                                CoTaskMemFree(name);
                                            }
                                            CoTaskMemFree(pidl);
                                        }
                                        pPF->Release();
                                    }
                                    pFV->Release();
                                }
                                pSV->Release();
                            }
                            pSB->Release();
                        }
                        pSP->Release();
                    }
                }

                // Only use this window if its path is redirectable
                // (i.e. not Control Panel or other special shell locations)
                if (!path.empty() && IsRedirectablePath(path)) {
                    result = hwnd;
                    pBrowser->Release();
                    pDisp->Release();
                    break;
                }
            }
            pBrowser->Release();
        }
        pDisp->Release();
    }
    pSW->Release();
    return result;
}

// ---------------------------------------------------------------------------
// COM: Extract navigation path from an Explorer window by HWND
// ---------------------------------------------------------------------------
static std::wstring GetExplorerWindowPath(HWND targetHwnd) {
    std::wstring result;

    IShellWindows* pSW = nullptr;
    if (FAILED(CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL,
                                IID_PPV_ARGS(&pSW))))
        return result;

    long count = 0;
    pSW->get_Count(&count);

    for (long i = 0; i < count; i++) {
        VARIANT idx;
        VariantInit(&idx);
        idx.vt   = VT_I4;
        idx.lVal = i;

        IDispatch* pDisp = nullptr;
        if (FAILED(pSW->Item(idx, &pDisp)) || !pDisp)
            continue;

        IWebBrowser2* pBrowser = nullptr;
        if (SUCCEEDED(pDisp->QueryInterface(IID_PPV_ARGS(&pBrowser)))) {
            SHANDLE_PTR hwndPtr = 0;
            pBrowser->get_HWND(&hwndPtr);

            if (reinterpret_cast<HWND>(static_cast<ULONG_PTR>(hwndPtr)) ==
                targetHwnd) {
                // Method 1: LocationURL → filesystem path
                BSTR url = nullptr;
                if (SUCCEEDED(pBrowser->get_LocationURL(&url)) && url &&
                    wcslen(url) > 0) {
                    WCHAR path[MAX_PATH * 2];
                    DWORD pathLen = MAX_PATH * 2;
                    if (SUCCEEDED(PathCreateFromUrlW(url, path, &pathLen, 0)))
                        result = path;
                    else
                        result = url;
                    SysFreeString(url);
                } else {
                    if (url) SysFreeString(url);
                }

                // Method 2: PIDL via IShellBrowser (for special folders)
                if (result.empty()) {
                    IServiceProvider* pSP = nullptr;
                    if (SUCCEEDED(
                            pDisp->QueryInterface(IID_PPV_ARGS(&pSP)))) {
                        IShellBrowser* pSB = nullptr;
                        if (SUCCEEDED(pSP->QueryService(
                                SID_STopLevelBrowser, IID_PPV_ARGS(&pSB)))) {
                            IShellView* pSV = nullptr;
                            if (SUCCEEDED(
                                    pSB->QueryActiveShellView(&pSV))) {
                                IFolderView* pFV = nullptr;
                                if (SUCCEEDED(pSV->QueryInterface(
                                        IID_PPV_ARGS(&pFV)))) {
                                    IPersistFolder2* pPF = nullptr;
                                    if (SUCCEEDED(pFV->GetFolder(
                                            IID_PPV_ARGS(&pPF)))) {
                                        PIDLIST_ABSOLUTE pidl = nullptr;
                                        if (SUCCEEDED(
                                                pPF->GetCurFolder(&pidl))) {
                                            PWSTR name = nullptr;
                                            if (SUCCEEDED(
                                                    SHGetNameFromIDList(
                                                        pidl,
                                                        SIGDN_DESKTOPABSOLUTEPARSING,
                                                        &name))) {
                                                result = name;
                                                CoTaskMemFree(name);
                                            }
                                            CoTaskMemFree(pidl);
                                        }
                                        pPF->Release();
                                    }
                                    pFV->Release();
                                }
                                pSV->Release();
                            }
                            pSB->Release();
                        }
                        pSP->Release();
                    }
                }

                pBrowser->Release();
                pDisp->Release();
                break;
            }
            pBrowser->Release();
        }
        pDisp->Release();
    }
    pSW->Release();
    return result;
}

// ---------------------------------------------------------------------------
// COM: Count current IShellWindows entries
// ---------------------------------------------------------------------------
static int GetShellWindowCount() {
    IShellWindows* pSW = nullptr;
    if (FAILED(CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL,
                                IID_PPV_ARGS(&pSW))))
        return 0;
    long count = 0;
    pSW->get_Count(&count);
    pSW->Release();
    return static_cast<int>(count);
}

// ---------------------------------------------------------------------------
// COM: Navigate the most recently added tab to the target path
// ---------------------------------------------------------------------------
static bool NavigateNewTab(HWND primaryHwnd, const std::wstring& path,
                           int prevCount) {
    IShellWindows* pSW = nullptr;
    if (FAILED(CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL,
                                IID_PPV_ARGS(&pSW))))
        return false;

    long count = 0;
    pSW->get_Count(&count);

    bool success = false;
    for (long i = count - 1; i >= 0; i--) {
        VARIANT idx;
        VariantInit(&idx);
        idx.vt   = VT_I4;
        idx.lVal = i;

        IDispatch* pDisp = nullptr;
        if (FAILED(pSW->Item(idx, &pDisp)) || !pDisp)
            continue;

        IWebBrowser2* pBrowser = nullptr;
        if (SUCCEEDED(pDisp->QueryInterface(IID_PPV_ARGS(&pBrowser)))) {
            SHANDLE_PTR hwndPtr = 0;
            pBrowser->get_HWND(&hwndPtr);

            if (reinterpret_cast<HWND>(static_cast<ULONG_PTR>(hwndPtr)) ==
                primaryHwnd) {
                // Only navigate empty/home tabs to avoid clobbering
                BSTR url = nullptr;
                pBrowser->get_LocationURL(&url);
                bool isEmptyOrNew =
                    (!url || wcslen(url) == 0 || i >= prevCount);
                if (url) SysFreeString(url);

                if (isEmptyOrNew) {
                    VARIANT target;
                    VariantInit(&target);
                    VARIANT empty;
                    VariantInit(&empty);
                    HRESULT hr = E_FAIL;

                    // CLSID paths (e.g. `::{...}`) need PIDL-based navigation
                    // because Navigate2 with a BSTR doesn't handle them.
                    if (path.size() >= 3 && path[0] == L':' &&
                        path[1] == L':' && path[2] == L'{') {
                        PIDLIST_ABSOLUTE pidl = nullptr;
                        if (SUCCEEDED(SHParseDisplayName(path.c_str(),
                                nullptr, &pidl, 0, nullptr)) && pidl) {
                            UINT pidlSize = ILGetSize(pidl);
                            SAFEARRAY* sa = SafeArrayCreateVector(
                                VT_UI1, 0, pidlSize);
                            if (sa) {
                                void* data = nullptr;
                                SafeArrayAccessData(sa, &data);
                                memcpy(data, pidl, pidlSize);
                                SafeArrayUnaccessData(sa);

                                target.vt     = VT_ARRAY | VT_UI1;
                                target.parray = sa;
                                hr = pBrowser->Navigate2(&target, &empty,
                                                          &empty, &empty,
                                                          &empty);
                                SafeArrayDestroy(sa);
                            }
                            CoTaskMemFree(pidl);
                        }
                    } else {
                        // Regular filesystem path — BSTR works fine
                        target.vt      = VT_BSTR;
                        target.bstrVal = SysAllocString(path.c_str());
                        hr = pBrowser->Navigate2(&target, &empty,
                                                  &empty, &empty, &empty);
                        SysFreeString(target.bstrVal);
                    }

                    Wh_Log(L"Navigate2 [%d] hr=0x%08X", static_cast<int>(i),
                           hr);
                    success = SUCCEEDED(hr);

                    pBrowser->Release();
                    pDisp->Release();
                    break;
                }
            }
            pBrowser->Release();
        }
        pDisp->Release();
    }
    pSW->Release();
    return success;
}

// ---------------------------------------------------------------------------
// Track tab closures via DWebBrowserEvents2::OnQuit
// ---------------------------------------------------------------------------
struct TabEventSink : public DWebBrowserEvents2 {
    IWebBrowser2* browser = nullptr;
    DWORD cookie = 0;
    IConnectionPoint* cp = nullptr;
    ULONG refCount = 1;
    
    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv) override {
        if (riid == IID_IUnknown || riid == IID_IDispatch || 
            riid == DIID_DWebBrowserEvents2) {
            *ppv = this;
            AddRef();
            return S_OK;
        }
        *ppv = nullptr;
        return E_NOINTERFACE;
    }
    
    ULONG STDMETHODCALLTYPE AddRef() override {
        return ++refCount;
    }
    
    ULONG STDMETHODCALLTYPE Release() override {
        ULONG count = --refCount;
        if (count == 0) {
            delete this;
        }
        return count;
    }
    
    HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT* pctinfo) override {
        *pctinfo = 0;
        return S_OK;
    }
    
    HRESULT STDMETHODCALLTYPE GetTypeInfo(UINT, LCID, ITypeInfo**) override {
        return E_NOTIMPL;
    }
    
    HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID, LPOLESTR*, UINT, LCID, DISPID*) override {
        return E_NOTIMPL;
    }
    
    HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID, LCID, WORD,
                                      DISPPARAMS*, VARIANT*, EXCEPINFO*, UINT*) override {
        if (dispIdMember == 253) {  // ONQUIT
            OnQuit();
        }
        return S_OK;
    }
    
    void OnQuit() {
        if (!browser) return;
        
        // Check if this window was explicitly closed by our mod
        SHANDLE_PTR hwndPtr = 0;
        if (SUCCEEDED(browser->get_HWND(&hwndPtr))) {
            HWND hwnd = reinterpret_cast<HWND>(static_cast<ULONG_PTR>(hwndPtr));
            if (hwnd && CheckAndClearIgnoredWindow(hwnd)) {
                Wh_Log(L"Ignoring OnQuit for mod-closed window: %p", hwnd);
                return;
            }
            
            // Only record when the parent window is still visible.
            if (!hwnd || !IsWindowVisible(hwnd)) {
                Wh_Log(L"Ignoring OnQuit for closing/hidden window: %p", hwnd);
                return;
            }
        } else {
            // Can't get HWND, window likely destroyed
            return;
        }
        
        std::wstring path;
        
        // Get path before the browser is destroyed
        BSTR url = nullptr;
        if (SUCCEEDED(browser->get_LocationURL(&url)) && url && wcslen(url) > 0) {
            WCHAR filePath[MAX_PATH * 2];
            DWORD len = MAX_PATH * 2;
            if (SUCCEEDED(PathCreateFromUrlW(url, filePath, &len, 0)))
                path = filePath;
            else
                path = url;
            SysFreeString(url);
        } else {
            if (url) SysFreeString(url);
            
            // Fallback: PIDL path
            IDispatch* pDisp = nullptr;
            if (SUCCEEDED(browser->QueryInterface(IID_PPV_ARGS(&pDisp)))) {
                IServiceProvider* pSP = nullptr;
                if (SUCCEEDED(pDisp->QueryInterface(IID_PPV_ARGS(&pSP)))) {
                    IShellBrowser* pSB = nullptr;
                    if (SUCCEEDED(pSP->QueryService(SID_STopLevelBrowser, IID_PPV_ARGS(&pSB)))) {
                        IShellView* pSV = nullptr;
                        if (SUCCEEDED(pSB->QueryActiveShellView(&pSV))) {
                            IFolderView* pFV = nullptr;
                            if (SUCCEEDED(pSV->QueryInterface(IID_PPV_ARGS(&pFV)))) {
                                IPersistFolder2* pPF = nullptr;
                                if (SUCCEEDED(pFV->GetFolder(IID_PPV_ARGS(&pPF)))) {
                                    PIDLIST_ABSOLUTE pidl = nullptr;
                                    if (SUCCEEDED(pPF->GetCurFolder(&pidl))) {
                                        PWSTR name = nullptr;
                                        if (SUCCEEDED(SHGetNameFromIDList(pidl,
                                                SIGDN_DESKTOPABSOLUTEPARSING, &name))) {
                                            path = name;
                                            CoTaskMemFree(name);
                                        }
                                        CoTaskMemFree(pidl);
                                    }
                                    pPF->Release();
                                }
                                pFV->Release();
                            }
                            pSV->Release();
                        }
                        pSB->Release();
                    }
                    pSP->Release();
                }
                pDisp->Release();
            }
        }
        
        if (!path.empty()) {
            RecordClosedTab(path);
        }
    }
    
    void Disconnect() {
        if (cp && cookie) {
            cp->Unadvise(cookie);
            cp->Release();
            cp = nullptr;
            cookie = 0;
        }
        if (browser) {
            browser->Release();
            browser = nullptr;
        }
    }
};

static std::vector<TabEventSink*> g_eventSinks;
static std::mutex g_eventSinksMutex;

// Connect event sink to a browser instance
static void ConnectEventSink(IWebBrowser2* browser) {
    IConnectionPointContainer* cpc = nullptr;
    if (FAILED(browser->QueryInterface(IID_PPV_ARGS(&cpc))))
        return;
    
    IConnectionPoint* cp = nullptr;
    if (FAILED(cpc->FindConnectionPoint(DIID_DWebBrowserEvents2, &cp))) {
        cpc->Release();
        return;
    }
    cpc->Release();
    
    auto* sink = new TabEventSink();
    sink->browser = browser;
    browser->AddRef();
    sink->cp = cp;
    
    DWORD cookie = 0;
    if (SUCCEEDED(cp->Advise(sink, &cookie))) {
        sink->cookie = cookie;
        
        std::lock_guard<std::mutex> lock(g_eventSinksMutex);
        g_eventSinks.push_back(sink);
        
        Wh_Log(L"Connected event sink to browser");
    } else {
        sink->Disconnect();
        sink->Release();
    }
}

// Scan and connect to all current Explorer windows
static void ConnectToExistingWindows() {
    IShellWindows* pSW = nullptr;
    if (FAILED(CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL,
                                IID_PPV_ARGS(&pSW))))
        return;
    
    long count = 0;
    pSW->get_Count(&count);
    
    for (long i = 0; i < count; i++) {
        VARIANT idx;
        VariantInit(&idx);
        idx.vt   = VT_I4;
        idx.lVal = i;
        
        IDispatch* pDisp = nullptr;
        if (FAILED(pSW->Item(idx, &pDisp)) || !pDisp)
            continue;
        
        IWebBrowser2* pBrowser = nullptr;
        if (SUCCEEDED(pDisp->QueryInterface(IID_PPV_ARGS(&pBrowser)))) {
            ConnectEventSink(pBrowser);
            pBrowser->Release();
        }
        pDisp->Release();
    }
    
    pSW->Release();
}

// Clean up event sinks
static void DisconnectAllEventSinks() {
    std::lock_guard<std::mutex> lock(g_eventSinksMutex);
    for (auto* sink : g_eventSinks) {
        sink->Disconnect();
        sink->Release();
    }
    g_eventSinks.clear();
}

// ---------------------------------------------------------------------------
// ShellWindowsEventSink: auto-connect event sinks to new windows
// ---------------------------------------------------------------------------
class ShellWindowsEventSink : public DShellWindowsEvents {
public:
    ULONG refCount = 1;
    IConnectionPoint* cp = nullptr;
    DWORD cookie = 0;
    
    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv) override {
        if (riid == IID_IUnknown || riid == IID_IDispatch || riid == DIID_DShellWindowsEvents) {
            *ppv = this; AddRef(); return S_OK;
        }
        *ppv = nullptr; return E_NOINTERFACE;
    }
    ULONG STDMETHODCALLTYPE AddRef() override { return ++refCount; }
    ULONG STDMETHODCALLTYPE Release() override {
        ULONG count = --refCount;
        if (count == 0) delete this;
        return count;
    }
    HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT* pctinfo) override { *pctinfo = 0; return S_OK; }
    HRESULT STDMETHODCALLTYPE GetTypeInfo(UINT, LCID, ITypeInfo**) override { return E_NOTIMPL; }
    HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID, LPOLESTR*, UINT, LCID, DISPID*) override { return E_NOTIMPL; }
    
    HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID, LCID, WORD,
                                      DISPPARAMS*, VARIANT*, EXCEPINFO*, UINT*) override {
        if (dispIdMember == 200) OnWindowRegistered(); // WindowRegistered
        return S_OK;
    }
    
    void OnWindowRegistered() {
        IShellWindows* pSW = nullptr;
        if (FAILED(CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&pSW)))) return;
        long count = 0; pSW->get_Count(&count);
        if (count > 0) {
            VARIANT idx; VariantInit(&idx); idx.vt = VT_I4; idx.lVal = count - 1;
            IDispatch* pDisp = nullptr;
            if (SUCCEEDED(pSW->Item(idx, &pDisp)) && pDisp) {
                IWebBrowser2* pBrowser = nullptr;
                if (SUCCEEDED(pDisp->QueryInterface(IID_PPV_ARGS(&pBrowser)))) {
                    bool found = false;
                    {
                        std::lock_guard<std::mutex> lock(g_eventSinksMutex);
                        for (auto* sink : g_eventSinks) {
                            if (sink->browser == pBrowser) { found = true; break; }
                        }
                    }
                    if (!found) ConnectEventSink(pBrowser);
                    pBrowser->Release();
                }
                pDisp->Release();
            }
        }
        pSW->Release();
    }
    
    void Disconnect() {
        if (cp && cookie) { cp->Unadvise(cookie); cp->Release(); cp = nullptr; cookie = 0; }
    }
};

static ShellWindowsEventSink* g_shellWindowsSink = nullptr;

static void ConnectShellWindowsEvents() {
    IShellWindows* pSW = nullptr;
    if (FAILED(CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&pSW)))) return;
    IConnectionPointContainer* cpc = nullptr;
    if (FAILED(pSW->QueryInterface(IID_PPV_ARGS(&cpc)))) { pSW->Release(); return; }
    pSW->Release();
    IConnectionPoint* cp = nullptr;
    if (FAILED(cpc->FindConnectionPoint(DIID_DShellWindowsEvents, &cp))) { cpc->Release(); return; }
    cpc->Release();
    g_shellWindowsSink = new ShellWindowsEventSink();
    g_shellWindowsSink->cp = cp;
    DWORD cookie = 0;
    if (SUCCEEDED(cp->Advise(g_shellWindowsSink, &cookie))) {
        g_shellWindowsSink->cookie = cookie;
    } else {
        g_shellWindowsSink->Disconnect();
        g_shellWindowsSink->Release();
        g_shellWindowsSink = nullptr;
    }
}

static void DisconnectShellWindowsEvents() {
    if (g_shellWindowsSink) {
        g_shellWindowsSink->Disconnect();
        g_shellWindowsSink->Release();
        g_shellWindowsSink = nullptr;
    }
}

// ---------------------------------------------------------------------------
// Redirect thread: close new window → create tab → navigate
// ---------------------------------------------------------------------------
static DWORD WINAPI RedirectThread(LPVOID param) {
    auto* info    = static_cast<RedirectInfo*>(param);
    HWND  newWnd  = info->newWindow;
    HWND  primary = info->primaryWindow;
    delete info;

    if (FAILED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED)))
        return 1;

    // Wait for the new window to finish navigating (max ~2s)
    std::wstring path;
    for (int attempt = 0; attempt < 40 && IsWindow(newWnd); attempt++) {
        Sleep(50);
        path = GetExplorerWindowPath(newWnd);
        if (!path.empty()) break;
    }

    // Re-verify primary is still alive
    if (!IsWindow(primary))
        primary = FindExistingExplorer(newWnd);

    if (path.empty() || !primary || !IsWindow(primary) ||
        newWnd == primary) {
        AbortRedirect(newWnd);
        CoUninitialize();
        return 0;
    }

    // Don't redirect Control Panel or other special shell windows
    if (!IsRedirectablePath(path)) {
        Wh_Log(L"Non-redirectable path, allowing window: %s", path.c_str());
        AbortRedirect(newWnd);
        CoUninitialize();
        return 0;
    }

    // Check whether this window was created by detaching a tab
    if (IsRecentlyClosedTab(path)) {
        Wh_Log(L"Allowing detached tab window: %s", path.c_str());
        AbortRedirect(newWnd);
        CoUninitialize();
        return 0;
    }

    // Prevent race conditions between tab creation and navigation
    {
        std::lock_guard<std::mutex> tabLock(g_tabCreationMutex);

        // Snapshot tab count before creating a new tab
        int prevCount = GetShellWindowCount() - 1;

        RemoveHiddenWindow(newWnd);

        // Register this window to be ignored by OnQuit before closing it
        IgnoreWindowClose(newWnd);

        PostMessageW(newWnd, WM_CLOSE, 0, 0);

        // Create a new tab via internal WM_COMMAND
        HWND shellTab = FindWindowExW(primary, nullptr, WC_SHELLTAB, nullptr);
        if (!shellTab) {
            Wh_Log(L"ShellTabWindowClass not found on primary %p", primary);
            CoUninitialize();
            return 0;
        }
        PostMessageW(shellTab, WM_COMMAND, CMD_NEW_TAB, 0);

        // Wait for the new tab to register in IShellWindows (max ~3s)
        bool navigated = false;
        for (int wait = 0; wait < 30; wait++) {
            Sleep(100);
            if (GetShellWindowCount() > prevCount) {
                navigated = NavigateNewTab(primary, path, prevCount);
                break;
            }
        }

        // Fallback: try navigating the last matching entry
        if (!navigated)
            navigated = NavigateNewTab(primary, path, 0);

        Wh_Log(L"%s → %s", navigated ? L"Redirected" : L"Failed", path.c_str());
    }

    // Bring primary window to foreground
    if (IsIconic(primary))
        ShowWindow(primary, SW_RESTORE);

    DWORD pid = 0;
    GetWindowThreadProcessId(primary, &pid);
    AllowSetForegroundWindow(pid);
    SetForegroundWindow(primary);

    CoUninitialize();
    return 0;
}

// ---------------------------------------------------------------------------
// Hook: CreateWindowExW
// ---------------------------------------------------------------------------
using CreateWindowExW_t = decltype(&CreateWindowExW);
CreateWindowExW_t CreateWindowExW_Original;

HWND WINAPI CreateWindowExW_Hook(DWORD dwExStyle, LPCWSTR lpClassName,
                                  LPCWSTR lpWindowName, DWORD dwStyle,
                                  int X, int Y, int nWidth, int nHeight,
                                  HWND hWndParent, HMENU hMenu,
                                  HINSTANCE hInstance, LPVOID lpParam) {
    auto original = [=]() {
        return CreateWindowExW_Original(dwExStyle, lpClassName, lpWindowName,
                                        dwStyle, X, Y, nWidth, nHeight,
                                        hWndParent, hMenu, hInstance, lpParam);
    };

    bool isCabinet = lpClassName && !IS_INTRESOURCE(lpClassName) &&
                     wcscmp(lpClassName, WC_CABINET) == 0;
    if (!isCabinet) {
        return original();
    }

    // Bypass if Shift is held
    if (GetAsyncKeyState(VK_SHIFT) & 0x8000) {
        return original();
    }

    HWND existing = FindExistingExplorer(nullptr);

    // First window
    if (!existing) {
        return original();
    }

    Wh_Log(L"Creating Explorer window");
    g_creatingExplorerWindow = true;

    HWND hwnd = CreateWindowExW_Original(dwExStyle, lpClassName, lpWindowName,
                                          dwStyle, X, Y, nWidth, nHeight,
                                          hWndParent, hMenu, hInstance,
                                          lpParam);

    Wh_Log(L"Created window %p", hwnd);
    g_creatingExplorerWindow = false;

    if (hwnd) {
        AddHiddenWindow(hwnd);

        // Make window fully transparent instead of stripping WS_VISIBLE.
        // This keeps WS_VISIBLE set so the window registers in IShellWindows
        // (required for path extraction of special folders like Recycle Bin)
        // but the user never sees it.
        SetWindowLongW(hwnd, GWL_EXSTYLE,
                       GetWindowLongW(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED);
        SetLayeredWindowAttributes(hwnd, 0, 0, LWA_ALPHA);

        // Redirect — window is transparent, thread will close it
        auto* ri = new RedirectInfo{hwnd, existing};
        HANDLE hThread =
            CreateThread(nullptr, 0, RedirectThread, ri, 0, nullptr);
        if (hThread) CloseHandle(hThread);
    }

    return hwnd;
}

// ---------------------------------------------------------------------------
// Hook: ShowWindow — prevent the redirected window from flashing
// ---------------------------------------------------------------------------
BOOL WINAPI ShowWindow_Hook(HWND hWnd, int nCmdShow) {
    if (g_creatingExplorerWindow || IsHiddenWindow(hWnd)) {
        WCHAR className[64];
        if (GetClassNameW(hWnd, className, ARRAYSIZE(className)) &&
            wcscmp(className, WC_CABINET) == 0) {
            Wh_Log(
                L"Blocked ShowWindow for %p %s", hWnd,
                g_creatingExplorerWindow ? L"(creating)" : L"(hidden)");
            return TRUE;
        }
    }
    return ShowWindow_Original(hWnd, nCmdShow);
}

// ---------------------------------------------------------------------------
// Mod lifecycle
// ---------------------------------------------------------------------------
BOOL Wh_ModInit() {
    Wh_Log(L"Explorer Single Window Tabs initializing");
    
    CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
    
    ConnectToExistingWindows();
    ConnectShellWindowsEvents();
    
    Wh_SetFunctionHook(reinterpret_cast<void*>(CreateWindowExW),
                        reinterpret_cast<void*>(CreateWindowExW_Hook),
                        reinterpret_cast<void**>(&CreateWindowExW_Original));
    Wh_SetFunctionHook(reinterpret_cast<void*>(ShowWindow),
                        reinterpret_cast<void*>(ShowWindow_Hook),
                        reinterpret_cast<void**>(&ShowWindow_Original));
    return TRUE;
}

void Wh_ModUninit() {
    Wh_Log(L"Explorer Single Window Tabs uninitializing");
    
    DisconnectShellWindowsEvents();
    DisconnectAllEventSinks();
    
    {
        std::lock_guard<std::mutex> lock(g_closedTabsMutex);
        g_closedTabs.clear();
    }
    
    {
        std::lock_guard<std::mutex> lock(g_hiddenMutex);
        g_hiddenWindows.clear();
    }
    
    {
        std::lock_guard<std::mutex> lock(g_ignoredHwndsMutex);
        g_ignoredHwnds.clear();
    }
    
    CoUninitialize();
}

@TheGamer1445891
Copy link
Copy Markdown

has this been added to windhawk yet?

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.

4 participants