From cf32790659b2e6d427afb2fde56dbc91e6c67cc7 Mon Sep 17 00:00:00 2001
From: YellowNest <59575587+YellowNest@users.noreply.github.com>
Date: Mon, 5 Oct 2026 18:34:00 +0200
Subject: [PATCH 1/5] Route Windows 11 Start input through XAML
---
Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp | 179 ++++-
.../StartMenuHelper/StartMenuHelper.vcxproj | 2 +-
.../StartMenuHelper/Win11StartButtonTap.cpp | 716 +++++++++++++++++-
3 files changed, 878 insertions(+), 19 deletions(-)
diff --git a/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp b/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp
index 088569e08..071559743 100644
--- a/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp
+++ b/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp
@@ -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;
@@ -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)
@@ -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);
@@ -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);
@@ -3621,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 (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 (taskBar && (info->hwnd != taskBar->startButton) && taskBar->oldButton)
+ 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;
@@ -4088,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;
diff --git a/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj b/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj
index b7c5e7d84..fd994f334 100644
--- a/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj
+++ b/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj
@@ -92,7 +92,7 @@
$(IntDir);%(AdditionalIncludeDirectories)
- comctl32.lib;uxtheme.lib;winmm.lib;htmlhelp.lib;wininet.lib;version.lib;%(AdditionalDependencies)
+ comctl32.lib;uxtheme.lib;winmm.lib;htmlhelp.lib;wininet.lib;version.lib;runtimeobject.lib;%(AdditionalDependencies)
$(TargetName).def
diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp
index 6770e6431..f727e2b69 100644
--- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp
+++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp
@@ -15,15 +15,90 @@
#include "..\StartMenuDLL\LogManager.h"
#include
+#include
+#include
#include
#include
+#include
+#include
+#include
+#include
+#include
#include
+#include
#include
static const GUID CLSID_OpenShellStartButtonTap =
{ 0x7d15741f, 0x2f3b, 0x4971, { 0xb8, 0x91, 0x6a, 0x5d, 0x42, 0xd7, 0x1a, 0x34 } };
+// windows.ui.xaml.hosting.desktopwindowxamlsource.h exposes this interface
+// through projection-specific declarations that do not compose cleanly with
+// this ATL project. Keep the documented COM ABI local instead of pulling a
+// second WinRT projection into StartMenuHelper.
+MIDL_INTERFACE("3cbcf1bf-2f76-4e9c-96ab-e84b37972554")
+IOpenShellDesktopWindowXamlSourceNative : public IUnknown
+{
+public:
+ virtual HRESULT STDMETHODCALLTYPE AttachToWindow( HWND parentWnd ) = 0;
+ virtual HRESULT STDMETHODCALLTYPE get_WindowHandle( HWND *hWnd ) = 0;
+};
+
+MIDL_INTERFACE("d585bfe1-00ff-51be-ba1d-a1329956ea0a")
+IOpenShellDesktopWindowXamlSource : public IInspectable
+{
+public:
+ virtual HRESULT STDMETHODCALLTYPE get_Content(
+ ABI::Windows::UI::Xaml::IUIElement **value ) = 0;
+};
+
static const UINT WM_OS_STARTBUTTON_APPLY = WM_APP + 0x35B;
+static const UINT WM_OS_STARTBUTTON_REPROBE = WM_APP + 0x35C;
+
+enum
+{
+ WIN11_START_INPUT_FALLBACK,
+ WIN11_START_INPUT_PROBING,
+ WIN11_START_INPUT_ACTIVE,
+};
+
+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 bool IsTaskbarWindow( HWND hwnd )
+{
+ if (!hwnd)
+ return false;
+ wchar_t className[64] = {};
+ if (!GetClassName(hwnd, className, _countof(className)))
+ return false;
+ return wcscmp(className, L"Shell_TrayWnd") == 0 ||
+ wcscmp(className, L"Shell_SecondaryTrayWnd") == 0;
+}
+
+static HWND FindTaskbarAncestor( HWND hwnd )
+{
+ for (int depth = 0; hwnd && depth < 16; depth++)
+ {
+ if (IsTaskbarWindow(hwnd))
+ return hwnd;
+ HWND parent = GetParent(hwnd);
+ if (!parent)
+ break;
+ hwnd = parent;
+ }
+
+ HWND root = hwnd ? GetAncestor(hwnd, GA_ROOT) : NULL;
+ return IsTaskbarWindow(root) ? root : NULL;
+}
static volatile LONG g_StartButtonActive = 0;
static volatile LONG g_StartButtonEnabled = 0;
@@ -60,7 +135,58 @@ struct StartElement
}
};
+struct StartInputRoute
+{
+ InstanceHandle startHandle;
+ InstanceHandle rootHandle;
+ InstanceHandle sourceHandle;
+ CComPtr startElement;
+ CComPtr startFramework;
+ CComPtr rootElement;
+ CComPtr pointerHandler;
+ CComPtr handler;
+ bool verified;
+ bool pointerInside;
+ HWND taskbar;
+
+ StartInputRoute( void )
+ {
+ startHandle = 0;
+ rootHandle = 0;
+ sourceHandle = 0;
+ verified = false;
+ pointerInside = false;
+ taskbar = NULL;
+ }
+};
+
class CWin11StartButtonTap;
+
+class CStartPointerHandler:
+ public Microsoft::WRL::RuntimeClass<
+ Microsoft::WRL::RuntimeClassFlags,
+ ABI::Windows::UI::Xaml::Input::IPointerEventHandler>
+{
+public:
+ CStartPointerHandler( CWin11StartButtonTap *owner, InstanceHandle startHandle );
+ ~CStartPointerHandler( void );
+
+ HRESULT STDMETHODCALLTYPE GetTrustLevel( TrustLevel *trustLevel ) override
+ {
+ if (!trustLevel)
+ return E_POINTER;
+ *trustLevel = BaseTrust;
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE Invoke( IInspectable *sender,
+ ABI::Windows::UI::Xaml::Input::IPointerRoutedEventArgs *args ) override;
+
+private:
+ CWin11StartButtonTap *m_Owner;
+ InstanceHandle m_StartHandle;
+};
+
static CWin11StartButtonTap *g_Tap = NULL;
static SRWLOCK g_TapLock = SRWLOCK_INIT;
@@ -99,12 +225,16 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
m_Dispatch = NULL;
m_PrimaryStart = 0;
m_NextDiscoveryOrder = 0;
+ m_ExpectedInputRoutes = 0;
+ m_LastInputState = -1;
InitializeCriticalSection(&m_Lock);
_AtlModule.Lock();
}
~CWin11StartButtonTap( void )
{
+ if (m_Dispatch && GetWindowThreadProcessId(m_Dispatch, NULL) == GetCurrentThreadId())
+ DetachAllInputRoutes();
if (m_Visual && m_Advised)
m_Visual->UnadviseVisualTreeChange(static_cast(this));
if (m_Dispatch && GetWindowThreadProcessId(m_Dispatch, NULL) == GetCurrentThreadId())
@@ -163,8 +293,17 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
}
m_Advised = false;
}
+ DetachAllInputRoutes();
+ m_UIElementStatics.Release();
+ m_PointerEnteredEvent.Release();
+ m_PointerMovedEvent.Release();
+ m_PointerPressedEvent.Release();
+ m_PointerReleasedEvent.Release();
+ m_PointerExitedEvent.Release();
+ m_Diagnostics.Release();
m_Visual.Release();
m_Site.Release();
+ m_LastInputState = -1;
if (!site)
{
@@ -175,6 +314,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
EnterCriticalSection(&m_Lock);
m_Elements.clear();
+ m_XamlSources.clear();
m_PrimaryStart = 0;
m_NextDiscoveryOrder = 0;
LeaveCriticalSection(&m_Lock);
@@ -193,6 +333,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
EnterCriticalSection(&m_Lock);
m_Elements.clear();
+ m_XamlSources.clear();
m_PrimaryStart = 0;
m_NextDiscoveryOrder = 0;
LeaveCriticalSection(&m_Lock);
@@ -206,15 +347,24 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
HRESULT hr = site->QueryInterface(__uuidof(IVisualTreeService), (void**)&m_Visual);
if (FAILED(hr) || !m_Visual)
{
+ m_Diagnostics.Release();
m_Visual.Release();
m_Site.Release();
InterlockedExchange(&g_ConnectStarted, 0);
return hr;
}
+ HRESULT diagnosticsHr = site->QueryInterface(__uuidof(IXamlDiagnostics), (void**)&m_Diagnostics);
+ if (FAILED(diagnosticsHr))
+ {
+ m_Diagnostics.Release();
+ LogToFile(STARTUP_LOG, L"Win11StartInput: IXamlDiagnostics unavailable 0x%08X; keeping WH_MOUSE fallback", diagnosticsHr);
+ }
+
if (!CreateDispatchWindow())
{
DWORD error = GetLastError();
+ m_Diagnostics.Release();
m_Visual.Release();
m_Site.Release();
InterlockedExchange(&g_ConnectStarted, 0);
@@ -246,6 +396,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
SetWindowLongPtr(dispatch, GWLP_USERDATA, 0);
DestroyWindow(dispatch);
}
+ m_Diagnostics.Release();
m_Visual.Release();
m_Site.Release();
InterlockedExchange(&g_ConnectStarted, 0);
@@ -273,6 +424,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
EnterCriticalSection(&m_Lock);
if (mutationType == Remove)
{
+ m_XamlSources.erase(element.Handle);
auto it = m_Elements.find(element.Handle);
if (it != m_Elements.end())
{
@@ -305,12 +457,24 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
record.discoveryOrder = ++m_NextDiscoveryOrder;
}
m_Elements[element.Handle] = record;
+ const bool isXamlSource = record.type == L"Windows.UI.Xaml.Hosting.DesktopWindowXamlSource";
+ if (isXamlSource)
+ m_XamlSources.insert(element.Handle);
- interesting = IsStartControlCandidate(record) || IsStartGlyph(record) ||
+ interesting = isXamlSource || IsStartControlCandidate(record) || IsStartGlyph(record) ||
IsUnderStartButtonLocked(record.parent);
}
LeaveCriticalSection(&m_Lock);
+ // Do not mutate XAML routed-event handler collections from inside the
+ // visual-tree callback. If an active route is disappearing, queue the
+ // fallback transition first; the posted apply pass then removes the stale
+ // route after the diagnostics callback has unwound.
+ if (mutationType == Remove && InputRouteUsesHandle(element.Handle))
+ {
+ ReportInputBridgeState(WIN11_START_INPUT_PROBING);
+ interesting = true;
+ }
if (interesting)
RequestApply(false);
return S_OK;
@@ -331,6 +495,131 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
PostMessage(m_Dispatch, WM_OS_STARTBUTTON_APPLY, 0, 0);
}
+ void RequestInputBridgeReprobe( void )
+ {
+ if (m_Dispatch)
+ PostMessage(m_Dispatch, WM_OS_STARTBUTTON_REPROBE, 0, 0);
+ }
+
+ HRESULT OnStartPointer( InstanceHandle startHandle,
+ ABI::Windows::UI::Xaml::Input::IPointerRoutedEventArgs *args )
+ {
+ if (!args || !InterlockedCompareExchange(&g_StartButtonActive, 0, 0) ||
+ !InterlockedCompareExchange(&g_StartButtonEnabled, 0, 0))
+ return S_OK;
+
+ StartInputRoute *route = FindInputRoute(startHandle);
+ if (!route)
+ return S_OK;
+
+ CComPtr pointer;
+ HRESULT hr = args->get_Pointer(&pointer);
+ if (FAILED(hr) || !pointer)
+ {
+ InvalidateInputRoute(*route);
+ return S_OK;
+ }
+
+ UINT32 pointerId = 0;
+ hr = pointer->get_PointerId(&pointerId);
+ if (FAILED(hr))
+ {
+ InvalidateInputRoute(*route);
+ return S_OK;
+ }
+
+ POINTER_INFO pointerInfo = {};
+ if (!GetPointerInfo(pointerId, &pointerInfo) || pointerInfo.pointerType != PT_MOUSE)
+ return S_OK;
+
+ CComPtr point;
+ hr = args->GetCurrentPoint(route->startElement, &point);
+ if (FAILED(hr) || !point)
+ {
+ InvalidateInputRoute(*route);
+ return S_OK;
+ }
+
+ ABI::Windows::Foundation::Point position = {};
+ DOUBLE width = 0;
+ DOUBLE height = 0;
+ if (FAILED(point->get_Position(&position)) ||
+ FAILED(route->startFramework->get_ActualWidth(&width)) ||
+ FAILED(route->startFramework->get_ActualHeight(&height)))
+ {
+ InvalidateInputRoute(*route);
+ return S_OK;
+ }
+
+ bool inside = position.X >= 0 && position.Y >= 0 &&
+ position.X < width && position.Y < height;
+ if (!inside)
+ {
+ if (route->pointerInside && route->taskbar)
+ PostInputMessage(route->taskbar, WM_MOUSELEAVE, pointerInfo.ptPixelLocation);
+ route->pointerInside = false;
+ return S_OK;
+ }
+
+ HWND taskbar = route->taskbar;
+ if (!taskbar || !IsWindow(taskbar))
+ {
+ InvalidateInputRoute(*route);
+ return S_OK;
+ }
+
+ UINT mouseMessage = WM_MOUSEMOVE;
+ CComPtr properties;
+ if (SUCCEEDED(point->get_Properties(&properties)) && properties)
+ {
+ ABI::Windows::UI::Input::PointerUpdateKind updateKind =
+ ABI::Windows::UI::Input::PointerUpdateKind_Other;
+ if (SUCCEEDED(properties->get_PointerUpdateKind(&updateKind)))
+ {
+ switch (updateKind)
+ {
+ case ABI::Windows::UI::Input::PointerUpdateKind_LeftButtonPressed:
+ mouseMessage = WM_LBUTTONDOWN;
+ break;
+ case ABI::Windows::UI::Input::PointerUpdateKind_LeftButtonReleased:
+ mouseMessage = WM_LBUTTONUP;
+ break;
+ case ABI::Windows::UI::Input::PointerUpdateKind_RightButtonPressed:
+ mouseMessage = WM_RBUTTONDOWN;
+ break;
+ case ABI::Windows::UI::Input::PointerUpdateKind_RightButtonReleased:
+ mouseMessage = WM_RBUTTONUP;
+ break;
+ case ABI::Windows::UI::Input::PointerUpdateKind_MiddleButtonPressed:
+ mouseMessage = WM_MBUTTONDOWN;
+ break;
+ case ABI::Windows::UI::Input::PointerUpdateKind_MiddleButtonReleased:
+ mouseMessage = WM_MBUTTONUP;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (!PostInputMessage(taskbar, mouseMessage, pointerInfo.ptPixelLocation))
+ {
+ InvalidateInputRoute(*route);
+ return S_OK;
+ }
+
+ route->pointerInside = true;
+ route->taskbar = taskbar;
+ args->put_Handled(TRUE);
+
+ if (!route->verified)
+ {
+ route->verified = true;
+ EvaluateInputBridgeState();
+ }
+ return S_OK;
+ }
+
HRESULT Deactivate( void )
{
// The diagnostics runtime retains this site beyond Open-Shell's lifetime.
@@ -343,6 +632,359 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
}
private:
+ bool PostInputMessage( HWND taskbar, UINT mouseMessage, POINT screenPoint )
+ {
+ if (!taskbar || !IsWindow(taskbar))
+ return false;
+ return PostMessage(taskbar, GetWin11StartInputMessage(), mouseMessage,
+ MAKELPARAM(screenPoint.x, screenPoint.y)) != FALSE;
+ }
+
+ void ReprobeInputBridgeState( void )
+ {
+ for (size_t i = 0; i < m_InputRoutes.size(); i++)
+ m_InputRoutes[i].verified = false;
+ m_LastInputState = -1;
+ ReportInputBridgeState(
+ InterlockedCompareExchange(&g_StartButtonEnabled, 0, 0) ?
+ WIN11_START_INPUT_PROBING : WIN11_START_INPUT_FALLBACK);
+ }
+
+ void ReportInputBridgeState( LONG state )
+ {
+ if (!InterlockedCompareExchange(&g_StartButtonActive, 0, 0))
+ {
+ m_LastInputState = -1;
+ return;
+ }
+ if (m_LastInputState == state)
+ return;
+
+ HWND taskbar = FindWindow(L"Shell_TrayWnd", NULL);
+ if (taskbar && PostMessage(taskbar, GetWin11StartInputStateMessage(), state, 0))
+ {
+ m_LastInputState = state;
+ LogToFile(STARTUP_LOG, L"Win11StartInput: routing state %d", state);
+ }
+ }
+
+ StartInputRoute *FindInputRoute( InstanceHandle startHandle )
+ {
+ for (size_t i = 0; i < m_InputRoutes.size(); i++)
+ if (m_InputRoutes[i].startHandle == startHandle)
+ return &m_InputRoutes[i];
+ return NULL;
+ }
+
+ bool InputRouteUsesHandle( InstanceHandle handle ) const
+ {
+ for (size_t i = 0; i < m_InputRoutes.size(); i++)
+ if (m_InputRoutes[i].startHandle == handle ||
+ m_InputRoutes[i].rootHandle == handle ||
+ m_InputRoutes[i].sourceHandle == handle)
+ return true;
+ return false;
+ }
+
+ void InvalidateInputRoute( StartInputRoute &route )
+ {
+ route.verified = false;
+ ReportInputBridgeState(WIN11_START_INPUT_PROBING);
+ }
+
+ bool EnsureUIElementStatics( void )
+ {
+ if (m_UIElementStatics && m_PointerEnteredEvent && m_PointerMovedEvent &&
+ m_PointerPressedEvent && m_PointerReleasedEvent && m_PointerExitedEvent)
+ return true;
+
+ static const wchar_t CLASS_NAME[] = L"Windows.UI.Xaml.UIElement";
+ HSTRING className = NULL;
+ HRESULT hr = WindowsCreateString(CLASS_NAME, _countof(CLASS_NAME) - 1, &className);
+ if (FAILED(hr))
+ return false;
+
+ hr = RoGetActivationFactory(className, __uuidof(ABI::Windows::UI::Xaml::IUIElementStatics),
+ (void**)&m_UIElementStatics);
+ WindowsDeleteString(className);
+ if (FAILED(hr) || !m_UIElementStatics)
+ {
+ LogToFile(STARTUP_LOG, L"Win11StartInput: UIElement statics unavailable 0x%08X", hr);
+ return false;
+ }
+
+ if (FAILED(m_UIElementStatics->get_PointerEnteredEvent(&m_PointerEnteredEvent)) ||
+ FAILED(m_UIElementStatics->get_PointerMovedEvent(&m_PointerMovedEvent)) ||
+ FAILED(m_UIElementStatics->get_PointerPressedEvent(&m_PointerPressedEvent)) ||
+ FAILED(m_UIElementStatics->get_PointerReleasedEvent(&m_PointerReleasedEvent)) ||
+ FAILED(m_UIElementStatics->get_PointerExitedEvent(&m_PointerExitedEvent)))
+ {
+ LogToFile(STARTUP_LOG, L"Win11StartInput: pointer routed-event metadata unavailable");
+ m_PointerEnteredEvent.Release();
+ m_PointerMovedEvent.Release();
+ m_PointerPressedEvent.Release();
+ m_PointerReleasedEvent.Release();
+ m_PointerExitedEvent.Release();
+ m_UIElementStatics.Release();
+ return false;
+ }
+ return true;
+ }
+
+ bool GetInputRoot( InstanceHandle startHandle, InstanceHandle *rootHandle,
+ CComPtr &rootElement )
+ {
+ if (!m_Diagnostics)
+ return false;
+
+ std::vector ancestors;
+ EnterCriticalSection(&m_Lock);
+ auto it = m_Elements.find(startHandle);
+ InstanceHandle current = it != m_Elements.end() ? it->second.parent : 0;
+ for (int depth = 0; depth < 64 && current; depth++)
+ {
+ ancestors.push_back(current);
+ auto parent = m_Elements.find(current);
+ if (parent == m_Elements.end())
+ break;
+ current = parent->second.parent;
+ }
+ LeaveCriticalSection(&m_Lock);
+
+ for (auto it2 = ancestors.rbegin(); it2 != ancestors.rend(); ++it2)
+ {
+ CComPtr inspectable;
+ if (FAILED(m_Diagnostics->GetIInspectableFromHandle(*it2, &inspectable)) || !inspectable)
+ continue;
+
+ CComPtr element;
+ if (SUCCEEDED(inspectable->QueryInterface(__uuidof(ABI::Windows::UI::Xaml::IUIElement),
+ (void**)&element)) && element)
+ {
+ *rootHandle = *it2;
+ rootElement = element;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ HWND ResolveInputTaskbar( InstanceHandle, InstanceHandle rootHandle,
+ ABI::Windows::UI::Xaml::IUIElement *rootElement, InstanceHandle *sourceHandle )
+ {
+ if (sourceHandle)
+ *sourceHandle = 0;
+ if (!m_Diagnostics || !rootElement)
+ return NULL;
+
+ CComPtr rootIdentity;
+ if (FAILED(rootElement->QueryInterface(IID_PPV_ARGS(&rootIdentity))) || !rootIdentity)
+ return NULL;
+
+ std::vector sources;
+ EnterCriticalSection(&m_Lock);
+ for (auto it = m_XamlSources.begin(); it != m_XamlSources.end(); ++it)
+ sources.push_back(*it);
+ LeaveCriticalSection(&m_Lock);
+
+ for (size_t i = 0; i < sources.size(); i++)
+ {
+ CComPtr inspectable;
+ if (FAILED(m_Diagnostics->GetIInspectableFromHandle(sources[i], &inspectable)) || !inspectable)
+ continue;
+
+ CComPtr source;
+ if (FAILED(inspectable->QueryInterface(IID_PPV_ARGS(&source))) || !source)
+ continue;
+
+ CComPtr content;
+ if (FAILED(source->get_Content(&content)) || !content)
+ continue;
+
+ CComPtr contentIdentity;
+ if (FAILED(content->QueryInterface(IID_PPV_ARGS(&contentIdentity))) ||
+ contentIdentity != rootIdentity)
+ continue;
+
+ CComPtr nativeSource;
+ if (FAILED(inspectable->QueryInterface(IID_PPV_ARGS(&nativeSource))) || !nativeSource)
+ continue;
+
+ HWND xamlHwnd = NULL;
+ if (FAILED(nativeSource->get_WindowHandle(&xamlHwnd)) || !xamlHwnd)
+ continue;
+
+ HWND taskbar = FindTaskbarAncestor(xamlHwnd);
+ if (taskbar)
+ {
+ if (sourceHandle)
+ *sourceHandle = sources[i];
+ return taskbar;
+ }
+ }
+ return NULL;
+ }
+
+ bool AttachInputRoute( InstanceHandle startHandle )
+ {
+ if (FindInputRoute(startHandle))
+ return true;
+ if (!m_Diagnostics || !EnsureUIElementStatics())
+ return false;
+
+ CComPtr inspectable;
+ if (FAILED(m_Diagnostics->GetIInspectableFromHandle(startHandle, &inspectable)) || !inspectable)
+ return false;
+
+ StartInputRoute route;
+ route.startHandle = startHandle;
+ if (FAILED(inspectable->QueryInterface(__uuidof(ABI::Windows::UI::Xaml::IUIElement),
+ (void**)&route.startElement)) || !route.startElement)
+ return false;
+ if (FAILED(inspectable->QueryInterface(__uuidof(ABI::Windows::UI::Xaml::IFrameworkElement),
+ (void**)&route.startFramework)) || !route.startFramework)
+ return false;
+ if (!GetInputRoot(startHandle, &route.rootHandle, route.rootElement))
+ return false;
+ route.taskbar = ResolveInputTaskbar(startHandle, route.rootHandle,
+ route.rootElement, &route.sourceHandle);
+ if (!route.taskbar)
+ {
+ LogToFile(STARTUP_LOG, L"Win11StartInput: unable to resolve taskbar HWND for handle %llu; keeping WH_MOUSE fallback",
+ (unsigned long long)startHandle);
+ return false;
+ }
+
+ auto handler = Microsoft::WRL::Make(this, startHandle);
+ if (!handler)
+ return false;
+ route.pointerHandler = handler.Get();
+ HRESULT hr = handler->QueryInterface(IID_PPV_ARGS(&route.handler));
+ if (FAILED(hr) || !route.handler)
+ return false;
+
+ hr = route.rootElement->AddHandler(m_PointerEnteredEvent, route.handler, TRUE);
+ if (SUCCEEDED(hr))
+ hr = route.rootElement->AddHandler(m_PointerMovedEvent, route.handler, TRUE);
+ if (SUCCEEDED(hr))
+ hr = route.rootElement->AddHandler(m_PointerPressedEvent, route.handler, TRUE);
+ if (SUCCEEDED(hr))
+ hr = route.rootElement->AddHandler(m_PointerReleasedEvent, route.handler, TRUE);
+ if (SUCCEEDED(hr))
+ hr = route.rootElement->AddHandler(m_PointerExitedEvent, route.handler, TRUE);
+ if (FAILED(hr))
+ {
+ route.rootElement->RemoveHandler(m_PointerEnteredEvent, route.handler);
+ route.rootElement->RemoveHandler(m_PointerMovedEvent, route.handler);
+ route.rootElement->RemoveHandler(m_PointerPressedEvent, route.handler);
+ route.rootElement->RemoveHandler(m_PointerReleasedEvent, route.handler);
+ route.rootElement->RemoveHandler(m_PointerExitedEvent, route.handler);
+ LogToFile(STARTUP_LOG, L"Win11StartInput: routed handler attach failed 0x%08X", hr);
+ return false;
+ }
+
+ m_InputRoutes.push_back(route);
+ LogToFile(STARTUP_LOG, L"Win11StartInput: route attached for handle %llu via root %llu/source %llu",
+ (unsigned long long)startHandle, (unsigned long long)route.rootHandle,
+ (unsigned long long)route.sourceHandle);
+ return true;
+ }
+
+ void DetachInputRoute( size_t index )
+ {
+ if (index >= m_InputRoutes.size())
+ return;
+ StartInputRoute &route = m_InputRoutes[index];
+
+ if (route.pointerInside && route.taskbar)
+ {
+ POINT point = {};
+ GetCursorPos(&point);
+ PostInputMessage(route.taskbar, WM_MOUSELEAVE, point);
+ }
+
+ if (route.rootElement && route.handler)
+ {
+ if (m_PointerEnteredEvent) route.rootElement->RemoveHandler(m_PointerEnteredEvent, route.handler);
+ if (m_PointerMovedEvent) route.rootElement->RemoveHandler(m_PointerMovedEvent, route.handler);
+ if (m_PointerPressedEvent) route.rootElement->RemoveHandler(m_PointerPressedEvent, route.handler);
+ if (m_PointerReleasedEvent) route.rootElement->RemoveHandler(m_PointerReleasedEvent, route.handler);
+ if (m_PointerExitedEvent) route.rootElement->RemoveHandler(m_PointerExitedEvent, route.handler);
+ }
+ m_InputRoutes.erase(m_InputRoutes.begin() + index);
+ }
+
+ void DetachAllInputRoutes( void )
+ {
+ while (!m_InputRoutes.empty())
+ DetachInputRoute(m_InputRoutes.size() - 1);
+ m_ExpectedInputRoutes = 0;
+ }
+
+ void SyncInputRoutes( const std::vector &targets )
+ {
+ m_ExpectedInputRoutes = targets.size();
+
+ for (size_t i = 0; i < m_InputRoutes.size();)
+ {
+ bool keep = false;
+ for (size_t j = 0; j < targets.size(); j++)
+ if (targets[j] == m_InputRoutes[i].startHandle)
+ {
+ keep = true;
+ break;
+ }
+
+ // Visual-tree removals are applied after the diagnostics callback
+ // unwinds. A root can disappear before its Start descendant is reported
+ // removed, so validate both handles before retaining the route.
+ if (keep)
+ {
+ EnterCriticalSection(&m_Lock);
+ keep = m_Elements.find(m_InputRoutes[i].startHandle) != m_Elements.end() &&
+ m_Elements.find(m_InputRoutes[i].rootHandle) != m_Elements.end() &&
+ m_Elements.find(m_InputRoutes[i].sourceHandle) != m_Elements.end();
+ LeaveCriticalSection(&m_Lock);
+ }
+
+ if (!keep)
+ DetachInputRoute(i);
+ else
+ i++;
+ }
+
+ for (size_t i = 0; i < targets.size(); i++)
+ if (!FindInputRoute(targets[i]))
+ AttachInputRoute(targets[i]);
+ }
+
+ void EvaluateInputBridgeState( void )
+ {
+ if (!InterlockedCompareExchange(&g_StartButtonEnabled, 0, 0) ||
+ !m_ExpectedInputRoutes || m_InputRoutes.size() != m_ExpectedInputRoutes)
+ {
+ ReportInputBridgeState(WIN11_START_INPUT_FALLBACK);
+ return;
+ }
+
+ bool verified = true;
+ EnterCriticalSection(&m_Lock);
+ for (size_t i = 0; i < m_InputRoutes.size(); i++)
+ {
+ auto element = m_Elements.find(m_InputRoutes[i].startHandle);
+ if (element == m_Elements.end() || !element->second.hitTestOverride)
+ {
+ verified = false;
+ break;
+ }
+ if (!m_InputRoutes[i].verified)
+ verified = false;
+ }
+ LeaveCriticalSection(&m_Lock);
+
+ ReportInputBridgeState(verified ? WIN11_START_INPUT_ACTIVE : WIN11_START_INPUT_PROBING);
+ }
+
static LRESULT CALLBACK DispatchProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
CWin11StartButtonTap *tap = (CWin11StartButtonTap*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
@@ -352,11 +994,22 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
tap = (CWin11StartButtonTap*)create->lpCreateParams;
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)tap);
}
+ if (msg == WM_OS_STARTBUTTON_REPROBE && tap)
+ {
+ tap->AddRef();
+ tap->ReprobeInputBridgeState();
+ tap->Release();
+ return 0;
+ }
if (msg == WM_OS_STARTBUTTON_APPLY && tap)
{
+ // GWLP_USERDATA is a raw pointer. Hold the TAP alive while ApplyState
+ // may detach the last routed-event handler that also references it.
+ tap->AddRef();
bool enabled = InterlockedCompareExchange(&g_StartButtonActive, 0, 0) != 0 &&
InterlockedCompareExchange(&g_StartButtonEnabled, 0, 0) != 0;
tap->ApplyState(enabled);
+ tap->Release();
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
@@ -632,6 +1285,31 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
const bool allTaskbars = InterlockedCompareExchange(&g_AllTaskbars, 0, 0) != 0;
+ size_t startControlCount = 0;
+ for (size_t i = 0; i < elements.size(); i++)
+ if (elements[i].second.isStartControl)
+ startControlCount++;
+
+ // The fallback hook is thread-global. It can be retired only when every
+ // native Start slot has an Open-Shell replacement/input route. With
+ // multiple taskbars and AllTaskbars disabled, secondary native Start
+ // controls must remain hit-testable for touch/pen and keep the established
+ // WH_MOUSE fallback for mouse input.
+ const bool canRetireMouseHook = allTaskbars || startControlCount <= 1;
+ std::vector inputTargets;
+ if (enabled && canRetireMouseHook)
+ {
+ for (size_t i = 0; i < elements.size(); i++)
+ {
+ if (!elements[i].second.isStartControl)
+ continue;
+ InstanceHandle handle = elements[i].first;
+ if (allTaskbars || !primaryStart || handle == primaryStart)
+ inputTargets.push_back(handle);
+ }
+ }
+ SyncInputRoutes(inputTargets);
+
for (size_t i = 0; i < elements.size(); i++)
{
InstanceHandle handle = elements[i].first;
@@ -694,6 +1372,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
}
}
}
+
+ EvaluateInputBridgeState();
}
LONG m_Refs;
@@ -702,11 +1382,41 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
CRITICAL_SECTION m_Lock;
CComPtr m_Site;
CComPtr m_Visual;
+ CComPtr m_Diagnostics;
+ CComPtr m_UIElementStatics;
+ CComPtr m_PointerEnteredEvent;
+ CComPtr m_PointerMovedEvent;
+ CComPtr m_PointerPressedEvent;
+ CComPtr m_PointerReleasedEvent;
+ CComPtr m_PointerExitedEvent;
InstanceHandle m_PrimaryStart;
unsigned int m_NextDiscoveryOrder;
+ size_t m_ExpectedInputRoutes;
+ LONG m_LastInputState;
std::unordered_map m_Elements;
+ std::unordered_set m_XamlSources;
+ std::vector m_InputRoutes;
};
+CStartPointerHandler::CStartPointerHandler( CWin11StartButtonTap *owner, InstanceHandle startHandle )
+ : m_Owner(owner), m_StartHandle(startHandle)
+{
+ if (m_Owner)
+ m_Owner->AddRef();
+}
+
+CStartPointerHandler::~CStartPointerHandler( void )
+{
+ if (m_Owner)
+ m_Owner->Release();
+}
+
+HRESULT STDMETHODCALLTYPE CStartPointerHandler::Invoke( IInspectable *,
+ ABI::Windows::UI::Xaml::Input::IPointerRoutedEventArgs *args )
+{
+ return m_Owner ? m_Owner->OnStartPointer(m_StartHandle, args) : S_OK;
+}
+
static CWin11StartButtonTap *GetTapRef( void )
{
CWin11StartButtonTap *tap = NULL;
@@ -888,6 +1598,7 @@ extern "C" void StartWin11StartButtonTap( BOOL enabled, BOOL allTaskbars )
CWin11StartButtonTap *tap = GetTapRef();
if (tap)
{
+ tap->RequestInputBridgeReprobe();
tap->RequestApply(false);
tap->Release();
return;
@@ -904,6 +1615,9 @@ extern "C" void UpdateWin11StartButtonTap( BOOL enabled, BOOL allTaskbars )
CWin11StartButtonTap *tap = GetTapRef();
if (tap)
{
+ // Re-arm fallback/probing on every taskbar/settings update. Existing
+ // route verification may describe XAML handles from the previous layout.
+ tap->RequestInputBridgeReprobe();
tap->RequestApply(false);
tap->Release();
}
From 7dbc74f2f030937d980199cdc47191bdd08a4c4b Mon Sep 17 00:00:00 2001
From: YellowNest <59575587+YellowNest@users.noreply.github.com>
Date: Mon, 5 Oct 2026 18:35:55 +0200
Subject: [PATCH 2/5] Harden XAML input probing and coordinates
---
Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp | 25 ++++++++++++++++++++-
1 file changed, 24 insertions(+), 1 deletion(-)
diff --git a/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp b/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp
index 071559743..12d155d27 100644
--- a/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp
+++ b/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp
@@ -3689,7 +3689,14 @@ static LRESULT CALLBACK HookDesktopThreadMouse(int code, WPARAM wParam, LPARAM l
if (taskBar && info->hwnd!=taskBar->startButton && taskBar->oldButton)
{
if (inputState==WIN11_START_INPUT_PROBING && wParam==WM_MOUSEMOVE)
+ {
+ // 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);
+ }
// Preserve the existing behavior until XAML routing is confirmed.
PostMessage(taskBar->oldButton,(UINT)wParam,0,MAKELPARAM(info->pt.x,info->pt.y));
@@ -3751,7 +3758,23 @@ static bool RewriteWin11StartInputMessage( MSG *msg )
return false;
}
- POINT screenPoint={(short)LOWORD(msg->lParam),(short)HIWORD(msg->lParam)};
+ POINT screenPoint={};
+ if (mouseMessage!=WM_MOUSELEAVE)
+ {
+ // The helper transports coordinates relative to the taskbar HWND rather
+ // than global virtual-screen coordinates. LPARAM only carries signed
+ // 16-bit components; taskbar-relative values avoid truncating negative
+ // or very large multi-monitor desktop coordinates.
+ POINT taskbarPoint={(short)LOWORD(msg->lParam),(short)HIWORD(msg->lParam)};
+ 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;
From ab1b3dbce5dc1128faefdc2c8737133d9f413333 Mon Sep 17 00:00:00 2001
From: YellowNest <59575587+YellowNest@users.noreply.github.com>
Date: Mon, 5 Oct 2026 18:35:58 +0200
Subject: [PATCH 3/5] Keep routed pointer coordinates taskbar-relative
---
.../StartMenuHelper/Win11StartButtonTap.cpp | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp
index f727e2b69..be5d69899 100644
--- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp
+++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp
@@ -636,8 +636,16 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
{
if (!taskbar || !IsWindow(taskbar))
return false;
- return PostMessage(taskbar, GetWin11StartInputMessage(), mouseMessage,
- MAKELPARAM(screenPoint.x, screenPoint.y)) != FALSE;
+
+ LPARAM position = 0;
+ if (mouseMessage != WM_MOUSELEAVE)
+ {
+ POINT taskbarPoint = screenPoint;
+ if (!ScreenToClient(taskbar, &taskbarPoint))
+ return false;
+ position = MAKELPARAM(taskbarPoint.x, taskbarPoint.y);
+ }
+ return PostMessage(taskbar, GetWin11StartInputMessage(), mouseMessage, position) != FALSE;
}
void ReprobeInputBridgeState( void )
From 19c8265bfda2d209077a370a71ca30e0df93dece Mon Sep 17 00:00:00 2001
From: YellowNest <59575587+YellowNest@users.noreply.github.com>
Date: Mon, 5 Oct 2026 18:42:03 +0200
Subject: [PATCH 4/5] Use XAML pointer type for input routing
---
.../StartMenuHelper/Win11StartButtonTap.cpp | 24 ++++++++++++++++---
1 file changed, 21 insertions(+), 3 deletions(-)
diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp
index be5d69899..ad0808c11 100644
--- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp
+++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp
@@ -15,6 +15,7 @@
#include "..\StartMenuDLL\LogManager.h"
#include
+#include
#include
#include
#include
@@ -520,6 +521,17 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
return S_OK;
}
+ ABI::Windows::Devices::Input::PointerDeviceType deviceType =
+ ABI::Windows::Devices::Input::PointerDeviceType_Touch;
+ hr = pointer->get_PointerDeviceType(&deviceType);
+ if (FAILED(hr))
+ {
+ InvalidateInputRoute(*route);
+ return S_OK;
+ }
+ if (deviceType != ABI::Windows::Devices::Input::PointerDeviceType_Mouse)
+ return S_OK;
+
UINT32 pointerId = 0;
hr = pointer->get_PointerId(&pointerId);
if (FAILED(hr))
@@ -528,9 +540,15 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
return S_OK;
}
+ POINT screenPoint = {};
POINTER_INFO pointerInfo = {};
- if (!GetPointerInfo(pointerId, &pointerInfo) || pointerInfo.pointerType != PT_MOUSE)
+ if (GetPointerInfo(pointerId, &pointerInfo))
+ screenPoint = screenPoint;
+ else if (!GetCursorPos(&screenPoint))
+ {
+ InvalidateInputRoute(*route);
return S_OK;
+ }
CComPtr point;
hr = args->GetCurrentPoint(route->startElement, &point);
@@ -556,7 +574,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
if (!inside)
{
if (route->pointerInside && route->taskbar)
- PostInputMessage(route->taskbar, WM_MOUSELEAVE, pointerInfo.ptPixelLocation);
+ PostInputMessage(route->taskbar, WM_MOUSELEAVE, screenPoint);
route->pointerInside = false;
return S_OK;
}
@@ -602,7 +620,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
}
}
- if (!PostInputMessage(taskbar, mouseMessage, pointerInfo.ptPixelLocation))
+ if (!PostInputMessage(taskbar, mouseMessage, screenPoint))
{
InvalidateInputRoute(*route);
return S_OK;
From 801e49d90ae861137d3bee9254a3c4d7c7504fce Mon Sep 17 00:00:00 2001
From: YellowNest <59575587+YellowNest@users.noreply.github.com>
Date: Mon, 5 Oct 2026 18:43:37 +0200
Subject: [PATCH 5/5] Preserve double-click routing without mouse hook
---
.../StartMenuHelper/Win11StartButtonTap.cpp | 44 +++++++++++++++++++
1 file changed, 44 insertions(+)
diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp
index ad0808c11..04d9f59c0 100644
--- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp
+++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp
@@ -149,6 +149,9 @@ struct StartInputRoute
bool verified;
bool pointerInside;
HWND taskbar;
+ DWORD lastPressTime;
+ POINT lastPressPoint;
+ UINT lastPressMessage;
StartInputRoute( void )
{
@@ -158,6 +161,10 @@ struct StartInputRoute
verified = false;
pointerInside = false;
taskbar = NULL;
+ lastPressTime = 0;
+ lastPressPoint.x = 0;
+ lastPressPoint.y = 0;
+ lastPressMessage = 0;
}
};
@@ -620,6 +627,43 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
}
}
+ // WH_MOUSE used to deliver Windows-generated double-click messages.
+ // Routed pointer events expose button transitions instead, so preserve the
+ // same behavior before retiring the hook.
+ UINT doubleClickMessage = 0;
+ if (mouseMessage == WM_LBUTTONDOWN)
+ doubleClickMessage = WM_LBUTTONDBLCLK;
+ else if (mouseMessage == WM_RBUTTONDOWN)
+ doubleClickMessage = WM_RBUTTONDBLCLK;
+ else if (mouseMessage == WM_MBUTTONDOWN)
+ doubleClickMessage = WM_MBUTTONDBLCLK;
+
+ if (doubleClickMessage)
+ {
+ DWORD now = GetTickCount();
+ LONG dx = screenPoint.x - route->lastPressPoint.x;
+ LONG dy = screenPoint.y - route->lastPressPoint.y;
+ if (dx < 0) dx = -dx;
+ if (dy < 0) dy = -dy;
+ const int maxDx = GetSystemMetrics(SM_CXDOUBLECLK) / 2;
+ const int maxDy = GetSystemMetrics(SM_CYDOUBLECLK) / 2;
+
+ if (route->lastPressMessage == mouseMessage &&
+ now - route->lastPressTime <= GetDoubleClickTime() &&
+ dx <= maxDx && dy <= maxDy)
+ {
+ mouseMessage = doubleClickMessage;
+ route->lastPressTime = 0;
+ route->lastPressMessage = 0;
+ }
+ else
+ {
+ route->lastPressTime = now;
+ route->lastPressPoint = screenPoint;
+ route->lastPressMessage = mouseMessage;
+ }
+ }
+
if (!PostInputMessage(taskbar, mouseMessage, screenPoint))
{
InvalidateInputRoute(*route);