Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
052a9fc
Hide native Windows 11 Start button when using a replacement
YellowNest Oct 3, 2026
9f572bb
Fix Win11 TAP control matching and shutdown
YellowNest Oct 4, 2026
3a98698
Fix Win11 TAP teardown ordering
YellowNest Oct 4, 2026
192b2e3
Keep Win11 TAP connection worker resident
YellowNest Oct 4, 2026
440d76e
Isolate Win11 XAML TAP lifetime from StartMenuDLL
YellowNest Oct 5, 2026
2ec3d6d
Do not free XAML callback input strings
YellowNest Oct 5, 2026
b139e94
Prepare Win11 taskbar thread for XAML Start input
YellowNest Oct 5, 2026
dab5469
Route Win11 Start mouse input through XAML
YellowNest Oct 5, 2026
d237008
Link WinRT activation support for XAML input
YellowNest Oct 5, 2026
4695d2d
Fix XAML routed handler ABI and route teardown
YellowNest Oct 5, 2026
3a72867
Harden XAML input handler lifetime
YellowNest Oct 5, 2026
fcda8d2
Make XAML input fallback transitions fail safe
YellowNest Oct 5, 2026
b3b3b8a
Always process XAML input safety state
YellowNest Oct 5, 2026
7fbd24b
Drop stale XAML input roots safely
YellowNest Oct 5, 2026
7aac8b3
Hold TAP alive during input route updates
YellowNest Oct 5, 2026
0150c72
Drain XAML input messages before unload
YellowNest Oct 5, 2026
d038fa4
Bind XAML input routes to taskbar islands
YellowNest Oct 5, 2026
42c09fc
Track XAML source lifetime for input routes
YellowNest Oct 5, 2026
3c31480
Use stable XAML source COM ABI
YellowNest Oct 5, 2026
314ad82
Cover Start input on every taskbar
YellowNest Oct 5, 2026
1197849
Match input routes to XAML source content
YellowNest Oct 5, 2026
2522c14
Retry input routing when XAML sources appear
YellowNest Oct 5, 2026
abd22de
Keep mouse fallback for uncovered taskbars
YellowNest Oct 5, 2026
5ee8252
Reprobe XAML input after taskbar updates
YellowNest Oct 5, 2026
8339573
Reprobe input routes on the XAML thread
YellowNest Oct 5, 2026
d89d5dc
Queue fallback before detaching XAML routes
YellowNest Oct 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 167 additions & 17 deletions Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "ResourceHelper.h"
#include "LogManager.h"
#include "TouchHelper.h"
#include "Win11StartButton.h"
#include "IatHookHelper.h"
#include "dllmain.h"
#include <uxtheme.h>
Expand Down Expand Up @@ -49,6 +50,44 @@ static TOOLINFO g_StartButtonTool;
static bool g_bHotkeyShift;
static int g_HotkeyCSM, g_HotkeyWSM, g_HotkeyShiftID, g_HotkeyCSMID, g_HotkeyWSMID;
static HHOOK g_ProgHook, g_StartHook, g_StartMouseHook, g_AppManagerHook, g_NewWindowHook, g_StartMenuHook;

enum
{
WIN11_START_INPUT_FALLBACK,
WIN11_START_INPUT_PROBING,
WIN11_START_INPUT_ACTIVE,
};

static volatile LONG g_Win11StartInputState=WIN11_START_INPUT_FALLBACK;

static UINT GetWin11StartInputMessage( void )
{
static UINT message=RegisterWindowMessage(L"OpenShell.Win11StartInput");
return message;
}

static UINT GetWin11StartInputStateMessage( void )
{
static UINT message=RegisterWindowMessage(L"OpenShell.Win11StartInputState");
return message;
}

static void DrainWin11StartInputMessages( void )
{
if (!IsWin11())
return;

MSG msg;
const UINT inputMessage=GetWin11StartInputMessage();
const UINT stateMessage=GetWin11StartInputStateMessage();
while (PeekMessage(&msg,NULL,inputMessage,inputMessage,PM_REMOVE))
{
}
while (PeekMessage(&msg,NULL,stateMessage,stateMessage,PM_REMOVE))
{
}
}

static bool g_bAllProgramsTimer;
static bool g_bInMenu;
static DWORD g_LastClickTime;
Expand Down Expand Up @@ -2643,6 +2682,7 @@ void UpdateTaskBars( TUpdateTaskbar update )
InvalidateRect(taskBar.taskBar,NULL,TRUE);
PostMessage(taskBar.taskBar,WM_THEMECHANGED,0,0);
}
UpdateWin11StartButtonMonitor();
}

///////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -3079,6 +3119,7 @@ static void InitStartMenuDLL( void )
g_StartHook=SetWindowsHookEx(WH_GETMESSAGE,HookDesktopThread,NULL,GetCurrentThreadId());
if (IsWin11())
{
InterlockedExchange(&g_Win11StartInputState,WIN11_START_INPUT_FALLBACK);
g_StartMouseHook=SetWindowsHookEx(WH_MOUSE,HookDesktopThreadMouse,NULL,GetCurrentThreadId());

// hook ShellRegisterHotKey to prevent twinui.dll to install shell hotkeys (Win, Ctrl+Esc)
Expand Down Expand Up @@ -3227,6 +3268,7 @@ if (!g_bTrimHooks)

UpdateTaskBars(TASKBAR_RECREATE_BUTTONS);
UpdateTaskBars(TASKBAR_UPDATE_TEXTURE);
StartWin11StartButtonMonitor();
}

static void RecreateStartButton( size_t taskbarId )
Expand Down Expand Up @@ -3288,6 +3330,13 @@ static DWORD WINAPI ExitThreadProc( void *param )

static void CleanStartMenuDLL( void )
{
StopWin11StartButtonMonitor();

// Stop the helper first, then remove any bridge messages that were already
// queued on Explorer's taskbar thread. This prevents stale pointer/state
// messages from surviving a StartMenuDLL unload/reload cycle.
DrainWin11StartInputMessages();

ClearIatHook(g_DwmpBTRHook);
g_DwmpBTRHook=NULL;
ClearIatHook(g_DwmpTWWRHook);
Expand Down Expand Up @@ -3329,6 +3378,7 @@ static void CleanStartMenuDLL( void )
UnhookWindowsHookEx(g_StartHook);
if (g_StartMouseHook) UnhookWindowsHookEx(g_StartMouseHook);
g_StartMouseHook=NULL;
InterlockedExchange(&g_Win11StartInputState,WIN11_START_INPUT_FALLBACK);
if (g_AppManagerHook) UnhookWindowsHookEx(g_AppManagerHook);
g_AppManagerHook=NULL;
if (g_NewWindowHook) UnhookWindowsHookEx(g_NewWindowHook);
Expand Down Expand Up @@ -3616,42 +3666,142 @@ static LRESULT CALLBACK HookProgManThread( int code, WPARAM wParam, LPARAM lPara
return CallNextHookEx(NULL,code,wParam,lParam);
}

// WH_MOUSE hook for taskbar thread (Win11+)
// WH_MOUSE fallback for the taskbar thread (Win11+).
//
// Once the XAML input route has proved that it receives pointer movement for
// the native Start slot, the fallback is removed. While probing, movement is
// allowed through so the XAML route can prove itself; button messages continue
// to use the established hook path until that transition is complete.
static LRESULT CALLBACK HookDesktopThreadMouse(int code, WPARAM wParam, LPARAM lParam)
{
if (code == HC_ACTION)
{
// we need to steal mouse messages that are issues in start button area
// so that they won't get to XAML framework that is handling original start button
auto info = (const MOUSEHOOKSTRUCT*)lParam;
LONG inputState=InterlockedCompareExchange(&g_Win11StartInputState,0,0);
auto info=(const MOUSEHOOKSTRUCT*)lParam;
auto taskBar=FindTaskBarInfoButton(info->hwnd);
if (!taskBar)
{
auto taskBar = FindTaskBarInfoButton(info->hwnd); // click on start button
if (!taskBar)
{
taskBar = FindTaskBarInfoBar(GetAncestor(info->hwnd, GA_ROOT)); // click on taskbar
if (taskBar && !PointAroundStartButton(taskBar->taskbarId))
taskBar = NULL;
}
taskBar=FindTaskBarInfoBar(GetAncestor(info->hwnd,GA_ROOT));
if (taskBar && !PointAroundStartButton(taskBar->taskbarId))
taskBar=NULL;
}

if (taskBar && (info->hwnd != taskBar->startButton) && taskBar->oldButton)
if (taskBar && info->hwnd!=taskBar->startButton && taskBar->oldButton)
{
if (inputState==WIN11_START_INPUT_PROBING && wParam==WM_MOUSEMOVE)
return CallNextHookEx(NULL,code,wParam,lParam);

// Preserve the existing behavior until XAML routing is confirmed.
PostMessage(taskBar->oldButton,(UINT)wParam,0,MAKELPARAM(info->pt.x,info->pt.y));
return 1;
}
}

return CallNextHookEx(NULL,code,wParam,lParam);
}

static void SetWin11StartInputState( LONG state )
{
if (!IsWin11())
return;

if (state==WIN11_START_INPUT_ACTIVE)
{
if (g_StartMouseHook)
{
if (!UnhookWindowsHookEx(g_StartMouseHook))
{
// steal messages from other than our custom button window
PostMessage(taskBar->oldButton, (UINT)wParam, 0, MAKELPARAM(info->pt.x, info->pt.y));
return 1;
DWORD error=GetLastError();
InterlockedExchange(&g_Win11StartInputState,WIN11_START_INPUT_FALLBACK);
LogToFile(STARTUP_LOG,L"Win11StartInput: failed to remove WH_MOUSE fallback 0x%08X",error);
return;
}
g_StartMouseHook=NULL;
}
InterlockedExchange(&g_Win11StartInputState,WIN11_START_INPUT_ACTIVE);
LogToFile(STARTUP_LOG,L"Win11StartInput: XAML routing active; WH_MOUSE fallback removed");
return;
}

InterlockedExchange(&g_Win11StartInputState,state);
if (!g_StartMouseHook)
{
g_StartMouseHook=SetWindowsHookEx(WH_MOUSE,HookDesktopThreadMouse,NULL,GetCurrentThreadId());
if (g_StartMouseHook)
LogToFile(STARTUP_LOG,L"Win11StartInput: WH_MOUSE fallback restored (%d)",state);
else
LogToFile(STARTUP_LOG,L"Win11StartInput: failed to restore WH_MOUSE fallback 0x%08X",GetLastError());
}
}

static bool RewriteWin11StartInputMessage( MSG *msg )
{
TaskbarInfo *taskBar=FindTaskBarInfoBar(msg->hwnd);
if (!taskBar || !taskBar->oldButton)
{
SetWin11StartInputState(WIN11_START_INPUT_FALLBACK);
msg->message=WM_NULL;
return false;
}

UINT mouseMessage=(UINT)msg->wParam;
if (!((mouseMessage>=WM_MOUSEFIRST && mouseMessage<=WM_MOUSELAST) || mouseMessage==WM_MOUSELEAVE))
{
msg->message=WM_NULL;
return false;
}

return CallNextHookEx(NULL, code, wParam, lParam);
POINT screenPoint={(short)LOWORD(msg->lParam),(short)HIWORD(msg->lParam)};
msg->hwnd=taskBar->oldButton;
msg->message=mouseMessage;
msg->wParam=0;
msg->pt=screenPoint;

if (mouseMessage==WM_MOUSELEAVE)
{
msg->lParam=0;
}
else
{
POINT clientPoint=screenPoint;
ScreenToClient(taskBar->oldButton,&clientPoint);
msg->lParam=MAKELPARAM(clientPoint.x,clientPoint.y);
}
return true;
}

// WH_GETMESSAGE hook for the taskbar thread
static LRESULT CALLBACK HookDesktopThread( int code, WPARAM wParam, LPARAM lParam )
{
// Input-route state changes are safety-critical. Process them even while an
// Open-Shell popup menu is running its nested message loop; otherwise a
// fallback request can be lost while WH_MOUSE is detached.
if (code==HC_ACTION && wParam)
{
MSG *controlMsg=(MSG*)lParam;
if (controlMsg->message==GetWin11StartInputStateMessage())
{
LONG state=(LONG)controlMsg->wParam;
if (state>=WIN11_START_INPUT_FALLBACK && state<=WIN11_START_INPUT_ACTIVE)
SetWin11StartInputState(state);
controlMsg->message=WM_NULL;
return CallNextHookEx(NULL,code,wParam,lParam);
}
if (g_bInMenu && controlMsg->message==GetWin11StartInputMessage())
{
controlMsg->message=WM_NULL;
return CallNextHookEx(NULL,code,wParam,lParam);
}
}

if (code==HC_ACTION && wParam && !g_bInMenu)
{
MSG *msg=(MSG*)lParam;
FindTaskBar();

if (msg->message==GetWin11StartInputMessage())
RewriteWin11StartInputMessage(msg);

if (IsSettingsMessage(msg))
{
msg->message=WM_NULL;
Expand Down Expand Up @@ -4083,7 +4233,7 @@ if (!g_bTrimHooks)
}
if (taskBar)
{
CPoint pt0(GetMessagePos());
CPoint pt0(msg->pt);
if (msg->message==WM_RBUTTONUP && msg->hwnd==taskBar->startButton && msg->lParam==MAKELPARAM(-1,-1))
{
RECT rc;
Expand Down
2 changes: 2 additions & 0 deletions Src/StartMenu/StartMenuDLL/StartMenuDLL.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="TouchHelper.cpp" />
<ClCompile Include="Win11StartButton.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="Accessibility.h" />
Expand All @@ -110,6 +111,7 @@
<ClInclude Include="stdafx.h" />
<ClInclude Include="targetver.h" />
<ClInclude Include="TouchHelper.h" />
<ClInclude Include="Win11StartButton.h" />
</ItemGroup>
<ItemGroup>
<Image Include="..\..\Setup\OpenShell.ico" />
Expand Down
6 changes: 6 additions & 0 deletions Src/StartMenu/StartMenuDLL/StartMenuDLL.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@
<ClCompile Include="TouchHelper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Win11StartButton.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Accessibility.h">
Expand Down Expand Up @@ -128,6 +131,9 @@
<ClInclude Include="TouchHelper.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Win11StartButton.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Resource Files</Filter>
</ClInclude>
Expand Down
Loading