create explorer-single-window-tabs mod#3457
create explorer-single-window-tabs mod#3457almas-cp wants to merge 7 commits intoramensoftware:mainfrom
Conversation
|
|
||
| // ==WindhawkModSettings== | ||
| /* | ||
| - enabled: true |
There was a problem hiding this comment.
What's the purpose of the option compared to just disabling the mod?
There was a problem hiding this comment.
nothing, should i remove it?
| } | ||
|
|
||
| // Hide and close the duplicate window | ||
| ShowWindow(newWnd, SW_HIDE); |
There was a problem hiding this comment.
Perhaps it's worth hiding the window earlier?
There was a problem hiding this comment.
can you clarify please?
There was a problem hiding this comment.
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).
|
I tried the mod, it indeed flashes, and hooking 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++) { |
There was a problem hiding this comment.
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);|
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();
} |
|
Glad to see this mod! I'd like to use it to replace ExplorerTabUtility, since I only really need its basic features. |
|
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. |
|
The Control Panel issue is resolved now! 🙌 Though I found a couple more issues 😅:
|
|
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. |
|
@ahzvenol |
|
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 But of course, these are just my personal thoughts — the decision is yours. |
|
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 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();
} |
|
has this been added to windhawk yet? |
Adds a new mod that forces File Explorer to use a single window with tabs instead of opening multiple windows.