Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
213 changes: 195 additions & 18 deletions Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,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 @@ -3081,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 @@ -3293,6 +3332,11 @@ 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 @@ -3334,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 @@ -3621,42 +3666,162 @@ 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));
if (taskBar && !PointAroundStartButton(taskBar->taskbarId))
taskBar=NULL;
}

if (taskBar && info->hwnd!=taskBar->startButton && taskBar->oldButton)
{
if (inputState==WIN11_START_INPUT_PROBING && wParam==WM_MOUSEMOVE)
{
taskBar = FindTaskBarInfoBar(GetAncestor(info->hwnd, GA_ROOT)); // click on taskbar
if (taskBar && !PointAroundStartButton(taskBar->taskbarId))
taskBar = NULL;
// Preserve existing Open-Shell hover behavior while also allowing
// this movement to reach XAML and prove the new route. A duplicate
// synthetic WM_MOUSEMOVE is harmless; button messages remain owned
// exclusively by the fallback until the route is confirmed.
PostMessage(taskBar->oldButton,WM_MOUSEMOVE,0,MAKELPARAM(info->pt.x,info->pt.y));
return CallNextHookEx(NULL,code,wParam,lParam);
}

if (taskBar && (info->hwnd != taskBar->startButton) && taskBar->oldButton)
// 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;
}

// The helper transports coordinates relative to the taskbar HWND rather
// than global virtual-screen coordinates. Preserve them for every routed
// mouse event, including WM_MOUSELEAVE, so later logic can use the event-time
// MSG.pt instead of sampling GetMessagePos() after queueing.
POINT taskbarPoint={(short)LOWORD(msg->lParam),(short)HIWORD(msg->lParam)};
POINT screenPoint=taskbarPoint;
if (!ClientToScreen(taskBar->taskBar,&screenPoint))
{
SetWin11StartInputState(WIN11_START_INPUT_FALLBACK);
msg->message=WM_NULL;
return false;
}

msg->hwnd=taskBar->oldButton;
msg->message=mouseMessage;
msg->wParam=0;
msg->pt=screenPoint;

return CallNextHookEx(NULL, code, wParam, lParam);
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();

bool win11RoutedInput=false;
if (msg->message==GetWin11StartInputMessage())
win11RoutedInput=RewriteWin11StartInputMessage(msg);

if (IsSettingsMessage(msg))
{
msg->message=WM_NULL;
Expand Down Expand Up @@ -3983,7 +4148,11 @@ if (!g_bTrimHooks)
{
APPBARDATA appbar={sizeof(appbar)};
if (SHAppBarMessage(ABM_GETSTATE,&appbar)&ABS_AUTOHIDE)
SendMessage(taskBar->taskBar,WM_NCHITTEST,0,GetMessagePos());
{
LPARAM hitTestPoint=win11RoutedInput ?
MAKELPARAM(msg->pt.x,msg->pt.y) : GetMessagePos();
SendMessage(taskBar->taskBar,WM_NCHITTEST,0,hitTestPoint);
}
}
}
}
Expand All @@ -3998,7 +4167,11 @@ if (!g_bTrimHooks)
{
RECT rc;
GetWindowRect(taskBar->oldButton,&rc);
CPoint pt(GetMessagePos());
CPoint pt;
if (win11RoutedInput)
pt=msg->pt;
else
pt=CPoint(GetMessagePos());
if (PtInRect(&rc,pt))
{
wchar_t className[256]={0};
Expand Down Expand Up @@ -4088,7 +4261,11 @@ if (!g_bTrimHooks)
}
if (taskBar)
{
CPoint pt0(GetMessagePos());
CPoint pt0;
if (win11RoutedInput)
pt0=msg->pt;
else
pt0=CPoint(GetMessagePos());
if (msg->message==WM_RBUTTONUP && msg->hwnd==taskBar->startButton && msg->lParam==MAKELPARAM(-1,-1))
{
RECT rc;
Expand Down Expand Up @@ -4238,7 +4415,7 @@ if (!g_bTrimHooks)
}
else if (bShowWin7)
{
CPoint pt(GetMessagePos());
CPoint pt(pt0);
ScreenToClient(g_WinStartButton,&pt);
PostMessage(g_WinStartButton,WM_RBUTTONUP,wParam,MAKELONG(pt.x,pt.y));
}
Expand Down
Loading