From 052a9fc250dd3eefdc6a7834fb0bfbc1654db22c Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 3 Oct 2026 16:13:46 +0200 Subject: [PATCH 01/26] Hide native Windows 11 Start button when using a replacement --- Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp | 5 + .../StartMenuDLL/StartMenuDLL.vcxproj | 2 + .../StartMenuDLL/StartMenuDLL.vcxproj.filters | 6 + .../StartMenuDLL/Win11StartButton.cpp | 745 ++++++++++++++++++ Src/StartMenu/StartMenuDLL/Win11StartButton.h | 8 + 5 files changed, 766 insertions(+) create mode 100644 Src/StartMenu/StartMenuDLL/Win11StartButton.cpp create mode 100644 Src/StartMenu/StartMenuDLL/Win11StartButton.h diff --git a/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp b/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp index 3a6b18770..088569e08 100644 --- a/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp +++ b/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp @@ -14,6 +14,7 @@ #include "ResourceHelper.h" #include "LogManager.h" #include "TouchHelper.h" +#include "Win11StartButton.h" #include "IatHookHelper.h" #include "dllmain.h" #include @@ -2643,6 +2644,7 @@ void UpdateTaskBars( TUpdateTaskbar update ) InvalidateRect(taskBar.taskBar,NULL,TRUE); PostMessage(taskBar.taskBar,WM_THEMECHANGED,0,0); } + UpdateWin11StartButtonMonitor(); } /////////////////////////////////////////////////////////////////////////////// @@ -3227,6 +3229,7 @@ if (!g_bTrimHooks) UpdateTaskBars(TASKBAR_RECREATE_BUTTONS); UpdateTaskBars(TASKBAR_UPDATE_TEXTURE); + StartWin11StartButtonMonitor(); } static void RecreateStartButton( size_t taskbarId ) @@ -3288,6 +3291,8 @@ static DWORD WINAPI ExitThreadProc( void *param ) static void CleanStartMenuDLL( void ) { + StopWin11StartButtonMonitor(); + ClearIatHook(g_DwmpBTRHook); g_DwmpBTRHook=NULL; ClearIatHook(g_DwmpTWWRHook); diff --git a/Src/StartMenu/StartMenuDLL/StartMenuDLL.vcxproj b/Src/StartMenu/StartMenuDLL/StartMenuDLL.vcxproj index 0412e588c..416c5a52f 100644 --- a/Src/StartMenu/StartMenuDLL/StartMenuDLL.vcxproj +++ b/Src/StartMenu/StartMenuDLL/StartMenuDLL.vcxproj @@ -89,6 +89,7 @@ Create + @@ -110,6 +111,7 @@ + diff --git a/Src/StartMenu/StartMenuDLL/StartMenuDLL.vcxproj.filters b/Src/StartMenu/StartMenuDLL/StartMenuDLL.vcxproj.filters index fc2bd40cb..bfb839860 100644 --- a/Src/StartMenu/StartMenuDLL/StartMenuDLL.vcxproj.filters +++ b/Src/StartMenu/StartMenuDLL/StartMenuDLL.vcxproj.filters @@ -72,6 +72,9 @@ Source Files + + Source Files + @@ -128,6 +131,9 @@ Header Files + + Header Files + Resource Files diff --git a/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp b/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp new file mode 100644 index 000000000..1bb94cb35 --- /dev/null +++ b/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp @@ -0,0 +1,745 @@ +// Windows 11 native Start button suppression. +// +// Open-Shell's replacement button is a separate layered window. Windows 11 +// renders its own Start button in XAML, so the old HWND hiding logic cannot +// remove the native glyph. This file uses the public XAML diagnostics API to +// suppress only the native Start glyph and its hit target. The XAML element is +// left in layout, so centered taskbar positioning remains owned by Windows. + +#include "stdafx.h" +#include "Win11StartButton.h" +#include "Settings.h" +#include "LogManager.h" +#include "ResourceHelper.h" + +#include +#include +#include +#include +#include + +static const GUID CLSID_OpenShellStartButtonTap = +{ 0x7d15741f, 0x2f3b, 0x4971, { 0xb8, 0x91, 0x6a, 0x5d, 0x42, 0xd7, 0x1a, 0x34 } }; + +static const UINT WM_OS_STARTBUTTON_APPLY = WM_APP + 0x35B; + +static volatile LONG g_StartButtonActive = 0; +static volatile LONG g_ConnectStarted = 0; + +static HMODULE GetThisModule( void ) +{ + HMODULE module = NULL; + GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCTSTR)&GetThisModule, &module); + return module; +} + +struct StartElement +{ + InstanceHandle parent; + CString type; + CString name; + bool visibilityOverride; + bool hitTestOverride; + + StartElement( void ) + { + parent = 0; + visibilityOverride = false; + hitTestOverride = false; + } +}; + +class CWin11StartButtonTap; +static CWin11StartButtonTap *g_Tap = NULL; +static SRWLOCK g_TapLock = SRWLOCK_INIT; + +static bool ContainsText( const CString &text, const wchar_t *part ) +{ + return text.Find(part) >= 0; +} + +static bool IsStartControl( const StartElement &element ) +{ + if (!ContainsText(element.type, L"ExperienceToggleButton")) + return false; + return element.name == L"LaunchListButton" || element.name == L"StartButton"; +} + +static bool IsStartGlyph( const StartElement &element ) +{ + if (element.name == L"Icon") + return true; + if (ContainsText(element.type, L"AnimatedVisualPlayer") || ContainsText(element.type, L"AepAnimatedIcon")) + return true; + if (ContainsText(element.type, L"FontIcon") || ContainsText(element.type, L"PathIcon") || + ContainsText(element.type, L"ImageIcon") || ContainsText(element.type, L"BitmapIcon") || + ContainsText(element.type, L"SymbolIcon")) + return true; + return false; +} + +class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCallback2 +{ +public: + CWin11StartButtonTap( void ) + { + m_Refs = 1; + m_Advised = false; + m_Dispatch = NULL; + m_PrimaryStart = 0; + InitializeCriticalSection(&m_Lock); + } + + ~CWin11StartButtonTap( void ) + { + if (m_Visual && m_Advised) + m_Visual->UnadviseVisualTreeChange(static_cast(this)); + if (m_Dispatch && GetWindowThreadProcessId(m_Dispatch, NULL) == GetCurrentThreadId()) + DestroyWindow(m_Dispatch); + + AcquireSRWLockExclusive(&g_TapLock); + if (g_Tap == this) + g_Tap = NULL; + ReleaseSRWLockExclusive(&g_TapLock); + + InterlockedExchange(&g_ConnectStarted, 0); + DeleteCriticalSection(&m_Lock); + } + + STDMETHODIMP QueryInterface( REFIID riid, void **ppv ) + { + if (!ppv) + return E_POINTER; + *ppv = NULL; + + if (riid == IID_IUnknown || riid == IID_IObjectWithSite) + *ppv = static_cast(this); + else if (riid == __uuidof(IVisualTreeServiceCallback) || riid == __uuidof(IVisualTreeServiceCallback2)) + *ppv = static_cast(this); + else + return E_NOINTERFACE; + + AddRef(); + return S_OK; + } + + STDMETHODIMP_(ULONG) AddRef( void ) + { + return (ULONG)InterlockedIncrement(&m_Refs); + } + + STDMETHODIMP_(ULONG) Release( void ) + { + LONG refs = InterlockedDecrement(&m_Refs); + if (!refs) + delete this; + return (ULONG)refs; + } + + STDMETHODIMP SetSite( IUnknown *site ) + { + if (m_Visual && m_Advised) + { + ApplyState(false); + m_Visual->UnadviseVisualTreeChange(static_cast(this)); + m_Advised = false; + } + m_Visual.Release(); + m_Site.Release(); + + if (!site) + { + AcquireSRWLockExclusive(&g_TapLock); + if (g_Tap == this) + g_Tap = NULL; + ReleaseSRWLockExclusive(&g_TapLock); + InterlockedExchange(&g_ConnectStarted, 0); + return S_OK; + } + + EnterCriticalSection(&m_Lock); + m_Elements.clear(); + m_PrimaryStart = 0; + LeaveCriticalSection(&m_Lock); + + m_Site = site; + HRESULT hr = site->QueryInterface(__uuidof(IVisualTreeService), (void**)&m_Visual); + if (FAILED(hr) || !m_Visual) + return hr; + + if (!CreateDispatchWindow()) + return HRESULT_FROM_WIN32(GetLastError()); + + AcquireSRWLockExclusive(&g_TapLock); + g_Tap = this; + ReleaseSRWLockExclusive(&g_TapLock); + + // Advise replays the existing tree. OnVisualTreeChange only records + // element handles; all property access is dispatched afterwards. + hr = m_Visual->AdviseVisualTreeChange(static_cast(this)); + if (SUCCEEDED(hr)) + { + m_Advised = true; + RequestApply(false); + } + LogToFile(STARTUP_LOG, L"Win11StartButton: visual tree advise 0x%08X", hr); + return hr; + } + + STDMETHODIMP GetSite( REFIID riid, void **ppv ) + { + if (!ppv) + return E_POINTER; + *ppv = NULL; + if (!m_Site) + return E_FAIL; + return m_Site->QueryInterface(riid, ppv); + } + + STDMETHODIMP OnVisualTreeChange( ParentChildRelation relation, VisualElement element, VisualMutationType mutationType ) + { + bool interesting = false; + + EnterCriticalSection(&m_Lock); + if (mutationType == Remove) + { + auto it = m_Elements.find(element.Handle); + if (it != m_Elements.end()) + { + interesting = it->second.visibilityOverride || it->second.hitTestOverride || IsStartControl(it->second); + bool wasPrimary = element.Handle == m_PrimaryStart; + m_Elements.erase(it); + if (wasPrimary) + { + m_PrimaryStart = 0; + for (auto candidate = m_Elements.begin(); candidate != m_Elements.end(); ++candidate) + { + if (IsStartControl(candidate->second)) + { + m_PrimaryStart = candidate->first; + break; + } + } + } + } + } + else if (mutationType == Add) + { + StartElement record; + record.parent = relation.Parent; + record.type = element.Type ? element.Type : L""; + record.name = element.Name ? element.Name : L""; + + auto previous = m_Elements.find(element.Handle); + if (previous != m_Elements.end()) + { + record.visibilityOverride = previous->second.visibilityOverride; + record.hitTestOverride = previous->second.hitTestOverride; + } + m_Elements[element.Handle] = record; + if (IsStartControl(record) && !m_PrimaryStart) + m_PrimaryStart = element.Handle; + + interesting = IsStartControl(record) || IsStartGlyph(record) || IsUnderStartButtonLocked(record.parent); + } + LeaveCriticalSection(&m_Lock); + + if (interesting) + RequestApply(false); + return S_OK; + } + + STDMETHODIMP OnElementStateChanged( InstanceHandle, VisualElementState, LPCWSTR ) + { + return S_OK; + } + + void RequestApply( bool synchronous ) + { + if (!m_Dispatch) + return; + if (synchronous) + SendMessage(m_Dispatch, WM_OS_STARTBUTTON_APPLY, 0, 0); + else + PostMessage(m_Dispatch, WM_OS_STARTBUTTON_APPLY, 0, 0); + } + +private: + static LRESULT CALLBACK DispatchProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ) + { + CWin11StartButtonTap *tap = (CWin11StartButtonTap*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (msg == WM_NCCREATE) + { + CREATESTRUCT *create = (CREATESTRUCT*)lParam; + tap = (CWin11StartButtonTap*)create->lpCreateParams; + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)tap); + } + if (msg == WM_OS_STARTBUTTON_APPLY && tap) + { + bool enabled = InterlockedCompareExchange(&g_StartButtonActive, 0, 0) != 0 && + GetSettingBool(L"EnableStartButton"); + tap->ApplyState(enabled); + return 0; + } + return DefWindowProc(hwnd, msg, wParam, lParam); + } + + bool CreateDispatchWindow( void ) + { + if (m_Dispatch) + return true; + + static const wchar_t CLASS_NAME[] = L"OpenShell.Win11StartButtonTap"; + WNDCLASS wc = {}; + HMODULE module = GetThisModule(); + if (!module) + return false; + + wc.lpfnWndProc = DispatchProc; + wc.hInstance = module; + wc.lpszClassName = CLASS_NAME; + if (!RegisterClass(&wc) && GetLastError() != ERROR_CLASS_ALREADY_EXISTS) + return false; + + m_Dispatch = CreateWindowEx(0, CLASS_NAME, L"", 0, 0, 0, 0, 0, + HWND_MESSAGE, NULL, module, this); + return m_Dispatch != NULL; + } + + bool IsUnderStartButtonLocked( InstanceHandle parent ) const + { + for (int depth = 0; depth < 24 && parent; depth++) + { + auto it = m_Elements.find(parent); + if (it == m_Elements.end()) + break; + if (IsStartControl(it->second)) + return true; + parent = it->second.parent; + } + return false; + } + + InstanceHandle GetStartAncestor( InstanceHandle handle ) + { + InstanceHandle result = 0; + EnterCriticalSection(&m_Lock); + auto it = m_Elements.find(handle); + if (it != m_Elements.end()) + { + InstanceHandle parent = it->second.parent; + for (int depth = 0; depth < 24 && parent; depth++) + { + auto pit = m_Elements.find(parent); + if (pit == m_Elements.end()) + break; + if (IsStartControl(pit->second)) + { + result = parent; + break; + } + parent = pit->second.parent; + } + } + LeaveCriticalSection(&m_Lock); + return result; + } + + static void FreeProperties( PropertyChainSource *sources, unsigned int sourceCount, + PropertyChainValue *values, unsigned int valueCount ) + { + if (sources) + { + for (unsigned int i = 0; i < sourceCount; i++) + { + SysFreeString(sources[i].TargetType); + SysFreeString(sources[i].Name); + SysFreeString(sources[i].SrcInfo.FileName); + SysFreeString(sources[i].SrcInfo.Hash); + } + CoTaskMemFree(sources); + } + if (values) + { + for (unsigned int i = 0; i < valueCount; i++) + { + SysFreeString(values[i].Type); + SysFreeString(values[i].DeclaringType); + SysFreeString(values[i].ValueType); + SysFreeString(values[i].ItemType); + SysFreeString(values[i].Value); + SysFreeString(values[i].PropertyName); + } + CoTaskMemFree(values); + } + } + + HRESULT FindProperty( InstanceHandle handle, const wchar_t *name, unsigned int *index, CString *typeName ) + { + unsigned int sourceCount = 0; + unsigned int valueCount = 0; + PropertyChainSource *sources = NULL; + PropertyChainValue *values = NULL; + HRESULT hr = m_Visual->GetPropertyValuesChain(handle, &sourceCount, &sources, &valueCount, &values); + if (FAILED(hr)) + { + FreeProperties(sources, sourceCount, values, valueCount); + return hr; + } + + bool found = false; + for (unsigned int i = 0; i < valueCount; i++) + { + if (!values[i].PropertyName || wcscmp(values[i].PropertyName, name) != 0) + continue; + if (values[i].MetadataBits & 0x2) // IsPropertyReadOnly + continue; + + *index = values[i].Index; + if (typeName) + *typeName = values[i].Type ? values[i].Type : L""; + found = true; + break; + } + FreeProperties(sources, sourceCount, values, valueCount); + return found ? S_OK : HRESULT_FROM_WIN32(ERROR_NOT_FOUND); + } + + HRESULT SetPropertyText( InstanceHandle handle, const wchar_t *name, const wchar_t *valueText ) + { + unsigned int index = 0; + CString typeName; + HRESULT hr = FindProperty(handle, name, &index, &typeName); + if (FAILED(hr) || typeName.IsEmpty()) + return FAILED(hr) ? hr : E_FAIL; + + CComBSTR type(typeName); + CComBSTR value(valueText); + InstanceHandle created = 0; + hr = m_Visual->CreateInstance(type, value, &created); + if (FAILED(hr)) + return hr; + return m_Visual->SetProperty(handle, created, index); + } + + HRESULT ClearPropertyByName( InstanceHandle handle, const wchar_t *name ) + { + unsigned int index = 0; + HRESULT hr = FindProperty(handle, name, &index, NULL); + if (FAILED(hr)) + return hr; + return m_Visual->ClearProperty(handle, index); + } + + void SetOverrideFlags( InstanceHandle handle, bool *visibility, bool *hitTest ) + { + EnterCriticalSection(&m_Lock); + auto it = m_Elements.find(handle); + if (it != m_Elements.end()) + { + if (visibility) + it->second.visibilityOverride = *visibility; + if (hitTest) + it->second.hitTestOverride = *hitTest; + } + LeaveCriticalSection(&m_Lock); + } + + void ApplyState( bool enabled ) + { + if (!m_Visual) + return; + + std::vector> elements; + InstanceHandle primaryStart = 0; + EnterCriticalSection(&m_Lock); + primaryStart = m_PrimaryStart; + for (auto it = m_Elements.begin(); it != m_Elements.end(); ++it) + elements.push_back(*it); + LeaveCriticalSection(&m_Lock); + + const bool allTaskbars = GetSettingBool(L"AllTaskbars"); + + for (size_t i = 0; i < elements.size(); i++) + { + InstanceHandle handle = elements[i].first; + StartElement record = elements[i].second; + + if (IsStartControl(record)) + { + bool target = allTaskbars || !primaryStart || handle == primaryStart; + if (enabled && target) + { + if (!record.hitTestOverride) + { + HRESULT hr = SetPropertyText(handle, L"IsHitTestVisible", L"False"); + if (SUCCEEDED(hr)) + { + bool value = true; + SetOverrideFlags(handle, NULL, &value); + } + } + } + else if (record.hitTestOverride) + { + HRESULT hr = ClearPropertyByName(handle, L"IsHitTestVisible"); + if (SUCCEEDED(hr)) + { + bool value = false; + SetOverrideFlags(handle, NULL, &value); + } + } + continue; + } + + if (!IsStartGlyph(record)) + continue; + + InstanceHandle startAncestor = GetStartAncestor(handle); + if (!startAncestor) + continue; + bool target = allTaskbars || !primaryStart || startAncestor == primaryStart; + + if (enabled && target) + { + if (!record.visibilityOverride) + { + HRESULT hr = SetPropertyText(handle, L"Visibility", L"Collapsed"); + if (SUCCEEDED(hr)) + { + bool value = true; + SetOverrideFlags(handle, &value, NULL); + } + } + } + else if (record.visibilityOverride) + { + HRESULT hr = ClearPropertyByName(handle, L"Visibility"); + if (SUCCEEDED(hr)) + { + bool value = false; + SetOverrideFlags(handle, &value, NULL); + } + } + } + } + + LONG m_Refs; + bool m_Advised; + HWND m_Dispatch; + CRITICAL_SECTION m_Lock; + CComPtr m_Site; + CComPtr m_Visual; + InstanceHandle m_PrimaryStart; + std::unordered_map m_Elements; +}; + +static CWin11StartButtonTap *GetTapRef( void ) +{ + CWin11StartButtonTap *tap = NULL; + AcquireSRWLockShared(&g_TapLock); + tap = g_Tap; + if (tap) + tap->AddRef(); + ReleaseSRWLockShared(&g_TapLock); + return tap; +} + +class CStartButtonTapFactory: public IClassFactory +{ +public: + CStartButtonTapFactory( void ) { m_Refs = 1; } + + STDMETHODIMP QueryInterface( REFIID riid, void **ppv ) + { + if (!ppv) + return E_POINTER; + *ppv = NULL; + if (riid != IID_IUnknown && riid != IID_IClassFactory) + return E_NOINTERFACE; + *ppv = static_cast(this); + AddRef(); + return S_OK; + } + + STDMETHODIMP_(ULONG) AddRef( void ) { return (ULONG)InterlockedIncrement(&m_Refs); } + STDMETHODIMP_(ULONG) Release( void ) { return (ULONG)InterlockedDecrement(&m_Refs); } + + STDMETHODIMP CreateInstance( IUnknown *outer, REFIID riid, void **ppv ) + { + if (!ppv) + return E_POINTER; + *ppv = NULL; + if (outer) + return CLASS_E_NOAGGREGATION; + + CWin11StartButtonTap *tap = new CWin11StartButtonTap(); + if (!tap) + return E_OUTOFMEMORY; + HRESULT hr = tap->QueryInterface(riid, ppv); + tap->Release(); + return hr; + } + + STDMETHODIMP LockServer( BOOL ) { return S_OK; } + +private: + LONG m_Refs; +}; + +static CStartButtonTapFactory g_Factory; + +// xamlom.h declares DllGetClassObject through the platform headers, so export +// our implementation under that name via a linker alias. +extern "C" HRESULT STDMETHODCALLTYPE OpenShellStartButtonDllGetClassObject( REFCLSID clsid, REFIID riid, LPVOID *ppv ) +{ + if (!IsEqualGUID(clsid, CLSID_OpenShellStartButtonTap)) + return CLASS_E_CLASSNOTAVAILABLE; + return g_Factory.QueryInterface(riid, ppv); +} + +#ifdef _M_IX86 +#pragma comment(linker, "/EXPORT:DllGetClassObject=_OpenShellStartButtonDllGetClassObject@12,PRIVATE") +#else +#pragma comment(linker, "/EXPORT:DllGetClassObject=OpenShellStartButtonDllGetClassObject,PRIVATE") +#endif + +typedef HRESULT (WINAPI *InitXamlDiagnosticsEx_t)( LPCWSTR, DWORD, LPCWSTR, LPCWSTR, CLSID, LPCWSTR ); + +struct ConnectAttempt +{ + InitXamlDiagnosticsEx_t init; + const wchar_t *endpoint; + wchar_t dllPath[MAX_PATH]; + HRESULT hr; +}; + +static DWORD WINAPI ConnectAttemptThread( LPVOID param ) +{ + ConnectAttempt *attempt = (ConnectAttempt*)param; + attempt->hr = attempt->init(attempt->endpoint, GetCurrentProcessId(), NULL, + attempt->dllPath, CLSID_OpenShellStartButtonTap, NULL); + return 0; +} + +static DWORD WINAPI ConnectThread( LPVOID ) +{ + HMODULE runtime = LoadLibraryEx(L"Windows.UI.Xaml.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (!runtime) + { + InterlockedExchange(&g_ConnectStarted, 0); + return 0; + } + + InitXamlDiagnosticsEx_t init = (InitXamlDiagnosticsEx_t)GetProcAddress(runtime, "InitializeXamlDiagnosticsEx"); + if (!init) + { + FreeLibrary(runtime); + InterlockedExchange(&g_ConnectStarted, 0); + return 0; + } + + HMODULE module = GetThisModule(); + wchar_t dllPath[MAX_PATH]; + if (!module || !GetModuleFileName(module, dllPath, _countof(dllPath))) + { + FreeLibrary(runtime); + InterlockedExchange(&g_ConnectStarted, 0); + return 0; + } + + const wchar_t *endpoints[] = { L"VisualDiagConnection1", L"VisualDiagConnection2" }; + HRESULT last = E_FAIL; + for (int retry = 0; retry < 8 && InterlockedCompareExchange(&g_StartButtonActive, 0, 0); retry++) + { + for (int i = 0; i < _countof(endpoints); i++) + { + ConnectAttempt attempt = {}; + attempt.init = init; + attempt.endpoint = endpoints[i]; + Strcpy(attempt.dllPath, _countof(attempt.dllPath), dllPath); + attempt.hr = E_FAIL; + + HANDLE thread = CreateThread(NULL, 0, ConnectAttemptThread, &attempt, 0, NULL); + if (!thread) + continue; + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); + last = attempt.hr; + if (SUCCEEDED(last)) + { + LogToFile(STARTUP_LOG, L"Win11StartButton: connected using %s", endpoints[i]); + FreeLibrary(runtime); + return 0; + } + } + Sleep(500); + } + + LogToFile(STARTUP_LOG, L"Win11StartButton: connection failed 0x%08X", last); + FreeLibrary(runtime); + InterlockedExchange(&g_ConnectStarted, 0); + return 0; +} + +static void EnsureConnection( void ) +{ + if (InterlockedCompareExchange(&g_ConnectStarted, 1, 0) != 0) + return; + + HANDLE thread = CreateThread(NULL, 0, ConnectThread, NULL, 0, NULL); + if (thread) + CloseHandle(thread); + else + InterlockedExchange(&g_ConnectStarted, 0); +} + +void StartWin11StartButtonMonitor( void ) +{ + if (!IsWin11()) + return; + + InterlockedExchange(&g_StartButtonActive, 1); + CWin11StartButtonTap *tap = GetTapRef(); + if (tap) + { + tap->RequestApply(false); + tap->Release(); + return; + } + EnsureConnection(); +} + +void UpdateWin11StartButtonMonitor( void ) +{ + if (!IsWin11()) + return; + + CWin11StartButtonTap *tap = GetTapRef(); + if (tap) + { + tap->RequestApply(false); + tap->Release(); + } + else if (InterlockedCompareExchange(&g_StartButtonActive, 0, 0)) + { + EnsureConnection(); + } +} + +void StopWin11StartButtonMonitor( void ) +{ + if (!IsWin11()) + return; + + InterlockedExchange(&g_StartButtonActive, 0); + CWin11StartButtonTap *tap = GetTapRef(); + if (tap) + { + // Synchronous restore: don't let Open-Shell exit while the native + // Start button is still carrying our XAML overrides. + tap->RequestApply(true); + tap->Release(); + } +} diff --git a/Src/StartMenu/StartMenuDLL/Win11StartButton.h b/Src/StartMenu/StartMenuDLL/Win11StartButton.h new file mode 100644 index 000000000..b4bc15b56 --- /dev/null +++ b/Src/StartMenu/StartMenuDLL/Win11StartButton.h @@ -0,0 +1,8 @@ +#pragma once + +// Windows 11 draws the native Start button in XAML. These helpers hide only +// the native glyph/hit target while Open-Shell's replacement button is active. +// They never move or resize either button. +void StartWin11StartButtonMonitor( void ); +void UpdateWin11StartButtonMonitor( void ); +void StopWin11StartButtonMonitor( void ); From 9f572bb07419216a6b3fe61f6ff64d828b52dd33 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sun, 4 Oct 2026 18:35:40 +0200 Subject: [PATCH 02/26] Fix Win11 TAP control matching and shutdown --- .../StartMenuDLL/Win11StartButton.cpp | 215 ++++++++++++++++-- 1 file changed, 191 insertions(+), 24 deletions(-) diff --git a/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp b/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp index 1bb94cb35..01f4a2df0 100644 --- a/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp +++ b/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp @@ -22,6 +22,7 @@ static const GUID CLSID_OpenShellStartButtonTap = { 0x7d15741f, 0x2f3b, 0x4971, { 0xb8, 0x91, 0x6a, 0x5d, 0x42, 0xd7, 0x1a, 0x34 } }; static const UINT WM_OS_STARTBUTTON_APPLY = WM_APP + 0x35B; +static const UINT WM_OS_STARTBUTTON_DESTROY = WM_APP + 0x35C; static volatile LONG g_StartButtonActive = 0; static volatile LONG g_ConnectStarted = 0; @@ -41,12 +42,18 @@ struct StartElement CString name; bool visibilityOverride; bool hitTestOverride; + bool startControlResolved; + bool isStartControl; + unsigned int discoveryOrder; StartElement( void ) { parent = 0; visibilityOverride = false; hitTestOverride = false; + startControlResolved = false; + isStartControl = false; + discoveryOrder = 0; } }; @@ -59,7 +66,7 @@ static bool ContainsText( const CString &text, const wchar_t *part ) return text.Find(part) >= 0; } -static bool IsStartControl( const StartElement &element ) +static bool IsStartControlCandidate( const StartElement &element ) { if (!ContainsText(element.type, L"ExperienceToggleButton")) return false; @@ -88,6 +95,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal m_Advised = false; m_Dispatch = NULL; m_PrimaryStart = 0; + m_NextDiscoveryOrder = 0; + m_InjectionReferenceBalanced = false; InitializeCriticalSection(&m_Lock); } @@ -161,9 +170,33 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal EnterCriticalSection(&m_Lock); m_Elements.clear(); m_PrimaryStart = 0; + m_NextDiscoveryOrder = 0; LeaveCriticalSection(&m_Lock); m_Site = site; + + // InitializeXamlDiagnosticsEx loads the TAP DLL by path even though + // StartMenuDLL is already loaded. Balance that injection reference so + // Open-Shell can unload and initialize the DLL again after an explicit Exit. + if (!m_InjectionReferenceBalanced) + { + HMODULE module = GetThisModule(); + if (module) + { + FreeLibrary(module); + m_InjectionReferenceBalanced = true; + } + } + + // Exit may race a connection attempt. Do not attach a new callback after + // shutdown has already begun. + if (!InterlockedCompareExchange(&g_StartButtonActive, 0, 0)) + { + m_Site.Release(); + InterlockedExchange(&g_ConnectStarted, 0); + return S_OK; + } + HRESULT hr = site->QueryInterface(__uuidof(IVisualTreeService), (void**)&m_Visual); if (FAILED(hr) || !m_Visual) return hr; @@ -207,21 +240,12 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal auto it = m_Elements.find(element.Handle); if (it != m_Elements.end()) { - interesting = it->second.visibilityOverride || it->second.hitTestOverride || IsStartControl(it->second); + interesting = it->second.visibilityOverride || it->second.hitTestOverride || + it->second.isStartControl || IsStartControlCandidate(it->second); bool wasPrimary = element.Handle == m_PrimaryStart; m_Elements.erase(it); if (wasPrimary) - { - m_PrimaryStart = 0; - for (auto candidate = m_Elements.begin(); candidate != m_Elements.end(); ++candidate) - { - if (IsStartControl(candidate->second)) - { - m_PrimaryStart = candidate->first; - break; - } - } - } + m_PrimaryStart = FindPrimaryStartLocked(); } } else if (mutationType == Add) @@ -236,12 +260,18 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal { record.visibilityOverride = previous->second.visibilityOverride; record.hitTestOverride = previous->second.hitTestOverride; + record.startControlResolved = previous->second.startControlResolved; + record.isStartControl = previous->second.isStartControl; + record.discoveryOrder = previous->second.discoveryOrder; + } + else + { + record.discoveryOrder = ++m_NextDiscoveryOrder; } m_Elements[element.Handle] = record; - if (IsStartControl(record) && !m_PrimaryStart) - m_PrimaryStart = element.Handle; - interesting = IsStartControl(record) || IsStartGlyph(record) || IsUnderStartButtonLocked(record.parent); + interesting = IsStartControlCandidate(record) || IsStartGlyph(record) || + IsUnderStartButtonLocked(record.parent); } LeaveCriticalSection(&m_Lock); @@ -265,6 +295,43 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal PostMessage(m_Dispatch, WM_OS_STARTBUTTON_APPLY, 0, 0); } + void Shutdown( void ) + { + // Prevent new users of the TAP while teardown is in progress. The caller + // holds a reference, so UnadviseVisualTreeChange cannot destroy this object + // out from under the shutdown sequence. + AcquireSRWLockExclusive(&g_TapLock); + if (g_Tap == this) + g_Tap = NULL; + ReleaseSRWLockExclusive(&g_TapLock); + + // Restore the native XAML state before detaching the callback. + RequestApply(true); + + if (m_Visual && m_Advised) + { + HRESULT hr = m_Visual->UnadviseVisualTreeChange(static_cast(this)); + if (FAILED(hr)) + { + // Keep the DLL resident rather than leave XAML with a callback into + // unloaded code. Explorer restart remains the safe recovery path. + HMODULE module = NULL; + GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + (LPCTSTR)&GetThisModule, &module); + LogToFile(STARTUP_LOG, L"Win11StartButton: visual tree unadvise failed 0x%08X", hr); + return; + } + m_Advised = false; + } + + m_Visual.Release(); + m_Site.Release(); + InterlockedExchange(&g_ConnectStarted, 0); + + if (m_Dispatch) + SendMessage(m_Dispatch, WM_OS_STARTBUTTON_DESTROY, 0, 0); + } + private: static LRESULT CALLBACK DispatchProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ) { @@ -282,6 +349,13 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal tap->ApplyState(enabled); return 0; } + if (msg == WM_OS_STARTBUTTON_DESTROY && tap) + { + tap->m_Dispatch = NULL; + SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); + DestroyWindow(hwnd); + return 0; + } return DefWindowProc(hwnd, msg, wParam, lParam); } @@ -314,7 +388,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal auto it = m_Elements.find(parent); if (it == m_Elements.end()) break; - if (IsStartControl(it->second)) + if (it->second.isStartControl) return true; parent = it->second.parent; } @@ -334,7 +408,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal auto pit = m_Elements.find(parent); if (pit == m_Elements.end()) break; - if (IsStartControl(pit->second)) + if (pit->second.isStartControl) { result = parent; break; @@ -375,6 +449,78 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } } + InstanceHandle FindPrimaryStartLocked( void ) const + { + InstanceHandle primary = 0; + unsigned int bestOrder = 0; + for (auto it = m_Elements.begin(); it != m_Elements.end(); ++it) + { + if (!it->second.isStartControl) + continue; + if (!primary || it->second.discoveryOrder < bestOrder) + { + primary = it->first; + bestOrder = it->second.discoveryOrder; + } + } + return primary; + } + + HRESULT ResolveStartControl( InstanceHandle handle, const StartElement &element, bool *isStartControl ) + { + *isStartControl = false; + if (!IsStartControlCandidate(element)) + return S_OK; + + // Older taskbar implementations may expose a distinct x:Name. + if (element.name.CompareNoCase(L"StartButton") == 0) + { + *isStartControl = true; + return S_OK; + } + + unsigned int sourceCount = 0; + unsigned int valueCount = 0; + PropertyChainSource *sources = NULL; + PropertyChainValue *values = NULL; + HRESULT hr = m_Visual->GetPropertyValuesChain(handle, &sourceCount, &sources, &valueCount, &values); + if (FAILED(hr)) + { + FreeProperties(sources, sourceCount, values, valueCount); + return hr; + } + + for (unsigned int i = 0; i < valueCount; i++) + { + if (!values[i].PropertyName || !values[i].Value) + continue; + if (_wcsicmp(values[i].PropertyName, L"AutomationId") != 0 && + _wcsicmp(values[i].PropertyName, L"AutomationProperties.AutomationId") != 0) + continue; + if (_wcsicmp(values[i].Value, L"StartButton") == 0) + { + *isStartControl = true; + break; + } + } + + FreeProperties(sources, sourceCount, values, valueCount); + return S_OK; + } + + void SetControlClassification( InstanceHandle handle, bool isStartControl ) + { + EnterCriticalSection(&m_Lock); + auto it = m_Elements.find(handle); + if (it != m_Elements.end()) + { + it->second.startControlResolved = true; + it->second.isStartControl = isStartControl; + m_PrimaryStart = FindPrimaryStartLocked(); + } + LeaveCriticalSection(&m_Lock); + } + HRESULT FindProperty( InstanceHandle handle, const wchar_t *name, unsigned int *index, CString *typeName ) { unsigned int sourceCount = 0; @@ -452,13 +598,34 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal return; std::vector> elements; - InstanceHandle primaryStart = 0; EnterCriticalSection(&m_Lock); - primaryStart = m_PrimaryStart; for (auto it = m_Elements.begin(); it != m_Elements.end(); ++it) elements.push_back(*it); LeaveCriticalSection(&m_Lock); + // Start and Task View share ExperienceToggleButton#LaunchListButton on + // current Windows 11 builds. Resolve the attached AutomationId on the XAML + // UI thread before changing any control or descendant. + for (size_t i = 0; i < elements.size(); i++) + { + StartElement &record = elements[i].second; + if (!IsStartControlCandidate(record) || record.startControlResolved) + continue; + + bool isStartControl = false; + if (SUCCEEDED(ResolveStartControl(elements[i].first, record, &isStartControl))) + { + record.startControlResolved = true; + record.isStartControl = isStartControl; + SetControlClassification(elements[i].first, isStartControl); + } + } + + InstanceHandle primaryStart = 0; + EnterCriticalSection(&m_Lock); + primaryStart = m_PrimaryStart; + LeaveCriticalSection(&m_Lock); + const bool allTaskbars = GetSettingBool(L"AllTaskbars"); for (size_t i = 0; i < elements.size(); i++) @@ -466,7 +633,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal InstanceHandle handle = elements[i].first; StartElement record = elements[i].second; - if (IsStartControl(record)) + if (record.isStartControl) { bool target = allTaskbars || !primaryStart || handle == primaryStart; if (enabled && target) @@ -532,6 +699,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal CComPtr m_Site; CComPtr m_Visual; InstanceHandle m_PrimaryStart; + unsigned int m_NextDiscoveryOrder; + bool m_InjectionReferenceBalanced; std::unordered_map m_Elements; }; @@ -737,9 +906,7 @@ void StopWin11StartButtonMonitor( void ) CWin11StartButtonTap *tap = GetTapRef(); if (tap) { - // Synchronous restore: don't let Open-Shell exit while the native - // Start button is still carrying our XAML overrides. - tap->RequestApply(true); + tap->Shutdown(); tap->Release(); } } From 3a9869845c43421fef700e2e7f7dbd90a9265b3c Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sun, 4 Oct 2026 20:42:56 +0200 Subject: [PATCH 03/26] Fix Win11 TAP teardown ordering --- .../StartMenuDLL/Win11StartButton.cpp | 159 ++++++++++++------ 1 file changed, 105 insertions(+), 54 deletions(-) diff --git a/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp b/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp index 01f4a2df0..abed3e766 100644 --- a/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp +++ b/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp @@ -22,7 +22,7 @@ static const GUID CLSID_OpenShellStartButtonTap = { 0x7d15741f, 0x2f3b, 0x4971, { 0xb8, 0x91, 0x6a, 0x5d, 0x42, 0xd7, 0x1a, 0x34 } }; static const UINT WM_OS_STARTBUTTON_APPLY = WM_APP + 0x35B; -static const UINT WM_OS_STARTBUTTON_DESTROY = WM_APP + 0x35C; +static const UINT WM_OS_STARTBUTTON_SHUTDOWN = WM_APP + 0x35C; static volatile LONG g_StartButtonActive = 0; static volatile LONG g_ConnectStarted = 0; @@ -175,38 +175,41 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal m_Site = site; - // InitializeXamlDiagnosticsEx loads the TAP DLL by path even though - // StartMenuDLL is already loaded. Balance that injection reference so - // Open-Shell can unload and initialize the DLL again after an explicit Exit. - if (!m_InjectionReferenceBalanced) - { - HMODULE module = GetThisModule(); - if (module) - { - FreeLibrary(module); - m_InjectionReferenceBalanced = true; - } - } - + // Keep the module reference acquired by InitializeXamlDiagnosticsEx for + // the whole TAP session. It is balanced only after the callback and all + // XAML/COM references have been released. StartMenuDLL has its own module + // reference while Open-Shell is active, so balancing the diagnostics + // reference during teardown cannot unload code that is still executing. + // // Exit may race a connection attempt. Do not attach a new callback after // shutdown has already begun. if (!InterlockedCompareExchange(&g_StartButtonActive, 0, 0)) { m_Site.Release(); + BalanceInjectionReference(); InterlockedExchange(&g_ConnectStarted, 0); return S_OK; } HRESULT hr = site->QueryInterface(__uuidof(IVisualTreeService), (void**)&m_Visual); if (FAILED(hr) || !m_Visual) + { + m_Visual.Release(); + m_Site.Release(); + BalanceInjectionReference(); + InterlockedExchange(&g_ConnectStarted, 0); return hr; + } if (!CreateDispatchWindow()) - return HRESULT_FROM_WIN32(GetLastError()); - - AcquireSRWLockExclusive(&g_TapLock); - g_Tap = this; - ReleaseSRWLockExclusive(&g_TapLock); + { + DWORD error = GetLastError(); + m_Visual.Release(); + m_Site.Release(); + BalanceInjectionReference(); + InterlockedExchange(&g_ConnectStarted, 0); + return HRESULT_FROM_WIN32(error); + } // Advise replays the existing tree. OnVisualTreeChange only records // element handles; all property access is dispatched afterwards. @@ -214,8 +217,30 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (SUCCEEDED(hr)) { m_Advised = true; + + // Publish the TAP only after the subscription is fully established. + // Otherwise an Advise failure can leave g_Tap pointing at an object + // that the XAML runtime is about to release. + AcquireSRWLockExclusive(&g_TapLock); + g_Tap = this; + ReleaseSRWLockExclusive(&g_TapLock); + RequestApply(false); } + else + { + if (m_Dispatch) + { + HWND dispatch = m_Dispatch; + m_Dispatch = NULL; + SetWindowLongPtr(dispatch, GWLP_USERDATA, 0); + DestroyWindow(dispatch); + } + m_Visual.Release(); + m_Site.Release(); + BalanceInjectionReference(); + InterlockedExchange(&g_ConnectStarted, 0); + } LogToFile(STARTUP_LOG, L"Win11StartButton: visual tree advise 0x%08X", hr); return hr; } @@ -295,41 +320,26 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal PostMessage(m_Dispatch, WM_OS_STARTBUTTON_APPLY, 0, 0); } - void Shutdown( void ) + HRESULT Shutdown( void ) { - // Prevent new users of the TAP while teardown is in progress. The caller - // holds a reference, so UnadviseVisualTreeChange cannot destroy this object - // out from under the shutdown sequence. + // SetSite creates the dispatch window on the XAML thread. Run the complete + // XAML/COM teardown on that same thread rather than releasing apartment- + // sensitive interfaces from whichever thread initiated Open-Shell Exit. + if (!m_Dispatch) + return E_UNEXPECTED; + + HRESULT hr = (HRESULT)SendMessage(m_Dispatch, WM_OS_STARTBUTTON_SHUTDOWN, 0, 0); + if (FAILED(hr)) + return hr; + AcquireSRWLockExclusive(&g_TapLock); if (g_Tap == this) g_Tap = NULL; ReleaseSRWLockExclusive(&g_TapLock); - // Restore the native XAML state before detaching the callback. - RequestApply(true); - - if (m_Visual && m_Advised) - { - HRESULT hr = m_Visual->UnadviseVisualTreeChange(static_cast(this)); - if (FAILED(hr)) - { - // Keep the DLL resident rather than leave XAML with a callback into - // unloaded code. Explorer restart remains the safe recovery path. - HMODULE module = NULL; - GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, - (LPCTSTR)&GetThisModule, &module); - LogToFile(STARTUP_LOG, L"Win11StartButton: visual tree unadvise failed 0x%08X", hr); - return; - } - m_Advised = false; - } - - m_Visual.Release(); - m_Site.Release(); InterlockedExchange(&g_ConnectStarted, 0); - - if (m_Dispatch) - SendMessage(m_Dispatch, WM_OS_STARTBUTTON_DESTROY, 0, 0); + BalanceInjectionReference(); + return S_OK; } private: @@ -349,14 +359,53 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal tap->ApplyState(enabled); return 0; } - if (msg == WM_OS_STARTBUTTON_DESTROY && tap) + if (msg == WM_OS_STARTBUTTON_SHUTDOWN && tap) + return tap->ShutdownOnDispatchThread(); + return DefWindowProc(hwnd, msg, wParam, lParam); + } + + HRESULT ShutdownOnDispatchThread( void ) + { + // The window is owned by the same thread on which SetSite ran. Keep all + // operations touching the XAML diagnostics interfaces on this thread. + ApplyState(false); + + if (m_Visual && m_Advised) { - tap->m_Dispatch = NULL; - SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); - DestroyWindow(hwnd); - return 0; + HRESULT hr = m_Visual->UnadviseVisualTreeChange(static_cast(this)); + if (FAILED(hr)) + { + LogToFile(STARTUP_LOG, L"Win11StartButton: visual tree unadvise failed 0x%08X", hr); + return hr; + } + m_Advised = false; + } + + m_Visual.Release(); + m_Site.Release(); + + if (m_Dispatch) + { + HWND dispatch = m_Dispatch; + m_Dispatch = NULL; + SetWindowLongPtr(dispatch, GWLP_USERDATA, 0); + DestroyWindow(dispatch); + } + return S_OK; + } + + void BalanceInjectionReference( void ) + { + if (m_InjectionReferenceBalanced) + return; + + HMODULE module = GetThisModule(); + if (module) + { + // Mark first: FreeLibrary can run loader callbacks before it returns. + m_InjectionReferenceBalanced = true; + FreeLibrary(module); } - return DefWindowProc(hwnd, msg, wParam, lParam); } bool CreateDispatchWindow( void ) @@ -906,7 +955,9 @@ void StopWin11StartButtonMonitor( void ) CWin11StartButtonTap *tap = GetTapRef(); if (tap) { - tap->Shutdown(); + HRESULT hr = tap->Shutdown(); + if (FAILED(hr)) + LogToFile(STARTUP_LOG, L"Win11StartButton: TAP shutdown failed 0x%08X", hr); tap->Release(); } } From 192b2e3a42bc7121c28cfd7ba3acae3cfe359b23 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sun, 4 Oct 2026 20:48:22 +0200 Subject: [PATCH 04/26] Keep Win11 TAP connection worker resident --- .../StartMenuDLL/Win11StartButton.cpp | 55 ++++++++++++------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp b/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp index abed3e766..edb68920d 100644 --- a/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp +++ b/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp @@ -841,31 +841,35 @@ static DWORD WINAPI ConnectAttemptThread( LPVOID param ) return 0; } -static DWORD WINAPI ConnectThread( LPVOID ) +static DWORD FinishConnectThread( HMODULE moduleReference, HMODULE runtime, bool resetConnectionState ) { + if (runtime) + FreeLibrary(runtime); + if (resetConnectionState) + InterlockedExchange(&g_ConnectStarted, 0); + + // The connection worker executes from StartMenuDLL and can outlive the normal + // Open-Shell unload path. Release its private module reference and terminate + // atomically so the DLL cannot disappear underneath the thread's return path. + FreeLibraryAndExitThread(moduleReference, 0); + return 0; +} + +static DWORD WINAPI ConnectThread( LPVOID param ) +{ + HMODULE moduleReference = (HMODULE)param; HMODULE runtime = LoadLibraryEx(L"Windows.UI.Xaml.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); if (!runtime) - { - InterlockedExchange(&g_ConnectStarted, 0); - return 0; - } + return FinishConnectThread(moduleReference, NULL, true); InitXamlDiagnosticsEx_t init = (InitXamlDiagnosticsEx_t)GetProcAddress(runtime, "InitializeXamlDiagnosticsEx"); if (!init) - { - FreeLibrary(runtime); - InterlockedExchange(&g_ConnectStarted, 0); - return 0; - } + return FinishConnectThread(moduleReference, runtime, true); HMODULE module = GetThisModule(); wchar_t dllPath[MAX_PATH]; if (!module || !GetModuleFileName(module, dllPath, _countof(dllPath))) - { - FreeLibrary(runtime); - InterlockedExchange(&g_ConnectStarted, 0); - return 0; - } + return FinishConnectThread(moduleReference, runtime, true); const wchar_t *endpoints[] = { L"VisualDiagConnection1", L"VisualDiagConnection2" }; HRESULT last = E_FAIL; @@ -888,17 +892,14 @@ static DWORD WINAPI ConnectThread( LPVOID ) if (SUCCEEDED(last)) { LogToFile(STARTUP_LOG, L"Win11StartButton: connected using %s", endpoints[i]); - FreeLibrary(runtime); - return 0; + return FinishConnectThread(moduleReference, runtime, false); } } Sleep(500); } LogToFile(STARTUP_LOG, L"Win11StartButton: connection failed 0x%08X", last); - FreeLibrary(runtime); - InterlockedExchange(&g_ConnectStarted, 0); - return 0; + return FinishConnectThread(moduleReference, runtime, true); } static void EnsureConnection( void ) @@ -906,11 +907,23 @@ static void EnsureConnection( void ) if (InterlockedCompareExchange(&g_ConnectStarted, 1, 0) != 0) return; - HANDLE thread = CreateThread(NULL, 0, ConnectThread, NULL, 0, NULL); + HMODULE moduleReference = NULL; + if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)&ConnectThread, &moduleReference)) + { + InterlockedExchange(&g_ConnectStarted, 0); + return; + } + + HANDLE thread = CreateThread(NULL, 0, ConnectThread, moduleReference, 0, NULL); if (thread) + { CloseHandle(thread); + } else + { + FreeLibrary(moduleReference); InterlockedExchange(&g_ConnectStarted, 0); + } } void StartWin11StartButtonMonitor( void ) From 440d76eb690d2020f8fdfa575294f4e549177df7 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:54:33 +0200 Subject: [PATCH 05/26] Isolate Win11 XAML TAP lifetime from StartMenuDLL --- .../StartMenuDLL/Win11StartButton.cpp | 992 ++---------------- .../StartMenuHelper/StartMenuHelper.cpp | 6 + .../StartMenuHelper/StartMenuHelper.vcxproj | 2 + .../StartMenuHelper.vcxproj.filters | 6 + .../StartMenuHelper/StartMenuHelper32.def | 3 + .../StartMenuHelper/StartMenuHelper64.def | 3 + .../StartMenuHelper/Win11StartButtonTap.cpp | 939 +++++++++++++++++ .../StartMenuHelper/Win11StartButtonTap.h | 9 + 8 files changed, 1029 insertions(+), 931 deletions(-) create mode 100644 Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp create mode 100644 Src/StartMenu/StartMenuHelper/Win11StartButtonTap.h diff --git a/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp b/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp index edb68920d..663d9fbe2 100644 --- a/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp +++ b/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp @@ -1,10 +1,8 @@ -// Windows 11 native Start button suppression. +// Windows 11 native Start button suppression bridge. // -// Open-Shell's replacement button is a separate layered window. Windows 11 -// renders its own Start button in XAML, so the old HWND hiding logic cannot -// remove the native glyph. This file uses the public XAML diagnostics API to -// suppress only the native Start glyph and its hit target. The XAML element is -// left in layout, so centered taskbar positioning remains owned by Windows. +// XAML Diagnostics retains its TAP site object for the diagnostics session. +// The TAP therefore lives in StartMenuHelper, which may remain resident in +// Explorer, while StartMenuDLL can still unload/reload on Exit. #include "stdafx.h" #include "Win11StartButton.h" @@ -12,965 +10,97 @@ #include "LogManager.h" #include "ResourceHelper.h" -#include -#include -#include -#include -#include +typedef void (__cdecl *StartWin11StartButtonTap_t)( BOOL enabled, BOOL allTaskbars ); +typedef void (__cdecl *UpdateWin11StartButtonTap_t)( BOOL enabled, BOOL allTaskbars ); +typedef void (__cdecl *StopWin11StartButtonTap_t)( void ); -static const GUID CLSID_OpenShellStartButtonTap = -{ 0x7d15741f, 0x2f3b, 0x4971, { 0xb8, 0x91, 0x6a, 0x5d, 0x42, 0xd7, 0x1a, 0x34 } }; +static HMODULE g_StartButtonTapModule = NULL; +static StartWin11StartButtonTap_t g_StartButtonTapStart = NULL; +static UpdateWin11StartButtonTap_t g_StartButtonTapUpdate = NULL; +static StopWin11StartButtonTap_t g_StartButtonTapStop = NULL; -static const UINT WM_OS_STARTBUTTON_APPLY = WM_APP + 0x35B; -static const UINT WM_OS_STARTBUTTON_SHUTDOWN = WM_APP + 0x35C; - -static volatile LONG g_StartButtonActive = 0; -static volatile LONG g_ConnectStarted = 0; - -static HMODULE GetThisModule( void ) +static bool LoadStartButtonTap( void ) { - HMODULE module = NULL; - GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, - (LPCTSTR)&GetThisModule, &module); - return module; -} - -struct StartElement -{ - InstanceHandle parent; - CString type; - CString name; - bool visibilityOverride; - bool hitTestOverride; - bool startControlResolved; - bool isStartControl; - unsigned int discoveryOrder; - - StartElement( void ) - { - parent = 0; - visibilityOverride = false; - hitTestOverride = false; - startControlResolved = false; - isStartControl = false; - discoveryOrder = 0; - } -}; - -class CWin11StartButtonTap; -static CWin11StartButtonTap *g_Tap = NULL; -static SRWLOCK g_TapLock = SRWLOCK_INIT; - -static bool ContainsText( const CString &text, const wchar_t *part ) -{ - return text.Find(part) >= 0; -} - -static bool IsStartControlCandidate( const StartElement &element ) -{ - if (!ContainsText(element.type, L"ExperienceToggleButton")) - return false; - return element.name == L"LaunchListButton" || element.name == L"StartButton"; -} - -static bool IsStartGlyph( const StartElement &element ) -{ - if (element.name == L"Icon") - return true; - if (ContainsText(element.type, L"AnimatedVisualPlayer") || ContainsText(element.type, L"AepAnimatedIcon")) + if (g_StartButtonTapModule) return true; - if (ContainsText(element.type, L"FontIcon") || ContainsText(element.type, L"PathIcon") || - ContainsText(element.type, L"ImageIcon") || ContainsText(element.type, L"BitmapIcon") || - ContainsText(element.type, L"SymbolIcon")) - return true; - return false; -} - -class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCallback2 -{ -public: - CWin11StartButtonTap( void ) - { - m_Refs = 1; - m_Advised = false; - m_Dispatch = NULL; - m_PrimaryStart = 0; - m_NextDiscoveryOrder = 0; - m_InjectionReferenceBalanced = false; - InitializeCriticalSection(&m_Lock); - } - - ~CWin11StartButtonTap( void ) - { - if (m_Visual && m_Advised) - m_Visual->UnadviseVisualTreeChange(static_cast(this)); - if (m_Dispatch && GetWindowThreadProcessId(m_Dispatch, NULL) == GetCurrentThreadId()) - DestroyWindow(m_Dispatch); - - AcquireSRWLockExclusive(&g_TapLock); - if (g_Tap == this) - g_Tap = NULL; - ReleaseSRWLockExclusive(&g_TapLock); - - InterlockedExchange(&g_ConnectStarted, 0); - DeleteCriticalSection(&m_Lock); - } - - STDMETHODIMP QueryInterface( REFIID riid, void **ppv ) - { - if (!ppv) - return E_POINTER; - *ppv = NULL; - - if (riid == IID_IUnknown || riid == IID_IObjectWithSite) - *ppv = static_cast(this); - else if (riid == __uuidof(IVisualTreeServiceCallback) || riid == __uuidof(IVisualTreeServiceCallback2)) - *ppv = static_cast(this); - else - return E_NOINTERFACE; - - AddRef(); - return S_OK; - } - - STDMETHODIMP_(ULONG) AddRef( void ) - { - return (ULONG)InterlockedIncrement(&m_Refs); - } - - STDMETHODIMP_(ULONG) Release( void ) - { - LONG refs = InterlockedDecrement(&m_Refs); - if (!refs) - delete this; - return (ULONG)refs; - } - - STDMETHODIMP SetSite( IUnknown *site ) - { - if (m_Visual && m_Advised) - { - ApplyState(false); - m_Visual->UnadviseVisualTreeChange(static_cast(this)); - m_Advised = false; - } - m_Visual.Release(); - m_Site.Release(); - - if (!site) - { - AcquireSRWLockExclusive(&g_TapLock); - if (g_Tap == this) - g_Tap = NULL; - ReleaseSRWLockExclusive(&g_TapLock); - InterlockedExchange(&g_ConnectStarted, 0); - return S_OK; - } - - EnterCriticalSection(&m_Lock); - m_Elements.clear(); - m_PrimaryStart = 0; - m_NextDiscoveryOrder = 0; - LeaveCriticalSection(&m_Lock); - - m_Site = site; - - // Keep the module reference acquired by InitializeXamlDiagnosticsEx for - // the whole TAP session. It is balanced only after the callback and all - // XAML/COM references have been released. StartMenuDLL has its own module - // reference while Open-Shell is active, so balancing the diagnostics - // reference during teardown cannot unload code that is still executing. - // - // Exit may race a connection attempt. Do not attach a new callback after - // shutdown has already begun. - if (!InterlockedCompareExchange(&g_StartButtonActive, 0, 0)) - { - m_Site.Release(); - BalanceInjectionReference(); - InterlockedExchange(&g_ConnectStarted, 0); - return S_OK; - } - - HRESULT hr = site->QueryInterface(__uuidof(IVisualTreeService), (void**)&m_Visual); - if (FAILED(hr) || !m_Visual) - { - m_Visual.Release(); - m_Site.Release(); - BalanceInjectionReference(); - InterlockedExchange(&g_ConnectStarted, 0); - return hr; - } - - if (!CreateDispatchWindow()) - { - DWORD error = GetLastError(); - m_Visual.Release(); - m_Site.Release(); - BalanceInjectionReference(); - InterlockedExchange(&g_ConnectStarted, 0); - return HRESULT_FROM_WIN32(error); - } - - // Advise replays the existing tree. OnVisualTreeChange only records - // element handles; all property access is dispatched afterwards. - hr = m_Visual->AdviseVisualTreeChange(static_cast(this)); - if (SUCCEEDED(hr)) - { - m_Advised = true; - // Publish the TAP only after the subscription is fully established. - // Otherwise an Advise failure can leave g_Tap pointing at an object - // that the XAML runtime is about to release. - AcquireSRWLockExclusive(&g_TapLock); - g_Tap = this; - ReleaseSRWLockExclusive(&g_TapLock); - - RequestApply(false); - } - else - { - if (m_Dispatch) - { - HWND dispatch = m_Dispatch; - m_Dispatch = NULL; - SetWindowLongPtr(dispatch, GWLP_USERDATA, 0); - DestroyWindow(dispatch); - } - m_Visual.Release(); - m_Site.Release(); - BalanceInjectionReference(); - InterlockedExchange(&g_ConnectStarted, 0); - } - LogToFile(STARTUP_LOG, L"Win11StartButton: visual tree advise 0x%08X", hr); - return hr; - } - - STDMETHODIMP GetSite( REFIID riid, void **ppv ) - { - if (!ppv) - return E_POINTER; - *ppv = NULL; - if (!m_Site) - return E_FAIL; - return m_Site->QueryInterface(riid, ppv); - } - - STDMETHODIMP OnVisualTreeChange( ParentChildRelation relation, VisualElement element, VisualMutationType mutationType ) - { - bool interesting = false; - - EnterCriticalSection(&m_Lock); - if (mutationType == Remove) - { - auto it = m_Elements.find(element.Handle); - if (it != m_Elements.end()) - { - interesting = it->second.visibilityOverride || it->second.hitTestOverride || - it->second.isStartControl || IsStartControlCandidate(it->second); - bool wasPrimary = element.Handle == m_PrimaryStart; - m_Elements.erase(it); - if (wasPrimary) - m_PrimaryStart = FindPrimaryStartLocked(); - } - } - else if (mutationType == Add) - { - StartElement record; - record.parent = relation.Parent; - record.type = element.Type ? element.Type : L""; - record.name = element.Name ? element.Name : L""; - - auto previous = m_Elements.find(element.Handle); - if (previous != m_Elements.end()) - { - record.visibilityOverride = previous->second.visibilityOverride; - record.hitTestOverride = previous->second.hitTestOverride; - record.startControlResolved = previous->second.startControlResolved; - record.isStartControl = previous->second.isStartControl; - record.discoveryOrder = previous->second.discoveryOrder; - } - else - { - record.discoveryOrder = ++m_NextDiscoveryOrder; - } - m_Elements[element.Handle] = record; - - interesting = IsStartControlCandidate(record) || IsStartGlyph(record) || - IsUnderStartButtonLocked(record.parent); - } - LeaveCriticalSection(&m_Lock); - - if (interesting) - RequestApply(false); - return S_OK; - } - - STDMETHODIMP OnElementStateChanged( InstanceHandle, VisualElementState, LPCWSTR ) - { - return S_OK; - } - - void RequestApply( bool synchronous ) - { - if (!m_Dispatch) - return; - if (synchronous) - SendMessage(m_Dispatch, WM_OS_STARTBUTTON_APPLY, 0, 0); - else - PostMessage(m_Dispatch, WM_OS_STARTBUTTON_APPLY, 0, 0); - } - - HRESULT Shutdown( void ) - { - // SetSite creates the dispatch window on the XAML thread. Run the complete - // XAML/COM teardown on that same thread rather than releasing apartment- - // sensitive interfaces from whichever thread initiated Open-Shell Exit. - if (!m_Dispatch) - return E_UNEXPECTED; - - HRESULT hr = (HRESULT)SendMessage(m_Dispatch, WM_OS_STARTBUTTON_SHUTDOWN, 0, 0); - if (FAILED(hr)) - return hr; - - AcquireSRWLockExclusive(&g_TapLock); - if (g_Tap == this) - g_Tap = NULL; - ReleaseSRWLockExclusive(&g_TapLock); - - InterlockedExchange(&g_ConnectStarted, 0); - BalanceInjectionReference(); - return S_OK; - } - -private: - static LRESULT CALLBACK DispatchProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ) - { - CWin11StartButtonTap *tap = (CWin11StartButtonTap*)GetWindowLongPtr(hwnd, GWLP_USERDATA); - if (msg == WM_NCCREATE) - { - CREATESTRUCT *create = (CREATESTRUCT*)lParam; - tap = (CWin11StartButtonTap*)create->lpCreateParams; - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)tap); - } - if (msg == WM_OS_STARTBUTTON_APPLY && tap) - { - bool enabled = InterlockedCompareExchange(&g_StartButtonActive, 0, 0) != 0 && - GetSettingBool(L"EnableStartButton"); - tap->ApplyState(enabled); - return 0; - } - if (msg == WM_OS_STARTBUTTON_SHUTDOWN && tap) - return tap->ShutdownOnDispatchThread(); - return DefWindowProc(hwnd, msg, wParam, lParam); - } - - HRESULT ShutdownOnDispatchThread( void ) - { - // The window is owned by the same thread on which SetSite ran. Keep all - // operations touching the XAML diagnostics interfaces on this thread. - ApplyState(false); - - if (m_Visual && m_Advised) - { - HRESULT hr = m_Visual->UnadviseVisualTreeChange(static_cast(this)); - if (FAILED(hr)) - { - LogToFile(STARTUP_LOG, L"Win11StartButton: visual tree unadvise failed 0x%08X", hr); - return hr; - } - m_Advised = false; - } - - m_Visual.Release(); - m_Site.Release(); - - if (m_Dispatch) - { - HWND dispatch = m_Dispatch; - m_Dispatch = NULL; - SetWindowLongPtr(dispatch, GWLP_USERDATA, 0); - DestroyWindow(dispatch); - } - return S_OK; - } - - void BalanceInjectionReference( void ) - { - if (m_InjectionReferenceBalanced) - return; - - HMODULE module = GetThisModule(); - if (module) - { - // Mark first: FreeLibrary can run loader callbacks before it returns. - m_InjectionReferenceBalanced = true; - FreeLibrary(module); - } - } - - bool CreateDispatchWindow( void ) - { - if (m_Dispatch) - return true; - - static const wchar_t CLASS_NAME[] = L"OpenShell.Win11StartButtonTap"; - WNDCLASS wc = {}; - HMODULE module = GetThisModule(); - if (!module) - return false; - - wc.lpfnWndProc = DispatchProc; - wc.hInstance = module; - wc.lpszClassName = CLASS_NAME; - if (!RegisterClass(&wc) && GetLastError() != ERROR_CLASS_ALREADY_EXISTS) - return false; - - m_Dispatch = CreateWindowEx(0, CLASS_NAME, L"", 0, 0, 0, 0, 0, - HWND_MESSAGE, NULL, module, this); - return m_Dispatch != NULL; - } - - bool IsUnderStartButtonLocked( InstanceHandle parent ) const - { - for (int depth = 0; depth < 24 && parent; depth++) - { - auto it = m_Elements.find(parent); - if (it == m_Elements.end()) - break; - if (it->second.isStartControl) - return true; - parent = it->second.parent; - } + wchar_t path[MAX_PATH]; + DWORD pathLength = GetModuleFileName(g_Instance, path, _countof(path)); + if (!pathLength || pathLength >= _countof(path)) return false; - } - - InstanceHandle GetStartAncestor( InstanceHandle handle ) - { - InstanceHandle result = 0; - EnterCriticalSection(&m_Lock); - auto it = m_Elements.find(handle); - if (it != m_Elements.end()) - { - InstanceHandle parent = it->second.parent; - for (int depth = 0; depth < 24 && parent; depth++) - { - auto pit = m_Elements.find(parent); - if (pit == m_Elements.end()) - break; - if (pit->second.isStartControl) - { - result = parent; - break; - } - parent = pit->second.parent; - } - } - LeaveCriticalSection(&m_Lock); - return result; - } - - static void FreeProperties( PropertyChainSource *sources, unsigned int sourceCount, - PropertyChainValue *values, unsigned int valueCount ) - { - if (sources) - { - for (unsigned int i = 0; i < sourceCount; i++) - { - SysFreeString(sources[i].TargetType); - SysFreeString(sources[i].Name); - SysFreeString(sources[i].SrcInfo.FileName); - SysFreeString(sources[i].SrcInfo.Hash); - } - CoTaskMemFree(sources); - } - if (values) - { - for (unsigned int i = 0; i < valueCount; i++) - { - SysFreeString(values[i].Type); - SysFreeString(values[i].DeclaringType); - SysFreeString(values[i].ValueType); - SysFreeString(values[i].ItemType); - SysFreeString(values[i].Value); - SysFreeString(values[i].PropertyName); - } - CoTaskMemFree(values); - } - } - - InstanceHandle FindPrimaryStartLocked( void ) const - { - InstanceHandle primary = 0; - unsigned int bestOrder = 0; - for (auto it = m_Elements.begin(); it != m_Elements.end(); ++it) - { - if (!it->second.isStartControl) - continue; - if (!primary || it->second.discoveryOrder < bestOrder) - { - primary = it->first; - bestOrder = it->second.discoveryOrder; - } - } - return primary; - } - - HRESULT ResolveStartControl( InstanceHandle handle, const StartElement &element, bool *isStartControl ) - { - *isStartControl = false; - if (!IsStartControlCandidate(element)) - return S_OK; - - // Older taskbar implementations may expose a distinct x:Name. - if (element.name.CompareNoCase(L"StartButton") == 0) - { - *isStartControl = true; - return S_OK; - } - - unsigned int sourceCount = 0; - unsigned int valueCount = 0; - PropertyChainSource *sources = NULL; - PropertyChainValue *values = NULL; - HRESULT hr = m_Visual->GetPropertyValuesChain(handle, &sourceCount, &sources, &valueCount, &values); - if (FAILED(hr)) - { - FreeProperties(sources, sourceCount, values, valueCount); - return hr; - } - - for (unsigned int i = 0; i < valueCount; i++) - { - if (!values[i].PropertyName || !values[i].Value) - continue; - if (_wcsicmp(values[i].PropertyName, L"AutomationId") != 0 && - _wcsicmp(values[i].PropertyName, L"AutomationProperties.AutomationId") != 0) - continue; - if (_wcsicmp(values[i].Value, L"StartButton") == 0) - { - *isStartControl = true; - break; - } - } - - FreeProperties(sources, sourceCount, values, valueCount); - return S_OK; - } - - void SetControlClassification( InstanceHandle handle, bool isStartControl ) - { - EnterCriticalSection(&m_Lock); - auto it = m_Elements.find(handle); - if (it != m_Elements.end()) - { - it->second.startControlResolved = true; - it->second.isStartControl = isStartControl; - m_PrimaryStart = FindPrimaryStartLocked(); - } - LeaveCriticalSection(&m_Lock); - } - - HRESULT FindProperty( InstanceHandle handle, const wchar_t *name, unsigned int *index, CString *typeName ) - { - unsigned int sourceCount = 0; - unsigned int valueCount = 0; - PropertyChainSource *sources = NULL; - PropertyChainValue *values = NULL; - HRESULT hr = m_Visual->GetPropertyValuesChain(handle, &sourceCount, &sources, &valueCount, &values); - if (FAILED(hr)) - { - FreeProperties(sources, sourceCount, values, valueCount); - return hr; - } - - bool found = false; - for (unsigned int i = 0; i < valueCount; i++) - { - if (!values[i].PropertyName || wcscmp(values[i].PropertyName, name) != 0) - continue; - if (values[i].MetadataBits & 0x2) // IsPropertyReadOnly - continue; - - *index = values[i].Index; - if (typeName) - *typeName = values[i].Type ? values[i].Type : L""; - found = true; - break; - } - FreeProperties(sources, sourceCount, values, valueCount); - return found ? S_OK : HRESULT_FROM_WIN32(ERROR_NOT_FOUND); - } - - HRESULT SetPropertyText( InstanceHandle handle, const wchar_t *name, const wchar_t *valueText ) - { - unsigned int index = 0; - CString typeName; - HRESULT hr = FindProperty(handle, name, &index, &typeName); - if (FAILED(hr) || typeName.IsEmpty()) - return FAILED(hr) ? hr : E_FAIL; - - CComBSTR type(typeName); - CComBSTR value(valueText); - InstanceHandle created = 0; - hr = m_Visual->CreateInstance(type, value, &created); - if (FAILED(hr)) - return hr; - return m_Visual->SetProperty(handle, created, index); - } - - HRESULT ClearPropertyByName( InstanceHandle handle, const wchar_t *name ) - { - unsigned int index = 0; - HRESULT hr = FindProperty(handle, name, &index, NULL); - if (FAILED(hr)) - return hr; - return m_Visual->ClearProperty(handle, index); - } - - void SetOverrideFlags( InstanceHandle handle, bool *visibility, bool *hitTest ) - { - EnterCriticalSection(&m_Lock); - auto it = m_Elements.find(handle); - if (it != m_Elements.end()) - { - if (visibility) - it->second.visibilityOverride = *visibility; - if (hitTest) - it->second.hitTestOverride = *hitTest; - } - LeaveCriticalSection(&m_Lock); - } - - void ApplyState( bool enabled ) - { - if (!m_Visual) - return; - - std::vector> elements; - EnterCriticalSection(&m_Lock); - for (auto it = m_Elements.begin(); it != m_Elements.end(); ++it) - elements.push_back(*it); - LeaveCriticalSection(&m_Lock); - - // Start and Task View share ExperienceToggleButton#LaunchListButton on - // current Windows 11 builds. Resolve the attached AutomationId on the XAML - // UI thread before changing any control or descendant. - for (size_t i = 0; i < elements.size(); i++) - { - StartElement &record = elements[i].second; - if (!IsStartControlCandidate(record) || record.startControlResolved) - continue; - - bool isStartControl = false; - if (SUCCEEDED(ResolveStartControl(elements[i].first, record, &isStartControl))) - { - record.startControlResolved = true; - record.isStartControl = isStartControl; - SetControlClassification(elements[i].first, isStartControl); - } - } - - InstanceHandle primaryStart = 0; - EnterCriticalSection(&m_Lock); - primaryStart = m_PrimaryStart; - LeaveCriticalSection(&m_Lock); - - const bool allTaskbars = GetSettingBool(L"AllTaskbars"); - - for (size_t i = 0; i < elements.size(); i++) - { - InstanceHandle handle = elements[i].first; - StartElement record = elements[i].second; - - if (record.isStartControl) - { - bool target = allTaskbars || !primaryStart || handle == primaryStart; - if (enabled && target) - { - if (!record.hitTestOverride) - { - HRESULT hr = SetPropertyText(handle, L"IsHitTestVisible", L"False"); - if (SUCCEEDED(hr)) - { - bool value = true; - SetOverrideFlags(handle, NULL, &value); - } - } - } - else if (record.hitTestOverride) - { - HRESULT hr = ClearPropertyByName(handle, L"IsHitTestVisible"); - if (SUCCEEDED(hr)) - { - bool value = false; - SetOverrideFlags(handle, NULL, &value); - } - } - continue; - } - - if (!IsStartGlyph(record)) - continue; - - InstanceHandle startAncestor = GetStartAncestor(handle); - if (!startAncestor) - continue; - bool target = allTaskbars || !primaryStart || startAncestor == primaryStart; - - if (enabled && target) - { - if (!record.visibilityOverride) - { - HRESULT hr = SetPropertyText(handle, L"Visibility", L"Collapsed"); - if (SUCCEEDED(hr)) - { - bool value = true; - SetOverrideFlags(handle, &value, NULL); - } - } - } - else if (record.visibilityOverride) - { - HRESULT hr = ClearPropertyByName(handle, L"Visibility"); - if (SUCCEEDED(hr)) - { - bool value = false; - SetOverrideFlags(handle, &value, NULL); - } - } - } - } - - LONG m_Refs; - bool m_Advised; - HWND m_Dispatch; - CRITICAL_SECTION m_Lock; - CComPtr m_Site; - CComPtr m_Visual; - InstanceHandle m_PrimaryStart; - unsigned int m_NextDiscoveryOrder; - bool m_InjectionReferenceBalanced; - std::unordered_map m_Elements; -}; - -static CWin11StartButtonTap *GetTapRef( void ) -{ - CWin11StartButtonTap *tap = NULL; - AcquireSRWLockShared(&g_TapLock); - tap = g_Tap; - if (tap) - tap->AddRef(); - ReleaseSRWLockShared(&g_TapLock); - return tap; -} - -class CStartButtonTapFactory: public IClassFactory -{ -public: - CStartButtonTapFactory( void ) { m_Refs = 1; } - - STDMETHODIMP QueryInterface( REFIID riid, void **ppv ) - { - if (!ppv) - return E_POINTER; - *ppv = NULL; - if (riid != IID_IUnknown && riid != IID_IClassFactory) - return E_NOINTERFACE; - *ppv = static_cast(this); - AddRef(); - return S_OK; - } - - STDMETHODIMP_(ULONG) AddRef( void ) { return (ULONG)InterlockedIncrement(&m_Refs); } - STDMETHODIMP_(ULONG) Release( void ) { return (ULONG)InterlockedDecrement(&m_Refs); } - - STDMETHODIMP CreateInstance( IUnknown *outer, REFIID riid, void **ppv ) - { - if (!ppv) - return E_POINTER; - *ppv = NULL; - if (outer) - return CLASS_E_NOAGGREGATION; - - CWin11StartButtonTap *tap = new CWin11StartButtonTap(); - if (!tap) - return E_OUTOFMEMORY; - HRESULT hr = tap->QueryInterface(riid, ppv); - tap->Release(); - return hr; - } - STDMETHODIMP LockServer( BOOL ) { return S_OK; } - -private: - LONG m_Refs; -}; - -static CStartButtonTapFactory g_Factory; - -// xamlom.h declares DllGetClassObject through the platform headers, so export -// our implementation under that name via a linker alias. -extern "C" HRESULT STDMETHODCALLTYPE OpenShellStartButtonDllGetClassObject( REFCLSID clsid, REFIID riid, LPVOID *ppv ) -{ - if (!IsEqualGUID(clsid, CLSID_OpenShellStartButtonTap)) - return CLASS_E_CLASSNOTAVAILABLE; - return g_Factory.QueryInterface(riid, ppv); -} + wchar_t *name = wcsrchr(path, L'\\'); + if (!name) + return false; + name++; -#ifdef _M_IX86 -#pragma comment(linker, "/EXPORT:DllGetClassObject=_OpenShellStartButtonDllGetClassObject@12,PRIVATE") +#ifdef _WIN64 + const wchar_t helperName[] = L"StartMenuHelper64.dll"; #else -#pragma comment(linker, "/EXPORT:DllGetClassObject=OpenShellStartButtonDllGetClassObject,PRIVATE") + const wchar_t helperName[] = L"StartMenuHelper32.dll"; #endif -typedef HRESULT (WINAPI *InitXamlDiagnosticsEx_t)( LPCWSTR, DWORD, LPCWSTR, LPCWSTR, CLSID, LPCWSTR ); - -struct ConnectAttempt -{ - InitXamlDiagnosticsEx_t init; - const wchar_t *endpoint; - wchar_t dllPath[MAX_PATH]; - HRESULT hr; -}; - -static DWORD WINAPI ConnectAttemptThread( LPVOID param ) -{ - ConnectAttempt *attempt = (ConnectAttempt*)param; - attempt->hr = attempt->init(attempt->endpoint, GetCurrentProcessId(), NULL, - attempt->dllPath, CLSID_OpenShellStartButtonTap, NULL); - return 0; -} - -static DWORD FinishConnectThread( HMODULE moduleReference, HMODULE runtime, bool resetConnectionState ) -{ - if (runtime) - FreeLibrary(runtime); - if (resetConnectionState) - InterlockedExchange(&g_ConnectStarted, 0); - - // The connection worker executes from StartMenuDLL and can outlive the normal - // Open-Shell unload path. Release its private module reference and terminate - // atomically so the DLL cannot disappear underneath the thread's return path. - FreeLibraryAndExitThread(moduleReference, 0); - return 0; -} - -static DWORD WINAPI ConnectThread( LPVOID param ) -{ - HMODULE moduleReference = (HMODULE)param; - HMODULE runtime = LoadLibraryEx(L"Windows.UI.Xaml.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); - if (!runtime) - return FinishConnectThread(moduleReference, NULL, true); - - InitXamlDiagnosticsEx_t init = (InitXamlDiagnosticsEx_t)GetProcAddress(runtime, "InitializeXamlDiagnosticsEx"); - if (!init) - return FinishConnectThread(moduleReference, runtime, true); - - HMODULE module = GetThisModule(); - wchar_t dllPath[MAX_PATH]; - if (!module || !GetModuleFileName(module, dllPath, _countof(dllPath))) - return FinishConnectThread(moduleReference, runtime, true); + const size_t remaining = path + _countof(path) - name; + if (_countof(helperName) > remaining) + return false; + wcscpy_s(name, remaining, helperName); - const wchar_t *endpoints[] = { L"VisualDiagConnection1", L"VisualDiagConnection2" }; - HRESULT last = E_FAIL; - for (int retry = 0; retry < 8 && InterlockedCompareExchange(&g_StartButtonActive, 0, 0); retry++) + HMODULE module = LoadLibraryEx(path, NULL, + LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32); + if (!module) { - for (int i = 0; i < _countof(endpoints); i++) - { - ConnectAttempt attempt = {}; - attempt.init = init; - attempt.endpoint = endpoints[i]; - Strcpy(attempt.dllPath, _countof(attempt.dllPath), dllPath); - attempt.hr = E_FAIL; - - HANDLE thread = CreateThread(NULL, 0, ConnectAttemptThread, &attempt, 0, NULL); - if (!thread) - continue; - WaitForSingleObject(thread, INFINITE); - CloseHandle(thread); - last = attempt.hr; - if (SUCCEEDED(last)) - { - LogToFile(STARTUP_LOG, L"Win11StartButton: connected using %s", endpoints[i]); - return FinishConnectThread(moduleReference, runtime, false); - } - } - Sleep(500); + LogToFile(STARTUP_LOG, L"Win11StartButton: unable to load TAP helper 0x%08X", GetLastError()); + return false; } - LogToFile(STARTUP_LOG, L"Win11StartButton: connection failed 0x%08X", last); - return FinishConnectThread(moduleReference, runtime, true); -} - -static void EnsureConnection( void ) -{ - if (InterlockedCompareExchange(&g_ConnectStarted, 1, 0) != 0) - return; - - HMODULE moduleReference = NULL; - if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)&ConnectThread, &moduleReference)) + StartWin11StartButtonTap_t start = + (StartWin11StartButtonTap_t)GetProcAddress(module, "StartWin11StartButtonTap"); + UpdateWin11StartButtonTap_t update = + (UpdateWin11StartButtonTap_t)GetProcAddress(module, "UpdateWin11StartButtonTap"); + StopWin11StartButtonTap_t stop = + (StopWin11StartButtonTap_t)GetProcAddress(module, "StopWin11StartButtonTap"); + if (!start || !update || !stop) { - InterlockedExchange(&g_ConnectStarted, 0); - return; + LogToFile(STARTUP_LOG, L"Win11StartButton: TAP helper exports are unavailable"); + FreeLibrary(module); + return false; } - HANDLE thread = CreateThread(NULL, 0, ConnectThread, moduleReference, 0, NULL); - if (thread) - { - CloseHandle(thread); - } - else - { - FreeLibrary(moduleReference); - InterlockedExchange(&g_ConnectStarted, 0); - } + g_StartButtonTapModule = module; + g_StartButtonTapStart = start; + g_StartButtonTapUpdate = update; + g_StartButtonTapStop = stop; + return true; } void StartWin11StartButtonMonitor( void ) { - if (!IsWin11()) + if (!IsWin11() || !LoadStartButtonTap()) return; - InterlockedExchange(&g_StartButtonActive, 1); - CWin11StartButtonTap *tap = GetTapRef(); - if (tap) - { - tap->RequestApply(false); - tap->Release(); - return; - } - EnsureConnection(); + g_StartButtonTapStart(GetSettingBool(L"EnableStartButton"), GetSettingBool(L"AllTaskbars")); } void UpdateWin11StartButtonMonitor( void ) { - if (!IsWin11()) + if (!IsWin11() || !g_StartButtonTapModule) return; - CWin11StartButtonTap *tap = GetTapRef(); - if (tap) - { - tap->RequestApply(false); - tap->Release(); - } - else if (InterlockedCompareExchange(&g_StartButtonActive, 0, 0)) - { - EnsureConnection(); - } + g_StartButtonTapUpdate(GetSettingBool(L"EnableStartButton"), GetSettingBool(L"AllTaskbars")); } void StopWin11StartButtonMonitor( void ) { - if (!IsWin11()) + if (!IsWin11() || !g_StartButtonTapModule) return; - InterlockedExchange(&g_StartButtonActive, 0); - CWin11StartButtonTap *tap = GetTapRef(); - if (tap) - { - HRESULT hr = tap->Shutdown(); - if (FAILED(hr)) - LogToFile(STARTUP_LOG, L"Win11StartButton: TAP shutdown failed 0x%08X", hr); - tap->Release(); - } + g_StartButtonTapStop(); + + // Drop only StartMenuDLL's explicit reference. XAML Diagnostics owns the TAP + // module reference for the diagnostics session after a successful connection. + FreeLibrary(g_StartButtonTapModule); + g_StartButtonTapModule = NULL; + g_StartButtonTapStart = NULL; + g_StartButtonTapUpdate = NULL; + g_StartButtonTapStop = NULL; } diff --git a/Src/StartMenu/StartMenuHelper/StartMenuHelper.cpp b/Src/StartMenu/StartMenuHelper/StartMenuHelper.cpp index 5004cb83e..1c9e07c41 100644 --- a/Src/StartMenu/StartMenuHelper/StartMenuHelper.cpp +++ b/Src/StartMenu/StartMenuHelper/StartMenuHelper.cpp @@ -5,6 +5,7 @@ #include "resource.h" #include "StartMenuHelper_h.h" #include "dllmain.h" +#include "Win11StartButtonTap.h" #include "ResourceHelper.h" #include "Settings.h" #include "StringUtils.h" @@ -206,6 +207,11 @@ static void StartStartMenu( void ) STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) { WaitDllInitThread(); + + HRESULT tapResult = GetWin11StartButtonTapClassObject(rclsid, riid, ppv); + if (tapResult != CLASS_E_CLASSNOTAVAILABLE) + return tapResult; + if (rclsid==g_EmulationClsid) { LogToFile(STARTUP_LOG,L"StartMenuHelper: DllGetClassObject1"); diff --git a/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj b/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj index 3e7790656..b7c5e7d84 100644 --- a/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj +++ b/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj @@ -109,6 +109,7 @@ + NotUsing @@ -138,6 +139,7 @@ + diff --git a/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj.filters b/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj.filters index 119fd0dfb..dc32bdb51 100644 --- a/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj.filters +++ b/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj.filters @@ -43,6 +43,9 @@ Source Files + + Source Files + @@ -100,6 +103,9 @@ Header Files + + Header Files + diff --git a/Src/StartMenu/StartMenuHelper/StartMenuHelper32.def b/Src/StartMenu/StartMenuHelper/StartMenuHelper32.def index 5b895b082..9b9d799ff 100644 --- a/Src/StartMenu/StartMenuHelper/StartMenuHelper32.def +++ b/Src/StartMenu/StartMenuHelper/StartMenuHelper32.def @@ -8,3 +8,6 @@ EXPORTS DllRegisterServer PRIVATE DllUnregisterServer PRIVATE DllInstall PRIVATE + StartWin11StartButtonTap + UpdateWin11StartButtonTap + StopWin11StartButtonTap diff --git a/Src/StartMenu/StartMenuHelper/StartMenuHelper64.def b/Src/StartMenu/StartMenuHelper/StartMenuHelper64.def index dd824295a..574e2b446 100644 --- a/Src/StartMenu/StartMenuHelper/StartMenuHelper64.def +++ b/Src/StartMenu/StartMenuHelper/StartMenuHelper64.def @@ -8,3 +8,6 @@ EXPORTS DllRegisterServer PRIVATE DllUnregisterServer PRIVATE DllInstall PRIVATE + StartWin11StartButtonTap + UpdateWin11StartButtonTap + StopWin11StartButtonTap diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp new file mode 100644 index 000000000..5539af2af --- /dev/null +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -0,0 +1,939 @@ +// Windows 11 native Start button suppression. +// +// Open-Shell's replacement button is a separate layered window. Windows 11 +// renders its own Start button in XAML, so the old HWND hiding logic cannot +// remove the native glyph. This file uses the public XAML diagnostics API to +// suppress only the native Start glyph and its hit target. The XAML element is +// left in layout, so centered taskbar positioning remains owned by Windows. + +#include "stdafx.h" +#include "Win11StartButtonTap.h" +#include "StartMenuHelper_h.h" +#include "dllmain.h" +#include "Settings.h" +#include "StringUtils.h" +#include "..\StartMenuDLL\LogManager.h" + +#include +#include +#include +#include +#include + +static const GUID CLSID_OpenShellStartButtonTap = +{ 0x7d15741f, 0x2f3b, 0x4971, { 0xb8, 0x91, 0x6a, 0x5d, 0x42, 0xd7, 0x1a, 0x34 } }; + +static const UINT WM_OS_STARTBUTTON_APPLY = WM_APP + 0x35B; + +static volatile LONG g_StartButtonActive = 0; +static volatile LONG g_StartButtonEnabled = 0; +static volatile LONG g_AllTaskbars = 0; +static volatile LONG g_ConnectStarted = 0; + +static HMODULE GetThisModule( void ) +{ + HMODULE module = NULL; + GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCTSTR)&GetThisModule, &module); + return module; +} + +struct StartElement +{ + InstanceHandle parent; + CString type; + CString name; + bool visibilityOverride; + bool hitTestOverride; + bool startControlResolved; + bool isStartControl; + unsigned int discoveryOrder; + + StartElement( void ) + { + parent = 0; + visibilityOverride = false; + hitTestOverride = false; + startControlResolved = false; + isStartControl = false; + discoveryOrder = 0; + } +}; + +class CWin11StartButtonTap; +static CWin11StartButtonTap *g_Tap = NULL; +static SRWLOCK g_TapLock = SRWLOCK_INIT; + +static bool ContainsText( const CString &text, const wchar_t *part ) +{ + return text.Find(part) >= 0; +} + +static bool IsStartControlCandidate( const StartElement &element ) +{ + if (!ContainsText(element.type, L"ExperienceToggleButton")) + return false; + return element.name == L"LaunchListButton" || element.name == L"StartButton"; +} + +static bool IsStartGlyph( const StartElement &element ) +{ + if (element.name == L"Icon") + return true; + if (ContainsText(element.type, L"AnimatedVisualPlayer") || ContainsText(element.type, L"AepAnimatedIcon")) + return true; + if (ContainsText(element.type, L"FontIcon") || ContainsText(element.type, L"PathIcon") || + ContainsText(element.type, L"ImageIcon") || ContainsText(element.type, L"BitmapIcon") || + ContainsText(element.type, L"SymbolIcon")) + return true; + return false; +} + +static void FreeVisualElementStrings( VisualElement &element ) +{ + SysFreeString(element.Type); + SysFreeString(element.Name); + SysFreeString(element.SrcInfo.FileName); + SysFreeString(element.SrcInfo.Hash); +} + +class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCallback2 +{ +public: + CWin11StartButtonTap( void ) + { + m_Refs = 1; + m_Advised = false; + m_Dispatch = NULL; + m_PrimaryStart = 0; + m_NextDiscoveryOrder = 0; + InitializeCriticalSection(&m_Lock); + _AtlModule.Lock(); + } + + ~CWin11StartButtonTap( void ) + { + if (m_Visual && m_Advised) + m_Visual->UnadviseVisualTreeChange(static_cast(this)); + if (m_Dispatch && GetWindowThreadProcessId(m_Dispatch, NULL) == GetCurrentThreadId()) + DestroyWindow(m_Dispatch); + + AcquireSRWLockExclusive(&g_TapLock); + if (g_Tap == this) + g_Tap = NULL; + ReleaseSRWLockExclusive(&g_TapLock); + + InterlockedExchange(&g_ConnectStarted, 0); + DeleteCriticalSection(&m_Lock); + _AtlModule.Unlock(); + } + + STDMETHODIMP QueryInterface( REFIID riid, void **ppv ) + { + if (!ppv) + return E_POINTER; + *ppv = NULL; + + if (riid == IID_IUnknown || riid == IID_IObjectWithSite) + *ppv = static_cast(this); + else if (riid == __uuidof(IVisualTreeServiceCallback) || riid == __uuidof(IVisualTreeServiceCallback2)) + *ppv = static_cast(this); + else + return E_NOINTERFACE; + + AddRef(); + return S_OK; + } + + STDMETHODIMP_(ULONG) AddRef( void ) + { + return (ULONG)InterlockedIncrement(&m_Refs); + } + + STDMETHODIMP_(ULONG) Release( void ) + { + LONG refs = InterlockedDecrement(&m_Refs); + if (!refs) + delete this; + return (ULONG)refs; + } + + STDMETHODIMP SetSite( IUnknown *site ) + { + if (m_Visual && m_Advised) + { + ApplyState(false); + HRESULT hr = m_Visual->UnadviseVisualTreeChange(static_cast(this)); + if (FAILED(hr)) + { + LogToFile(STARTUP_LOG, L"Win11StartButtonTap: visual tree unadvise failed 0x%08X", hr); + return hr; + } + m_Advised = false; + } + m_Visual.Release(); + m_Site.Release(); + + if (!site) + { + AcquireSRWLockExclusive(&g_TapLock); + if (g_Tap == this) + g_Tap = NULL; + ReleaseSRWLockExclusive(&g_TapLock); + + EnterCriticalSection(&m_Lock); + m_Elements.clear(); + m_PrimaryStart = 0; + m_NextDiscoveryOrder = 0; + LeaveCriticalSection(&m_Lock); + + if (m_Dispatch && GetWindowThreadProcessId(m_Dispatch, NULL) == GetCurrentThreadId()) + { + HWND dispatch = m_Dispatch; + m_Dispatch = NULL; + SetWindowLongPtr(dispatch, GWLP_USERDATA, 0); + DestroyWindow(dispatch); + } + + InterlockedExchange(&g_ConnectStarted, 0); + return S_OK; + } + + EnterCriticalSection(&m_Lock); + m_Elements.clear(); + m_PrimaryStart = 0; + m_NextDiscoveryOrder = 0; + LeaveCriticalSection(&m_Lock); + + m_Site = site; + + // XAML Diagnostics keeps the TAP site object and its module loaded for + // the lifetime of the diagnostics session. Do not reject a late SetSite + // when Open-Shell is inactive: keeping the site attached lets a later + // StartMenuDLL instance reuse the same resident TAP safely. + HRESULT hr = site->QueryInterface(__uuidof(IVisualTreeService), (void**)&m_Visual); + if (FAILED(hr) || !m_Visual) + { + m_Visual.Release(); + m_Site.Release(); + InterlockedExchange(&g_ConnectStarted, 0); + return hr; + } + + if (!CreateDispatchWindow()) + { + DWORD error = GetLastError(); + m_Visual.Release(); + m_Site.Release(); + InterlockedExchange(&g_ConnectStarted, 0); + return HRESULT_FROM_WIN32(error); + } + + // Advise replays the existing tree. OnVisualTreeChange only records + // element handles; all property access is dispatched afterwards. + hr = m_Visual->AdviseVisualTreeChange(static_cast(this)); + if (SUCCEEDED(hr)) + { + m_Advised = true; + + // Publish the TAP only after the subscription is fully established. + // Otherwise an Advise failure can leave g_Tap pointing at an object + // that the XAML runtime is about to release. + AcquireSRWLockExclusive(&g_TapLock); + g_Tap = this; + ReleaseSRWLockExclusive(&g_TapLock); + + RequestApply(false); + } + else + { + if (m_Dispatch) + { + HWND dispatch = m_Dispatch; + m_Dispatch = NULL; + SetWindowLongPtr(dispatch, GWLP_USERDATA, 0); + DestroyWindow(dispatch); + } + m_Visual.Release(); + m_Site.Release(); + InterlockedExchange(&g_ConnectStarted, 0); + } + LogToFile(STARTUP_LOG, L"Win11StartButton: visual tree advise 0x%08X", hr); + return hr; + } + + STDMETHODIMP GetSite( REFIID riid, void **ppv ) + { + if (!ppv) + return E_POINTER; + *ppv = NULL; + if (!m_Site) + return E_FAIL; + return m_Site->QueryInterface(riid, ppv); + } + + STDMETHODIMP OnVisualTreeChange( ParentChildRelation relation, VisualElement element, VisualMutationType mutationType ) + { + // Add notifications transfer ownership of VisualElement's BSTRs to the + // callback. For Remove notifications only element.Handle is valid. + const bool ownsElementStrings = mutationType == Add; + bool interesting = false; + + EnterCriticalSection(&m_Lock); + if (mutationType == Remove) + { + auto it = m_Elements.find(element.Handle); + if (it != m_Elements.end()) + { + interesting = it->second.visibilityOverride || it->second.hitTestOverride || + it->second.isStartControl || IsStartControlCandidate(it->second); + bool wasPrimary = element.Handle == m_PrimaryStart; + m_Elements.erase(it); + if (wasPrimary) + m_PrimaryStart = FindPrimaryStartLocked(); + } + } + else if (mutationType == Add) + { + StartElement record; + record.parent = relation.Parent; + record.type = element.Type ? element.Type : L""; + record.name = element.Name ? element.Name : L""; + + auto previous = m_Elements.find(element.Handle); + if (previous != m_Elements.end()) + { + record.visibilityOverride = previous->second.visibilityOverride; + record.hitTestOverride = previous->second.hitTestOverride; + record.startControlResolved = previous->second.startControlResolved; + record.isStartControl = previous->second.isStartControl; + record.discoveryOrder = previous->second.discoveryOrder; + } + else + { + record.discoveryOrder = ++m_NextDiscoveryOrder; + } + m_Elements[element.Handle] = record; + + interesting = IsStartControlCandidate(record) || IsStartGlyph(record) || + IsUnderStartButtonLocked(record.parent); + } + LeaveCriticalSection(&m_Lock); + + if (interesting) + RequestApply(false); + if (ownsElementStrings) + FreeVisualElementStrings(element); + return S_OK; + } + + STDMETHODIMP OnElementStateChanged( InstanceHandle, VisualElementState, LPCWSTR ) + { + return S_OK; + } + + void RequestApply( bool synchronous ) + { + if (!m_Dispatch) + return; + if (synchronous) + SendMessage(m_Dispatch, WM_OS_STARTBUTTON_APPLY, 0, 0); + else + PostMessage(m_Dispatch, WM_OS_STARTBUTTON_APPLY, 0, 0); + } + + HRESULT Deactivate( void ) + { + // The diagnostics runtime retains this site beyond Open-Shell's lifetime. + // Restore only our overrides; keep the site, callback and dispatch window + // alive so StartMenuDLL can unload/reload independently. + if (!m_Dispatch) + return E_UNEXPECTED; + RequestApply(true); + return S_OK; + } + +private: + static LRESULT CALLBACK DispatchProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ) + { + CWin11StartButtonTap *tap = (CWin11StartButtonTap*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (msg == WM_NCCREATE) + { + CREATESTRUCT *create = (CREATESTRUCT*)lParam; + tap = (CWin11StartButtonTap*)create->lpCreateParams; + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)tap); + } + if (msg == WM_OS_STARTBUTTON_APPLY && tap) + { + bool enabled = InterlockedCompareExchange(&g_StartButtonActive, 0, 0) != 0 && + InterlockedCompareExchange(&g_StartButtonEnabled, 0, 0) != 0; + tap->ApplyState(enabled); + return 0; + } + return DefWindowProc(hwnd, msg, wParam, lParam); + } + + + bool CreateDispatchWindow( void ) + { + if (m_Dispatch) + return true; + + static const wchar_t CLASS_NAME[] = L"OpenShell.Win11StartButtonTap"; + WNDCLASS wc = {}; + HMODULE module = GetThisModule(); + if (!module) + return false; + + wc.lpfnWndProc = DispatchProc; + wc.hInstance = module; + wc.lpszClassName = CLASS_NAME; + if (!RegisterClass(&wc) && GetLastError() != ERROR_CLASS_ALREADY_EXISTS) + return false; + + m_Dispatch = CreateWindowEx(0, CLASS_NAME, L"", 0, 0, 0, 0, 0, + HWND_MESSAGE, NULL, module, this); + return m_Dispatch != NULL; + } + + bool IsUnderStartButtonLocked( InstanceHandle parent ) const + { + for (int depth = 0; depth < 24 && parent; depth++) + { + auto it = m_Elements.find(parent); + if (it == m_Elements.end()) + break; + if (it->second.isStartControl) + return true; + parent = it->second.parent; + } + return false; + } + + InstanceHandle GetStartAncestor( InstanceHandle handle ) + { + InstanceHandle result = 0; + EnterCriticalSection(&m_Lock); + auto it = m_Elements.find(handle); + if (it != m_Elements.end()) + { + InstanceHandle parent = it->second.parent; + for (int depth = 0; depth < 24 && parent; depth++) + { + auto pit = m_Elements.find(parent); + if (pit == m_Elements.end()) + break; + if (pit->second.isStartControl) + { + result = parent; + break; + } + parent = pit->second.parent; + } + } + LeaveCriticalSection(&m_Lock); + return result; + } + + static void FreeProperties( PropertyChainSource *sources, unsigned int sourceCount, + PropertyChainValue *values, unsigned int valueCount ) + { + if (sources) + { + for (unsigned int i = 0; i < sourceCount; i++) + { + SysFreeString(sources[i].TargetType); + SysFreeString(sources[i].Name); + SysFreeString(sources[i].SrcInfo.FileName); + SysFreeString(sources[i].SrcInfo.Hash); + } + CoTaskMemFree(sources); + } + if (values) + { + for (unsigned int i = 0; i < valueCount; i++) + { + SysFreeString(values[i].Type); + SysFreeString(values[i].DeclaringType); + SysFreeString(values[i].ValueType); + SysFreeString(values[i].ItemType); + SysFreeString(values[i].Value); + SysFreeString(values[i].PropertyName); + } + CoTaskMemFree(values); + } + } + + InstanceHandle FindPrimaryStartLocked( void ) const + { + InstanceHandle primary = 0; + unsigned int bestOrder = 0; + for (auto it = m_Elements.begin(); it != m_Elements.end(); ++it) + { + if (!it->second.isStartControl) + continue; + if (!primary || it->second.discoveryOrder < bestOrder) + { + primary = it->first; + bestOrder = it->second.discoveryOrder; + } + } + return primary; + } + + HRESULT ResolveStartControl( InstanceHandle handle, const StartElement &element, bool *isStartControl ) + { + *isStartControl = false; + if (!IsStartControlCandidate(element)) + return S_OK; + + // Older taskbar implementations may expose a distinct x:Name. + if (element.name.CompareNoCase(L"StartButton") == 0) + { + *isStartControl = true; + return S_OK; + } + + unsigned int sourceCount = 0; + unsigned int valueCount = 0; + PropertyChainSource *sources = NULL; + PropertyChainValue *values = NULL; + HRESULT hr = m_Visual->GetPropertyValuesChain(handle, &sourceCount, &sources, &valueCount, &values); + if (FAILED(hr)) + { + FreeProperties(sources, sourceCount, values, valueCount); + return hr; + } + + for (unsigned int i = 0; i < valueCount; i++) + { + if (!values[i].PropertyName || !values[i].Value) + continue; + if (_wcsicmp(values[i].PropertyName, L"AutomationId") != 0 && + _wcsicmp(values[i].PropertyName, L"AutomationProperties.AutomationId") != 0) + continue; + if (_wcsicmp(values[i].Value, L"StartButton") == 0) + { + *isStartControl = true; + break; + } + } + + FreeProperties(sources, sourceCount, values, valueCount); + return S_OK; + } + + void SetControlClassification( InstanceHandle handle, bool isStartControl ) + { + EnterCriticalSection(&m_Lock); + auto it = m_Elements.find(handle); + if (it != m_Elements.end()) + { + it->second.startControlResolved = true; + it->second.isStartControl = isStartControl; + m_PrimaryStart = FindPrimaryStartLocked(); + } + LeaveCriticalSection(&m_Lock); + } + + HRESULT FindProperty( InstanceHandle handle, const wchar_t *name, unsigned int *index, CString *typeName ) + { + unsigned int sourceCount = 0; + unsigned int valueCount = 0; + PropertyChainSource *sources = NULL; + PropertyChainValue *values = NULL; + HRESULT hr = m_Visual->GetPropertyValuesChain(handle, &sourceCount, &sources, &valueCount, &values); + if (FAILED(hr)) + { + FreeProperties(sources, sourceCount, values, valueCount); + return hr; + } + + bool found = false; + for (unsigned int i = 0; i < valueCount; i++) + { + if (!values[i].PropertyName || wcscmp(values[i].PropertyName, name) != 0) + continue; + if (values[i].MetadataBits & 0x2) // IsPropertyReadOnly + continue; + + *index = values[i].Index; + if (typeName) + *typeName = values[i].Type ? values[i].Type : L""; + found = true; + break; + } + FreeProperties(sources, sourceCount, values, valueCount); + return found ? S_OK : HRESULT_FROM_WIN32(ERROR_NOT_FOUND); + } + + HRESULT SetPropertyText( InstanceHandle handle, const wchar_t *name, const wchar_t *valueText ) + { + unsigned int index = 0; + CString typeName; + HRESULT hr = FindProperty(handle, name, &index, &typeName); + if (FAILED(hr) || typeName.IsEmpty()) + return FAILED(hr) ? hr : E_FAIL; + + CComBSTR type(typeName); + CComBSTR value(valueText); + InstanceHandle created = 0; + hr = m_Visual->CreateInstance(type, value, &created); + if (FAILED(hr)) + return hr; + return m_Visual->SetProperty(handle, created, index); + } + + HRESULT ClearPropertyByName( InstanceHandle handle, const wchar_t *name ) + { + unsigned int index = 0; + HRESULT hr = FindProperty(handle, name, &index, NULL); + if (FAILED(hr)) + return hr; + return m_Visual->ClearProperty(handle, index); + } + + void SetOverrideFlags( InstanceHandle handle, bool *visibility, bool *hitTest ) + { + EnterCriticalSection(&m_Lock); + auto it = m_Elements.find(handle); + if (it != m_Elements.end()) + { + if (visibility) + it->second.visibilityOverride = *visibility; + if (hitTest) + it->second.hitTestOverride = *hitTest; + } + LeaveCriticalSection(&m_Lock); + } + + void ApplyState( bool enabled ) + { + if (!m_Visual) + return; + + std::vector> elements; + EnterCriticalSection(&m_Lock); + for (auto it = m_Elements.begin(); it != m_Elements.end(); ++it) + elements.push_back(*it); + LeaveCriticalSection(&m_Lock); + + // Start and Task View share ExperienceToggleButton#LaunchListButton on + // current Windows 11 builds. Resolve the attached AutomationId on the XAML + // UI thread before changing any control or descendant. + for (size_t i = 0; i < elements.size(); i++) + { + StartElement &record = elements[i].second; + if (!IsStartControlCandidate(record) || record.startControlResolved) + continue; + + bool isStartControl = false; + if (SUCCEEDED(ResolveStartControl(elements[i].first, record, &isStartControl))) + { + record.startControlResolved = true; + record.isStartControl = isStartControl; + SetControlClassification(elements[i].first, isStartControl); + } + } + + InstanceHandle primaryStart = 0; + EnterCriticalSection(&m_Lock); + primaryStart = m_PrimaryStart; + LeaveCriticalSection(&m_Lock); + + const bool allTaskbars = InterlockedCompareExchange(&g_AllTaskbars, 0, 0) != 0; + + for (size_t i = 0; i < elements.size(); i++) + { + InstanceHandle handle = elements[i].first; + StartElement record = elements[i].second; + + if (record.isStartControl) + { + bool target = allTaskbars || !primaryStart || handle == primaryStart; + if (enabled && target) + { + if (!record.hitTestOverride) + { + HRESULT hr = SetPropertyText(handle, L"IsHitTestVisible", L"False"); + if (SUCCEEDED(hr)) + { + bool value = true; + SetOverrideFlags(handle, NULL, &value); + } + } + } + else if (record.hitTestOverride) + { + HRESULT hr = ClearPropertyByName(handle, L"IsHitTestVisible"); + if (SUCCEEDED(hr)) + { + bool value = false; + SetOverrideFlags(handle, NULL, &value); + } + } + continue; + } + + if (!IsStartGlyph(record)) + continue; + + InstanceHandle startAncestor = GetStartAncestor(handle); + if (!startAncestor) + continue; + bool target = allTaskbars || !primaryStart || startAncestor == primaryStart; + + if (enabled && target) + { + if (!record.visibilityOverride) + { + HRESULT hr = SetPropertyText(handle, L"Visibility", L"Collapsed"); + if (SUCCEEDED(hr)) + { + bool value = true; + SetOverrideFlags(handle, &value, NULL); + } + } + } + else if (record.visibilityOverride) + { + HRESULT hr = ClearPropertyByName(handle, L"Visibility"); + if (SUCCEEDED(hr)) + { + bool value = false; + SetOverrideFlags(handle, &value, NULL); + } + } + } + } + + LONG m_Refs; + bool m_Advised; + HWND m_Dispatch; + CRITICAL_SECTION m_Lock; + CComPtr m_Site; + CComPtr m_Visual; + InstanceHandle m_PrimaryStart; + unsigned int m_NextDiscoveryOrder; + std::unordered_map m_Elements; +}; + +static CWin11StartButtonTap *GetTapRef( void ) +{ + CWin11StartButtonTap *tap = NULL; + AcquireSRWLockShared(&g_TapLock); + tap = g_Tap; + if (tap) + tap->AddRef(); + ReleaseSRWLockShared(&g_TapLock); + return tap; +} + +class CStartButtonTapFactory: public IClassFactory +{ +public: + CStartButtonTapFactory( void ) { m_Refs = 1; } + + STDMETHODIMP QueryInterface( REFIID riid, void **ppv ) + { + if (!ppv) + return E_POINTER; + *ppv = NULL; + if (riid != IID_IUnknown && riid != IID_IClassFactory) + return E_NOINTERFACE; + *ppv = static_cast(this); + AddRef(); + return S_OK; + } + + STDMETHODIMP_(ULONG) AddRef( void ) { return (ULONG)InterlockedIncrement(&m_Refs); } + STDMETHODIMP_(ULONG) Release( void ) { return (ULONG)InterlockedDecrement(&m_Refs); } + + STDMETHODIMP CreateInstance( IUnknown *outer, REFIID riid, void **ppv ) + { + if (!ppv) + return E_POINTER; + *ppv = NULL; + if (outer) + return CLASS_E_NOAGGREGATION; + + CWin11StartButtonTap *tap = new CWin11StartButtonTap(); + if (!tap) + return E_OUTOFMEMORY; + HRESULT hr = tap->QueryInterface(riid, ppv); + tap->Release(); + return hr; + } + + STDMETHODIMP LockServer( BOOL lock ) + { + if (lock) + _AtlModule.Lock(); + else + _AtlModule.Unlock(); + return S_OK; + } + +private: + LONG m_Refs; +}; + +static CStartButtonTapFactory g_Factory; + +HRESULT GetWin11StartButtonTapClassObject( REFCLSID clsid, REFIID riid, LPVOID *ppv ) +{ + if (!IsEqualGUID(clsid, CLSID_OpenShellStartButtonTap)) + return CLASS_E_CLASSNOTAVAILABLE; + return g_Factory.QueryInterface(riid, ppv); +} + +typedef HRESULT (WINAPI *InitXamlDiagnosticsEx_t)( LPCWSTR, DWORD, LPCWSTR, LPCWSTR, CLSID, LPCWSTR ); + +struct ConnectAttempt +{ + InitXamlDiagnosticsEx_t init; + const wchar_t *endpoint; + wchar_t dllPath[MAX_PATH]; + HRESULT hr; +}; + +static DWORD WINAPI ConnectAttemptThread( LPVOID param ) +{ + ConnectAttempt *attempt = (ConnectAttempt*)param; + attempt->hr = attempt->init(attempt->endpoint, GetCurrentProcessId(), NULL, + attempt->dllPath, CLSID_OpenShellStartButtonTap, NULL); + return 0; +} + +static DWORD FinishConnectThread( HMODULE moduleReference, HMODULE runtime, bool resetConnectionState ) +{ + if (runtime) + FreeLibrary(runtime); + if (resetConnectionState) + InterlockedExchange(&g_ConnectStarted, 0); + + // Keep a private StartMenuHelper reference while this worker is running. + // Release it atomically with thread termination so a failed diagnostics + // connection cannot unload the helper underneath the worker's return path. + FreeLibraryAndExitThread(moduleReference, 0); + return 0; +} + +static DWORD WINAPI ConnectThread( LPVOID param ) +{ + HMODULE moduleReference = (HMODULE)param; + HMODULE runtime = LoadLibraryEx(L"Windows.UI.Xaml.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (!runtime) + return FinishConnectThread(moduleReference, NULL, true); + + InitXamlDiagnosticsEx_t init = (InitXamlDiagnosticsEx_t)GetProcAddress(runtime, "InitializeXamlDiagnosticsEx"); + if (!init) + return FinishConnectThread(moduleReference, runtime, true); + + HMODULE module = GetThisModule(); + wchar_t dllPath[MAX_PATH]; + if (!module || !GetModuleFileName(module, dllPath, _countof(dllPath))) + return FinishConnectThread(moduleReference, runtime, true); + + const wchar_t *endpoints[] = { L"VisualDiagConnection1", L"VisualDiagConnection2" }; + HRESULT last = E_FAIL; + for (int retry = 0; retry < 8 && InterlockedCompareExchange(&g_StartButtonActive, 0, 0); retry++) + { + for (int i = 0; i < _countof(endpoints); i++) + { + ConnectAttempt attempt = {}; + attempt.init = init; + attempt.endpoint = endpoints[i]; + Strcpy(attempt.dllPath, _countof(attempt.dllPath), dllPath); + attempt.hr = E_FAIL; + + HANDLE thread = CreateThread(NULL, 0, ConnectAttemptThread, &attempt, 0, NULL); + if (!thread) + continue; + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); + last = attempt.hr; + if (SUCCEEDED(last)) + { + LogToFile(STARTUP_LOG, L"Win11StartButton: connected using %s", endpoints[i]); + return FinishConnectThread(moduleReference, runtime, false); + } + } + Sleep(500); + } + + LogToFile(STARTUP_LOG, L"Win11StartButton: connection failed 0x%08X", last); + return FinishConnectThread(moduleReference, runtime, true); +} + +static void EnsureConnection( void ) +{ + if (InterlockedCompareExchange(&g_ConnectStarted, 1, 0) != 0) + return; + + HMODULE moduleReference = NULL; + if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)&ConnectThread, &moduleReference)) + { + InterlockedExchange(&g_ConnectStarted, 0); + return; + } + + HANDLE thread = CreateThread(NULL, 0, ConnectThread, moduleReference, 0, NULL); + if (thread) + { + CloseHandle(thread); + } + else + { + FreeLibrary(moduleReference); + InterlockedExchange(&g_ConnectStarted, 0); + } +} + +extern "C" void StartWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) +{ + InterlockedExchange(&g_StartButtonEnabled, enabled ? 1 : 0); + InterlockedExchange(&g_AllTaskbars, allTaskbars ? 1 : 0); + InterlockedExchange(&g_StartButtonActive, 1); + + CWin11StartButtonTap *tap = GetTapRef(); + if (tap) + { + tap->RequestApply(false); + tap->Release(); + return; + } + + EnsureConnection(); +} + +extern "C" void UpdateWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) +{ + InterlockedExchange(&g_StartButtonEnabled, enabled ? 1 : 0); + InterlockedExchange(&g_AllTaskbars, allTaskbars ? 1 : 0); + + CWin11StartButtonTap *tap = GetTapRef(); + if (tap) + { + tap->RequestApply(false); + tap->Release(); + } + else if (InterlockedCompareExchange(&g_StartButtonActive, 0, 0)) + { + EnsureConnection(); + } +} + +extern "C" void StopWin11StartButtonTap( void ) +{ + InterlockedExchange(&g_StartButtonActive, 0); + + CWin11StartButtonTap *tap = GetTapRef(); + if (tap) + { + HRESULT hr = tap->Deactivate(); + if (FAILED(hr)) + LogToFile(STARTUP_LOG, L"Win11StartButtonTap: deactivate failed 0x%08X", hr); + tap->Release(); + } +} diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.h b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.h new file mode 100644 index 000000000..08df88960 --- /dev/null +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +HRESULT GetWin11StartButtonTapClassObject( REFCLSID clsid, REFIID riid, LPVOID *ppv ); + +extern "C" void StartWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ); +extern "C" void UpdateWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ); +extern "C" void StopWin11StartButtonTap( void ); From 2ec3d6d408414298c3ec26f327276e912f8fbbfd Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Mon, 5 Oct 2026 13:12:47 +0200 Subject: [PATCH 06/26] Do not free XAML callback input strings --- .../StartMenuHelper/Win11StartButtonTap.cpp | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 5539af2af..6770e6431 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -89,14 +89,6 @@ static bool IsStartGlyph( const StartElement &element ) return false; } -static void FreeVisualElementStrings( VisualElement &element ) -{ - SysFreeString(element.Type); - SysFreeString(element.Name); - SysFreeString(element.SrcInfo.FileName); - SysFreeString(element.SrcInfo.Hash); -} - class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCallback2 { public: @@ -274,9 +266,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal STDMETHODIMP OnVisualTreeChange( ParentChildRelation relation, VisualElement element, VisualMutationType mutationType ) { - // Add notifications transfer ownership of VisualElement's BSTRs to the - // callback. For Remove notifications only element.Handle is valid. - const bool ownsElementStrings = mutationType == Add; + // VisualElement is an [in] parameter. The XAML diagnostics runtime owns + // the BSTR fields; copy the values we need but never free callback input. bool interesting = false; EnterCriticalSection(&m_Lock); @@ -322,8 +313,6 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (interesting) RequestApply(false); - if (ownsElementStrings) - FreeVisualElementStrings(element); return S_OK; } From b139e9464b0b62b549cefdd9dbe1074a9ba7df4c Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:09:09 +0200 Subject: [PATCH 07/26] Prepare Win11 taskbar thread for XAML Start input --- Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp | 137 +++++++++++++++++--- 1 file changed, 118 insertions(+), 19 deletions(-) diff --git a/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp b/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp index 088569e08..922d4a268 100644 --- a/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp +++ b/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp @@ -50,6 +50,28 @@ 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 bool g_bAllProgramsTimer; static bool g_bInMenu; static DWORD g_LastClickTime; @@ -3081,6 +3103,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) @@ -3334,6 +3357,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,33 +3645,96 @@ 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) - { - // steal messages from other than our custom button window - PostMessage(taskBar->oldButton, (UINT)wParam, 0, MAKELPARAM(info->pt.x, info->pt.y)); - return 1; - } + 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; + + InterlockedExchange(&g_Win11StartInputState,state); + if (state==WIN11_START_INPUT_ACTIVE) + { + if (g_StartMouseHook) + { + UnhookWindowsHookEx(g_StartMouseHook); + g_StartMouseHook=NULL; + LogToFile(STARTUP_LOG,L"Win11StartInput: XAML routing active; WH_MOUSE fallback removed"); } } + else if (!g_StartMouseHook) + { + g_StartMouseHook=SetWindowsHookEx(WH_MOUSE,HookDesktopThreadMouse,NULL,GetCurrentThreadId()); + LogToFile(STARTUP_LOG,L"Win11StartInput: WH_MOUSE fallback restored (%d)",state); + } +} - return CallNextHookEx(NULL, code, wParam, lParam); +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; + } + + 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 @@ -3657,6 +3744,18 @@ static LRESULT CALLBACK HookDesktopThread( int code, WPARAM wParam, LPARAM lPara { MSG *msg=(MSG*)lParam; FindTaskBar(); + + if (msg->message==GetWin11StartInputStateMessage()) + { + LONG state=(LONG)msg->wParam; + if (state>=WIN11_START_INPUT_FALLBACK && state<=WIN11_START_INPUT_ACTIVE) + SetWin11StartInputState(state); + msg->message=WM_NULL; + return CallNextHookEx(NULL,code,wParam,lParam); + } + if (msg->message==GetWin11StartInputMessage()) + RewriteWin11StartInputMessage(msg); + if (IsSettingsMessage(msg)) { msg->message=WM_NULL; @@ -4088,7 +4187,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; From dab54690b639a1a35092ac72af7a9a7d89003fa7 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:14:30 +0200 Subject: [PATCH 08/26] Route Win11 Start mouse input through XAML --- .../StartMenuHelper/Win11StartButtonTap.cpp | 541 ++++++++++++++++++ 1 file changed, 541 insertions(+) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 6770e6431..5c027e027 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -15,8 +15,15 @@ #include "..\StartMenuDLL\LogManager.h" #include +#include +#include #include #include +#include +#include +#include +#include +#include #include #include @@ -25,6 +32,54 @@ static const GUID CLSID_OpenShellStartButtonTap = static const UINT WM_OS_STARTBUTTON_APPLY = WM_APP + 0x35B; +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 FindTaskbarWindowAtPoint( POINT point ) +{ + HWND hwnd = WindowFromPoint(point); + for (int depth = 0; hwnd && depth < 16; depth++) + { + if (IsTaskbarWindow(hwnd)) + return hwnd; + HWND parent = GetParent(hwnd); + if (!parent) + break; + hwnd = parent; + } + + hwnd = WindowFromPoint(point); + 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; static volatile LONG g_AllTaskbars = 0; @@ -60,7 +115,57 @@ struct StartElement } }; +struct StartInputRoute +{ + InstanceHandle startHandle; + InstanceHandle rootHandle; + CComPtr startElement; + CComPtr startFramework; + CComPtr rootElement; + CComPtr handler; + bool verified; + bool pointerInside; + HWND taskbar; + + StartInputRoute( void ) + { + startHandle = 0; + rootHandle = 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 ) + : m_Owner(owner), m_StartHandle(startHandle) + { + } + + 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 +204,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 +272,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) { @@ -206,15 +324,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); @@ -311,6 +438,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } LeaveCriticalSection(&m_Lock); + if (mutationType == Remove && InputRouteUsesHandle(element.Handle)) + interesting = true; if (interesting) RequestApply(false); return S_OK; @@ -331,6 +460,131 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal PostMessage(m_Dispatch, WM_OS_STARTBUTTON_APPLY, 0, 0); } + void ResetInputBridgeState( void ) + { + m_LastInputState = -1; + } + + 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; + route->taskbar = NULL; + return S_OK; + } + + HWND taskbar = FindTaskbarWindowAtPoint(pointerInfo.ptPixelLocation); + if (!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 +597,260 @@ 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 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) + 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"); + 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; + } + + 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; + + auto handler = Microsoft::WRL::Make(this, startHandle); + if (!handler) + return false; + route.handler = handler.Get(); + + HRESULT 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", + (unsigned long long)startHandle, (unsigned long long)route.rootHandle); + 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; + } + 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); @@ -632,6 +1140,20 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal const bool allTaskbars = InterlockedCompareExchange(&g_AllTaskbars, 0, 0) != 0; + std::vector inputTargets; + if (enabled) + { + 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 +1216,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } } } + + EvaluateInputBridgeState(); } LONG m_Refs; @@ -702,11 +1226,27 @@ 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::vector m_InputRoutes; }; +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 +1428,7 @@ extern "C" void StartWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) CWin11StartButtonTap *tap = GetTapRef(); if (tap) { + tap->ResetInputBridgeState(); tap->RequestApply(false); tap->Release(); return; From d237008dda5de75bfd7b47144bf364eac3f1a6df Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:14:47 +0200 Subject: [PATCH 09/26] Link WinRT activation support for XAML input --- Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 4695d2d62e80c0af885882680d562001aa546d12 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:24:10 +0200 Subject: [PATCH 10/26] Fix XAML routed handler ABI and route teardown --- .../StartMenuHelper/Win11StartButtonTap.cpp | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 5c027e027..1240f40cc 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -122,7 +122,8 @@ struct StartInputRoute CComPtr startElement; CComPtr startFramework; CComPtr rootElement; - CComPtr handler; + CComPtr pointerHandler; + CComPtr handler; bool verified; bool pointerInside; HWND taskbar; @@ -373,6 +374,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); @@ -439,7 +441,10 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal LeaveCriticalSection(&m_Lock); if (mutationType == Remove && InputRouteUsesHandle(element.Handle)) + { + RemoveInputRoutesUsingHandle(element.Handle); interesting = true; + } if (interesting) RequestApply(false); return S_OK; @@ -639,6 +644,17 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal return false; } + void RemoveInputRoutesUsingHandle( InstanceHandle handle ) + { + for (size_t i = 0; i < m_InputRoutes.size();) + { + if (m_InputRoutes[i].startHandle == handle || m_InputRoutes[i].rootHandle == handle) + DetachInputRoute(i); + else + i++; + } + } + void InvalidateInputRoute( StartInputRoute &route ) { route.verified = false; @@ -741,9 +757,12 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal auto handler = Microsoft::WRL::Make(this, startHandle); if (!handler) return false; - route.handler = handler.Get(); + route.pointerHandler = handler.Get(); + HRESULT hr = handler->QueryInterface(IID_PPV_ARGS(&route.handler)); + if (FAILED(hr) || !route.handler) + return false; - HRESULT hr = route.rootElement->AddHandler(m_PointerEnteredEvent, route.handler, TRUE); + hr = route.rootElement->AddHandler(m_PointerEnteredEvent, route.handler, TRUE); if (SUCCEEDED(hr)) hr = route.rootElement->AddHandler(m_PointerMovedEvent, route.handler, TRUE); if (SUCCEEDED(hr)) From 3a728672ea9a2cc209bb3502c1fd0212f5251857 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:36:46 +0200 Subject: [PATCH 11/26] Harden XAML input handler lifetime --- .../StartMenuHelper/Win11StartButtonTap.cpp | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 1240f40cc..340ac830e 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -146,10 +146,8 @@ class CStartPointerHandler: ABI::Windows::UI::Xaml::Input::IPointerEventHandler> { public: - CStartPointerHandler( CWin11StartButtonTap *owner, InstanceHandle startHandle ) - : m_Owner(owner), m_StartHandle(startHandle) - { - } + CStartPointerHandler( CWin11StartButtonTap *owner, InstanceHandle startHandle ); + ~CStartPointerHandler( void ); HRESULT STDMETHODCALLTYPE GetTrustLevel( TrustLevel *trustLevel ) override { @@ -440,11 +438,12 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } LeaveCriticalSection(&m_Lock); + // Do not mutate XAML routed-event handler collections from inside the + // visual-tree callback. The posted apply pass runs on this same XAML + // thread after the diagnostics callback has unwound and removes stale + // routes through SyncInputRoutes(). if (mutationType == Remove && InputRouteUsesHandle(element.Handle)) - { - RemoveInputRoutesUsingHandle(element.Handle); interesting = true; - } if (interesting) RequestApply(false); return S_OK; @@ -644,17 +643,6 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal return false; } - void RemoveInputRoutesUsingHandle( InstanceHandle handle ) - { - for (size_t i = 0; i < m_InputRoutes.size();) - { - if (m_InputRoutes[i].startHandle == handle || m_InputRoutes[i].rootHandle == handle) - DetachInputRoute(i); - else - i++; - } - } - void InvalidateInputRoute( StartInputRoute &route ) { route.verified = false; @@ -689,6 +677,12 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal 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; @@ -1260,6 +1254,19 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal 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 ) { From fcda8d2c8317447d67ddaf6246281caca2708e1c Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:37:37 +0200 Subject: [PATCH 12/26] Make XAML input fallback transitions fail safe --- Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp | 22 ++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp b/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp index 922d4a268..3408af8fd 100644 --- a/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp +++ b/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp @@ -3684,20 +3684,32 @@ static void SetWin11StartInputState( LONG state ) if (!IsWin11()) return; - InterlockedExchange(&g_Win11StartInputState,state); if (state==WIN11_START_INPUT_ACTIVE) { if (g_StartMouseHook) { - UnhookWindowsHookEx(g_StartMouseHook); + if (!UnhookWindowsHookEx(g_StartMouseHook)) + { + 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; - LogToFile(STARTUP_LOG,L"Win11StartInput: XAML routing active; WH_MOUSE fallback removed"); } + InterlockedExchange(&g_Win11StartInputState,WIN11_START_INPUT_ACTIVE); + LogToFile(STARTUP_LOG,L"Win11StartInput: XAML routing active; WH_MOUSE fallback removed"); + return; } - else if (!g_StartMouseHook) + + InterlockedExchange(&g_Win11StartInputState,state); + if (!g_StartMouseHook) { g_StartMouseHook=SetWindowsHookEx(WH_MOUSE,HookDesktopThreadMouse,NULL,GetCurrentThreadId()); - LogToFile(STARTUP_LOG,L"Win11StartInput: WH_MOUSE fallback restored (%d)",state); + 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()); } } From b3b3b8a1e2ad1082ff1e3521ad2749a37f671836 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:39:32 +0200 Subject: [PATCH 13/26] Always process XAML input safety state --- Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp | 27 +++++++++++++++------ 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp b/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp index 3408af8fd..4bad55510 100644 --- a/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp +++ b/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp @@ -3752,19 +3752,32 @@ static bool RewriteWin11StartInputMessage( MSG *msg ) // WH_GETMESSAGE hook for the taskbar thread static LRESULT CALLBACK HookDesktopThread( int code, WPARAM wParam, LPARAM lParam ) { - if (code==HC_ACTION && wParam && !g_bInMenu) + // 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 *msg=(MSG*)lParam; - FindTaskBar(); - - if (msg->message==GetWin11StartInputStateMessage()) + MSG *controlMsg=(MSG*)lParam; + if (controlMsg->message==GetWin11StartInputStateMessage()) { - LONG state=(LONG)msg->wParam; + LONG state=(LONG)controlMsg->wParam; if (state>=WIN11_START_INPUT_FALLBACK && state<=WIN11_START_INPUT_ACTIVE) SetWin11StartInputState(state); - msg->message=WM_NULL; + 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); From 7fbd24b9e2211e9ecf9fe48066cafb7afb1a2c06 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:42:13 +0200 Subject: [PATCH 14/26] Drop stale XAML input roots safely --- .../StartMenuHelper/Win11StartButtonTap.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 340ac830e..e11df503c 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -826,6 +826,18 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal 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(); + LeaveCriticalSection(&m_Lock); + } + if (!keep) DetachInputRoute(i); else From 7aac8b3a6d7b4aeff96b156647d4859dd7f305f6 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:42:53 +0200 Subject: [PATCH 15/26] Hold TAP alive during input route updates --- Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index e11df503c..ff7e02dc1 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -887,9 +887,13 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } 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); From 0150c72ff088ddde67bc00693f01de92a4aefd5c Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:45:09 +0200 Subject: [PATCH 16/26] Drain XAML input messages before unload --- Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp b/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp index 4bad55510..071559743 100644 --- a/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp +++ b/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp @@ -72,6 +72,22 @@ static UINT GetWin11StartInputStateMessage( void ) 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; @@ -3316,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); From d038fa457c3feff74a6e83fd0e56843fabd417c6 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:48:53 +0200 Subject: [PATCH 17/26] Bind XAML input routes to taskbar islands --- .../StartMenuHelper/Win11StartButtonTap.cpp | 57 +++++++++++++++++-- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index ff7e02dc1..8b4c82eb7 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -62,9 +63,8 @@ static bool IsTaskbarWindow( HWND hwnd ) wcscmp(className, L"Shell_SecondaryTrayWnd") == 0; } -static HWND FindTaskbarWindowAtPoint( POINT point ) +static HWND FindTaskbarAncestor( HWND hwnd ) { - HWND hwnd = WindowFromPoint(point); for (int depth = 0; hwnd && depth < 16; depth++) { if (IsTaskbarWindow(hwnd)) @@ -75,7 +75,6 @@ static HWND FindTaskbarWindowAtPoint( POINT point ) hwnd = parent; } - hwnd = WindowFromPoint(point); HWND root = hwnd ? GetAncestor(hwnd, GA_ROOT) : NULL; return IsTaskbarWindow(root) ? root : NULL; } @@ -526,12 +525,11 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (route->pointerInside && route->taskbar) PostInputMessage(route->taskbar, WM_MOUSELEAVE, pointerInfo.ptPixelLocation); route->pointerInside = false; - route->taskbar = NULL; return S_OK; } - HWND taskbar = FindTaskbarWindowAtPoint(pointerInfo.ptPixelLocation); - if (!taskbar) + HWND taskbar = route->taskbar; + if (!taskbar || !IsWindow(taskbar)) { InvalidateInputRoute(*route); return S_OK; @@ -726,6 +724,46 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal return false; } + HWND ResolveInputTaskbar( InstanceHandle startHandle ) + { + if (!m_Diagnostics) + return NULL; + + 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 xamlSource; + if (FAILED(inspectable->QueryInterface(IID_PPV_ARGS(&xamlSource))) || !xamlSource) + continue; + + HWND xamlHwnd = NULL; + if (FAILED(xamlSource->get_WindowHandle(&xamlHwnd)) || !xamlHwnd) + continue; + + HWND taskbar = FindTaskbarAncestor(xamlHwnd); + if (taskbar) + return taskbar; + } + return NULL; + } + bool AttachInputRoute( InstanceHandle startHandle ) { if (FindInputRoute(startHandle)) @@ -747,6 +785,13 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal return false; if (!GetInputRoot(startHandle, &route.rootHandle, route.rootElement)) return false; + route.taskbar = ResolveInputTaskbar(startHandle); + 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) From 42c09fc339017c4ef27c349f4d469daa645b6ff7 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:49:56 +0200 Subject: [PATCH 18/26] Track XAML source lifetime for input routes --- .../StartMenuHelper/Win11StartButtonTap.cpp | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 8b4c82eb7..c66a8c4d3 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -118,6 +118,7 @@ struct StartInputRoute { InstanceHandle startHandle; InstanceHandle rootHandle; + InstanceHandle sourceHandle; CComPtr startElement; CComPtr startFramework; CComPtr rootElement; @@ -131,6 +132,7 @@ struct StartInputRoute { startHandle = 0; rootHandle = 0; + sourceHandle = 0; verified = false; pointerInside = false; taskbar = NULL; @@ -636,7 +638,9 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal 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) + if (m_InputRoutes[i].startHandle == handle || + m_InputRoutes[i].rootHandle == handle || + m_InputRoutes[i].sourceHandle == handle) return true; return false; } @@ -724,8 +728,10 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal return false; } - HWND ResolveInputTaskbar( InstanceHandle startHandle ) + HWND ResolveInputTaskbar( InstanceHandle startHandle, InstanceHandle *sourceHandle ) { + if (sourceHandle) + *sourceHandle = 0; if (!m_Diagnostics) return NULL; @@ -759,7 +765,11 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal HWND taskbar = FindTaskbarAncestor(xamlHwnd); if (taskbar) + { + if (sourceHandle) + *sourceHandle = *it2; return taskbar; + } } return NULL; } @@ -785,7 +795,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal return false; if (!GetInputRoot(startHandle, &route.rootHandle, route.rootElement)) return false; - route.taskbar = ResolveInputTaskbar(startHandle); + route.taskbar = ResolveInputTaskbar(startHandle, &route.sourceHandle); if (!route.taskbar) { LogToFile(STARTUP_LOG, L"Win11StartInput: unable to resolve taskbar HWND for handle %llu; keeping WH_MOUSE fallback", @@ -822,8 +832,9 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } m_InputRoutes.push_back(route); - LogToFile(STARTUP_LOG, L"Win11StartInput: route attached for handle %llu via root %llu", - (unsigned long long)startHandle, (unsigned long long)route.rootHandle); + 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; } @@ -879,7 +890,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal { 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].rootHandle) != m_Elements.end() && + m_Elements.find(m_InputRoutes[i].sourceHandle) != m_Elements.end(); LeaveCriticalSection(&m_Lock); } From 3c31480d0eb2ebe86563f203c7aa5ebfb2cf1d97 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:54:13 +0200 Subject: [PATCH 19/26] Use stable XAML source COM ABI --- .../StartMenuHelper/Win11StartButtonTap.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index c66a8c4d3..5e39f25ed 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -31,6 +30,18 @@ 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; +}; + static const UINT WM_OS_STARTBUTTON_APPLY = WM_APP + 0x35B; enum @@ -755,7 +766,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (FAILED(m_Diagnostics->GetIInspectableFromHandle(*it2, &inspectable)) || !inspectable) continue; - CComPtr xamlSource; + CComPtr xamlSource; if (FAILED(inspectable->QueryInterface(IID_PPV_ARGS(&xamlSource))) || !xamlSource) continue; From 314ad8273d5a06b35d83476be0bb28de89a74250 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:55:43 +0200 Subject: [PATCH 20/26] Cover Start input on every taskbar --- .../StartMenuHelper/Win11StartButtonTap.cpp | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 5e39f25ed..86c41d2d6 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -1237,17 +1237,16 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal const bool allTaskbars = InterlockedCompareExchange(&g_AllTaskbars, 0, 0) != 0; + // WH_MOUSE historically intercepts the native Start area on every + // taskbar, even when the custom visual button is shown only on the + // primary taskbar. XAML input coverage must therefore include every + // native Start control before the thread-global hook can be removed. std::vector inputTargets; if (enabled) { 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); - } + if (elements[i].second.isStartControl) + inputTargets.push_back(elements[i].first); } SyncInputRoutes(inputTargets); @@ -1258,8 +1257,11 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (record.isStartControl) { - bool target = allTaskbars || !primaryStart || handle == primaryStart; - if (enabled && target) + // Input interception is global across Explorer's taskbar thread. + // Keep every native Start control visually in place as configured, + // but make all of them transparent to XAML hit testing while the + // Open-Shell input bridge is active. + if (enabled) { if (!record.hitTestOverride) { From 119784990efa56fbd10d1096ab8729bfafc3c497 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:58:28 +0200 Subject: [PATCH 21/26] Match input routes to XAML source content --- .../StartMenuHelper/Win11StartButtonTap.cpp | 66 +++++++++++++------ 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 86c41d2d6..d7c503877 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include static const GUID CLSID_OpenShellStartButtonTap = @@ -42,6 +43,14 @@ IOpenShellDesktopWindowXamlSourceNative : public IUnknown 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; enum @@ -304,6 +313,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); @@ -322,6 +332,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); @@ -412,6 +423,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()) { @@ -444,6 +456,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal record.discoveryOrder = ++m_NextDiscoveryOrder; } m_Elements[element.Handle] = record; + if (record.type == L"Windows.UI.Xaml.Hosting.DesktopWindowXamlSource") + m_XamlSources.insert(element.Handle); interesting = IsStartControlCandidate(record) || IsStartGlyph(record) || IsUnderStartButtonLocked(record.parent); @@ -739,46 +753,56 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal return false; } - HWND ResolveInputTaskbar( InstanceHandle startHandle, InstanceHandle *sourceHandle ) + HWND ResolveInputTaskbar( InstanceHandle, InstanceHandle rootHandle, + ABI::Windows::UI::Xaml::IUIElement *rootElement, InstanceHandle *sourceHandle ) { if (sourceHandle) *sourceHandle = 0; - if (!m_Diagnostics) + if (!m_Diagnostics || !rootElement) return NULL; - std::vector ancestors; + CComPtr rootIdentity; + if (FAILED(rootElement->QueryInterface(IID_PPV_ARGS(&rootIdentity))) || !rootIdentity) + return NULL; + + std::vector sources; 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; - } + for (auto it = m_XamlSources.begin(); it != m_XamlSources.end(); ++it) + sources.push_back(*it); LeaveCriticalSection(&m_Lock); - for (auto it2 = ancestors.rbegin(); it2 != ancestors.rend(); ++it2) + for (size_t i = 0; i < sources.size(); i++) { CComPtr inspectable; - if (FAILED(m_Diagnostics->GetIInspectableFromHandle(*it2, &inspectable)) || !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 xamlSource; - if (FAILED(inspectable->QueryInterface(IID_PPV_ARGS(&xamlSource))) || !xamlSource) + CComPtr nativeSource; + if (FAILED(inspectable->QueryInterface(IID_PPV_ARGS(&nativeSource))) || !nativeSource) continue; HWND xamlHwnd = NULL; - if (FAILED(xamlSource->get_WindowHandle(&xamlHwnd)) || !xamlHwnd) + if (FAILED(nativeSource->get_WindowHandle(&xamlHwnd)) || !xamlHwnd) continue; HWND taskbar = FindTaskbarAncestor(xamlHwnd); if (taskbar) { if (sourceHandle) - *sourceHandle = *it2; + *sourceHandle = sources[i]; return taskbar; } } @@ -806,7 +830,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal return false; if (!GetInputRoot(startHandle, &route.rootHandle, route.rootElement)) return false; - route.taskbar = ResolveInputTaskbar(startHandle, &route.sourceHandle); + 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", @@ -1337,6 +1362,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal size_t m_ExpectedInputRoutes; LONG m_LastInputState; std::unordered_map m_Elements; + std::unordered_set m_XamlSources; std::vector m_InputRoutes; }; From 2522c14cc9dff6d3e807a9ff77f6257d1eaa882f Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:58:56 +0200 Subject: [PATCH 22/26] Retry input routing when XAML sources appear --- Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index d7c503877..3094f567b 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -456,10 +456,11 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal record.discoveryOrder = ++m_NextDiscoveryOrder; } m_Elements[element.Handle] = record; - if (record.type == L"Windows.UI.Xaml.Hosting.DesktopWindowXamlSource") + 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); From abd22dea2c16f8648b88a6f172f8688ffdd4172e Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:03:11 +0200 Subject: [PATCH 23/26] Keep mouse fallback for uncovered taskbars --- .../StartMenuHelper/Win11StartButtonTap.cpp | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 3094f567b..3c2e5d694 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -1263,16 +1263,28 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal const bool allTaskbars = InterlockedCompareExchange(&g_AllTaskbars, 0, 0) != 0; - // WH_MOUSE historically intercepts the native Start area on every - // taskbar, even when the custom visual button is shown only on the - // primary taskbar. XAML input coverage must therefore include every - // native Start control before the thread-global hook can be removed. + 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) + if (enabled && canRetireMouseHook) { for (size_t i = 0; i < elements.size(); i++) - if (elements[i].second.isStartControl) - inputTargets.push_back(elements[i].first); + { + if (!elements[i].second.isStartControl) + continue; + InstanceHandle handle = elements[i].first; + if (allTaskbars || !primaryStart || handle == primaryStart) + inputTargets.push_back(handle); + } } SyncInputRoutes(inputTargets); @@ -1283,11 +1295,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (record.isStartControl) { - // Input interception is global across Explorer's taskbar thread. - // Keep every native Start control visually in place as configured, - // but make all of them transparent to XAML hit testing while the - // Open-Shell input bridge is active. - if (enabled) + bool target = allTaskbars || !primaryStart || handle == primaryStart; + if (enabled && target) { if (!record.hitTestOverride) { From 5ee8252719d94361d67dabfc7feec18ad4c692e5 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:06:35 +0200 Subject: [PATCH 24/26] Reprobe XAML input after taskbar updates --- Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 3c2e5d694..adeff7522 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -493,7 +493,12 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal void ResetInputBridgeState( 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); } HRESULT OnStartPointer( InstanceHandle startHandle, @@ -1593,6 +1598,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->ResetInputBridgeState(); tap->RequestApply(false); tap->Release(); } From 83395739ee6dc2ff9a42daadf5a99996136c1e98 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:07:15 +0200 Subject: [PATCH 25/26] Reprobe input routes on the XAML thread --- .../StartMenuHelper/Win11StartButtonTap.cpp | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index adeff7522..21ec1616c 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -52,6 +52,7 @@ IOpenShellDesktopWindowXamlSource : public IInspectable }; static const UINT WM_OS_STARTBUTTON_APPLY = WM_APP + 0x35B; +static const UINT WM_OS_STARTBUTTON_REPROBE = WM_APP + 0x35C; enum { @@ -491,14 +492,10 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal PostMessage(m_Dispatch, WM_OS_STARTBUTTON_APPLY, 0, 0); } - void ResetInputBridgeState( void ) + void RequestInputBridgeReprobe( 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); + if (m_Dispatch) + PostMessage(m_Dispatch, WM_OS_STARTBUTTON_REPROBE, 0, 0); } HRESULT OnStartPointer( InstanceHandle startHandle, @@ -640,6 +637,16 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal 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)) @@ -984,6 +991,13 @@ 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 @@ -1581,7 +1595,7 @@ extern "C" void StartWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) CWin11StartButtonTap *tap = GetTapRef(); if (tap) { - tap->ResetInputBridgeState(); + tap->RequestInputBridgeReprobe(); tap->RequestApply(false); tap->Release(); return; @@ -1600,7 +1614,7 @@ extern "C" void UpdateWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) { // Re-arm fallback/probing on every taskbar/settings update. Existing // route verification may describe XAML handles from the previous layout. - tap->ResetInputBridgeState(); + tap->RequestInputBridgeReprobe(); tap->RequestApply(false); tap->Release(); } From d89d5dcf3f56bb1f1309aaf91816fcefc38a0723 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:09:42 +0200 Subject: [PATCH 26/26] Queue fallback before detaching XAML routes --- Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 21ec1616c..f727e2b69 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -467,11 +467,14 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal LeaveCriticalSection(&m_Lock); // Do not mutate XAML routed-event handler collections from inside the - // visual-tree callback. The posted apply pass runs on this same XAML - // thread after the diagnostics callback has unwound and removes stale - // routes through SyncInputRoutes(). + // 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;